# Rendering
Camera sensors render a Genesis World scene to images off-screen: color, depth, segmentation, and surface normals, plus video. Unlike the {doc}`viewer `, they need no display, so they work headless on a render farm, in a container, or over SSH. This page covers adding a camera, the image types it produces, recording a video, lighting, and the rendering backends, from the fast default rasterizer to photorealistic path tracing.
The complete script is [`examples/tutorials/visualization.py`](https://github.com/Genesis-Embodied-AI/genesis-world/blob/main/examples/tutorials/visualization.py).
## Adding a camera
A camera is a sensor you add to the scene. It renders independently of the viewer, so it is the tool for headless rendering and for capturing views from angles other than the viewer's:
```python
cam = scene.add_camera(
res=(640, 480), # (width, height) in pixels
pos=(3.5, 0.0, 2.5),
lookat=(0, 0, 0.5),
fov=30,
GUI=False,
)
```
With `GUI=True`, the camera opens an OpenCV window that displays each rendered frame. This is separate from the viewer window. Leave it `False` when running headless.
## Rendering images
Build the scene, then call `cam.render()`. The camera can produce four image types: color, depth, segmentation mask, and surface normals. Only RGB is rendered by default; enable the others with keyword flags. `render()` always returns the four in the same order, with disabled types returned as `None`:
```python
scene.build()
# render rgb, depth, segmentation, normal
rgb, depth, segmentation, normal = cam.render(rgb=True, depth=True, segmentation=True, normal=True)
```
Each returned array is shaped `(height, width, ...)` following the `res=(width, height)` you set. By default the segmentation mask stores an integer object index per pixel; set `colorize_seg=True` for a viewable color mask. The index maps back to scene objects at the level set by `VisOptions.segmentation_level` (for example, `link_idx` into `scene.rigid_solver.links`).
```{figure} ../../_static/images/multimodal.png
:alt: The Franka scene rendered four ways: color, depth, segmentation mask, and surface normals
```
:::{tip}
OpenCV windows opened with `GUI=True` sometimes render black on the first frame. Call `cam.render()` again to refresh them, or `cv2.waitKey(1)`.
:::
## Recording a video
To capture a video, call `start_recording()`, render a frame each step, then `stop_recording()` to encode the accumulated frames. Every `cam.render()` call between the two is added to the recording. Here the camera orbits the scene while the simulation steps:
```python
import math
cam.start_recording()
for i in range(120):
scene.step()
cam.set_pose(
pos=(3.0 * math.sin(i / 60), 3.0 * math.cos(i / 60), 2.5),
lookat=(0, 0, 0.5),
)
cam.render()
cam.stop_recording(save_to_filename="video.mp4", fps=60)
```
If you omit `save_to_filename`, Genesis World generates a name from the calling script. The result:
For recording sensor and simulation data (not just video) on a schedule, see {doc}`Recording data `.
## Lighting
The rasterizer (the viewer and any rasterizer camera sensor) lights the scene from a list of lights on `VisOptions`. With no configuration it uses a single directional light, so scenes are lit out of the box; set `lights` to control direction, color, and intensity yourself. The light classes live in `gs.options.vis`:
```python
scene = gs.Scene(
vis_options=gs.options.VisOptions(
lights=[
gs.options.vis.DirectionalLight(
dir=(-1, -1, -1), # direction the light travels, world frame
color=(1.0, 1.0, 1.0), # RGB in [0, 1]
intensity=5.0,
),
gs.options.vis.PointLight(
pos=(2.0, 0.0, 3.0), # meters, world frame
color=(1.0, 0.9, 0.8),
intensity=8.0,
),
],
ambient_light=(0.1, 0.1, 0.1), # uniform fill so shadows are not pure black
),
)
```
Two light types are available:
- **`DirectionalLight`:** parallel rays from a fixed direction, like sunlight. Set `dir` (the direction the light travels), `color`, and `intensity`. Position does not matter.
- **`PointLight`:** light radiating outward from a point. Set `pos`, `color`, and `intensity`.
Ambient light is a separate, uniform fill set through the `ambient_light` field rather than an entry in `lights`.
:::{note}
This controls the rasterizer only. The ray tracer has no light objects: lights there are entities with an `Emission` surface, covered in {doc}`Surfaces and textures `. The `BatchRenderer` backend instead takes lights at runtime through `scene.add_light(...)`.
:::
## Rendering backends
`gs.Scene(renderer=...)` selects how camera sensors turn the scene into pixels. Genesis World provides:
- `gs.renderers.Rasterizer()`: the default. Fast, and what the viewer always uses.
- `gs.renderers.RayTracer()`: a path tracer for photorealistic stills (see [below](#photorealistic-rendering-with-luisa-deprecating)).
- `gs.renderers.BatchRenderer(...)`: high-throughput rendering across many environments (see [Batch rendering with gs-madrona](#batch-rendering-with-gs-madrona)).
### Photorealistic rendering with Nyx
**Nyx** is the recommended path toward photorealistic rendering. Unlike the backends above, it attaches as a camera *sensor* rather than a scene-wide renderer: you add a `NyxCameraOptions` sensor and read frames back from `cam.read().rgb`. It supports PBR materials, HDRI lighting, 3D Gaussian splat assets, multi-camera and multi-environment rendering, and per-pixel object picking. See the {doc}`Nyx renderer ` page for installation, a minimal example, and the full feature set.
:::{note}
**Roadmap.** We are unifying rasterization and path tracing under Nyx as a single, sensor-based rendering interface. Nyx will gradually replace both the Luisa backend below and the default rasterizer. Over time, all camera-based rendering in Genesis World will go through Nyx.
:::
### Photorealistic rendering with Luisa (deprecating)
Genesis World also ships a Luisa-based ray-tracing backend. Enable it by passing `renderer=gs.renderers.RayTracer()` when creating the scene; it exposes extra parameters such as `spp`, `aperture`, and camera `model`.
:::{warning}
This backend is deprecated in favor of Nyx and requires building `LuisaRender` from source. Prefer Nyx for new work.
:::
Setup, tested on Ubuntu 22.04 with CUDA 12.4 and Python 3.9:
1. Fetch the submodule and install the render extras:
```bash
# inside the genesis-world repo
git submodule update --init --recursive
pip install -e ".[render]"
```
2. Ensure `gcc`/`g++` >= 11 and CMake >= 3.26 are on your `PATH`, and install the Vulkan, X11, UUID, and zlib development headers (via `apt` with sudo, or `conda install -c conda-forge` without).
3. Build `LuisaRender`:
```bash
cd genesis/ext/LuisaRender
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D PYTHON_VERSIONS=3.9 \
-D LUISA_COMPUTE_DOWNLOAD_NVCOMP=ON -D LUISA_COMPUTE_ENABLE_GUI=OFF \
-D LUISA_RENDER_BUILD_TESTS=OFF
cmake --build build -j $(nproc)
```
4. Run the demo:
```bash
cd examples/rendering
python demo.py
```
```{figure} ../../_static/images/raytracing_demo.png
:alt: A scene rendered photorealistically with the Luisa ray-tracing backend
```
:::{note}
Prebuilt LuisaRender binaries for common CUDA and Python combinations are available [on Google Drive](https://drive.google.com/drive/folders/1Ah580EIylJJ0v2vGOeSBU_b8zPDWESxS?usp=sharing), named `build__cuda_python`. Download the one matching your system, rename it to `build/`, and place it in `genesis/ext/LuisaRender`. For build and CUDA-toolkit troubleshooting, see the [genesis-world README](https://github.com/Genesis-Embodied-AI/genesis-world#quick-installation).
:::
### Batch rendering with gs-madrona
For high-throughput rendering across many parallel environments, use the gs-madrona backend by passing `renderer=gs.renderers.BatchRenderer(use_rasterizer=True)` (set `use_rasterizer=False` to path-trace instead).
Install the package first. Prebuilt wheels are available on PyPI for x86 and Python >= 3.10:
```bash
pip install gs-madrona
```
Then run the bundled example, which writes frames to `./image_output`:
```bash
python examples/rigid/single_franka_batch_render.py
```
## See also
- {doc}`Visualization `: the interactive viewer and the `gs` command-line tools.
- {doc}`Surfaces and textures `: how entities look when rendered.
- {doc}`Nyx renderer `: photorealistic path tracing in depth.
```{toctree}
:hidden:
:maxdepth: 1
surfaces_textures
nyx_renderer
```