Take a snapshot¶
Now that we can load models, it is time to render something.
Resolution¶
By default a snapshot uses the current framebuffer resolution of brayns instance but this one can be relatively low by default and we might need it to position the camera (using its aspect ratio).
That is why we will define it in a variable for later use:
# Snapshot resolution (1920x1080)
resolution = brayns.Resolution.full_hd
Target¶
First we have to choose which models to render and for that, we need the bounds of the area we want to see in the snapshot.
# Focus on a specific model.
target = model.bounds
# Or the entire scene.
target = brayns.get_bounds(instance)
# Or a list of models.
target = brayns.merge_bounds([
item.bounds
for item in models
])
Camera¶
A camera is composed of a view (position, target and up direction) and a projection (3D -> 2D transformation).
As the current camera of a brayns instance is not automatically moved to focus on the current scene, a custom camera is needed to see something.
Camera positioning can be complex and depends on the target orientation, camera projection and resolution aspect ratio.
Therefore, brayns provides a CameraController
class to help building a
camera focusing on a given target.
# Minimal usage with only target bounds.
controller = brayns.CameraController(target)
# More advanced / precise usage.
controller = brayns.CameraController(
target=target,
aspect_ratio=resolution.aspect_ratio,
translation=brayns.Vector3(1, 2, 3),
rotation=brayns.CameraRotation.left,
projection=brayns.OrthographicProjection,
)
# Create a camera using the controller.
camera = controller.camera
Here we use the resolution aspect ratio to make sure the viewport takes also the model width into account.
The advantage of passing a translation and a rotation to the controller instead of moving the camera manually after its creation is that the camera distance computation can take the rotation into account to make sure the entire target is visible.
The camera translation is also easier to compute before the rotation as it is relative to the front view (X right, Y up and Z toward the observer).
Hint
The camera controller is just a dataclass and creates a new camera on each
call to the camera
property. You can update its fields selectively
without affecting already created cameras.
# Create controller.
controller = brayns.CameraController(target)
# Compute the camera settings.
front_camera = controller.camera
# Rotate the camera.
controller.rotation = brayns.CameraRotation.left
# Compute different camera settings.
left_camera = controller.camera
Renderer¶
Brayns has two renderers available, one for fast / interactive rendering and another one for slow and precise rendering (production).
renderer = brayns.InteractiveRenderer()
# Or
renderer = brayns.ProductionRenderer()
The renderer can also be used to configure the number of samples per pixel and the max ray bounces (reflection of light from a non emissive surface to another).
Light¶
By default, Brayns scene is empty, that is why we need to add a light to be able to see what we render.
# Some directional light for shadows.
directional = brayns.DirectionalLight(
intensity=10,
direction=camera.direction,
)
# Lights are models.
light_model = brayns.add_light(instance, directional)
# Some ambient light so the shadows are not completely black.
ambient = brayns.AmbientLight(0.5)
brayns.add_light(instance, ambient)
Here we add a directional light oriented from the camera to the target. The model returned can be used to remove or transform it, but in this example we don’t use it.
Lights can be selectively removed with remove_models
, or cleared using
clear_lights
. They can also be updated with update_model
.
Snapshot¶
Now we have everything we need to take a snapshot.
# Snapshot settings.
snapshot = brayns.Snapshot(
resolution=resolution,
camera=camera,
renderer=renderer,
frame=3,
)
# Download and save the snapshot on the script host.
snapshot.save(instance, 'snapshot.png')
We can here specify also a resolution and a simulation frame. If any of the parameter is None (the default), the current object of the instance is used.
That’s it, snapshots can also be saved on the backend machine using
save_remotely
or retreived as raw bytes using download
.
Snapshot vs Image¶
An image of the current scene can be rendered either using Snapshot
or
Image
.
The Snapshot
renders all accumulation frames in one call using a temporary
context (camera, renderer, framebuffer and simulation frame) so it can use
different settings for rendering without modifying the instance.
The Image
can render one or all accumulation frame(s) using the current
state of an instance. It doesn’t render anything if the max accumulation has
been reached and nothing has changed in the scene.
To summarize, use Image
to make a quick render of the current state of a
Brayns instance and Snapshot
to make a more complex rendering with many
samples per pixel without changing the instance state.
Attention
Image
is usually faster to render than Snapshot
when using a
renderer with few samples per pixel (1-3) but can be a lot slower with more
samples (> 3).
The reason is that image uses the current context so it doesn’t have the overhead of the snapshot to create a temporary one, which makes it faster to render one sample.
However, images render all samples individually using accumulation to allow retreiving intermediate results which is slower than the technique used by the snapshots.