Rendering Utilities¶
The batched Gaussian-splat renderer behind SplatCamera and BatchSplatCamera (gsplat backend), its .ply
loading and conversion, and the shadow transfer that darkens a composited background where the simulation casts a
shadow. The sensors and rendering guide explains when each is used.
Batched splat renderer¶
Batched Gaussian-splat renderer for Eden, backed by gsplat.
Two rendering regimes, selected by per_env_gaussians:
-
shared (
per_env_gaussians=False) — one Gaussian set is shared by every environment and rasterized from the per-env camera poses (the env axis is used as gsplat's camera batch). The Gaussian geometry (means/quats/scales) is stored once — never replicated or transformed per env — so this is the mode for a scene that is static across envs (a photorealistic background composited under Madrona-rendered robots/objects), and it is the memory-light choice. Given a single camera pose it renders exactly one view, which the caller uses for every env (:class:~eden.entities.camera.SplatCamera). -
per-env (
per_env_gaussians=True) — each env carries its own Gaussians, whose dynamic points are rigidly driven by that env's per-body sim poses (:meth:update), then rasterized with the env axis as gsplat's batch dimension. Means and quats are materializednum_envstimes, so this fits tens–hundreds of envs. Use when splat objects move independently per env, or the scene is domain-randomized per env.
Memory note: gsplat evaluates view-dependent spherical harmonics per rendered
view, so peak VRAM scales with (views_per_chunk * G * K) in both modes
(K = (sh_degree+1)**2). minibatch chunks the env/view axis to bound that
peak — set it whenever num_envs is large; shared mode's win is avoiding the
per-env geometry copy, not escaping the per-view SH cost.
The renderer is deliberately sim-agnostic: it consumes camera extrinsics and
per-body poses as plain torch tensors. Eden's camera/env layer is responsible
for reading those out of Genesis (see SplatComposite in
:mod:eden.entities.camera_overlays).
Classes¶
SplatEntry
dataclass
¶
SplatEntry(ply_path: str, pos: tuple[float, float, float] = (0.0, 0.0, 0.0), quat: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0), scale: float = 1.0, body_row: int | None = None)
One splat PLY plus its placement and (optional) rigid-body binding.
| PARAMETER | DESCRIPTION |
|---|---|
ply_path
|
Path to a standard 3DGS/2DGS
TYPE:
|
pos
|
Baked into the loaded Gaussians. For a static entry this is the world
placement; for a dynamic entry (
TYPE:
|
quat
|
Baked into the loaded Gaussians. For a static entry this is the world
placement; for a dynamic entry (
TYPE:
|
scale
|
Baked into the loaded Gaussians. For a static entry this is the world
placement; for a dynamic entry (
TYPE:
|
body_row
|
TYPE:
|
BatchSplatRenderer
¶
BatchSplatRenderer(entries: list[SplatEntry], num_envs: int, *, per_env_gaussians: bool = False, device: device | str = 'cuda', minibatch: int | None = None)
Batched gsplat renderer with a shared / per-env Gaussian switch.
Source code in eden/utils/batch_splat_renderer/renderer.py
Attributes¶
world_xyz
property
¶
World-frame xyz of every loaded Gaussian (baked placement), for debug-point drawing.
Methods:¶
update
¶
update(body_pos: Tensor, body_quat: Tensor, *, scalar_first: bool = True) -> None
Refresh per-env Gaussians from rigid-body poses (per-env mode).
| PARAMETER | DESCRIPTION |
|---|---|
body_pos
|
TYPE:
|
body_quat
|
TYPE:
|
scalar_first
|
Whether
TYPE:
|
Source code in eden/utils/batch_splat_renderer/renderer.py
render
¶
render(cam_pos: Tensor, cam_rot: Tensor, height: int, width: int, fovy: float | ndarray, *, bg_imgs: Tensor | None = None, return_alpha: bool = False, clamp: bool = True) -> tuple[Tensor, ...]
Render every env's view.
Returns (rgb, depth) or (rgb, depth, alpha) if return_alpha.
rgb is (num_envs, H, W, 3) float in [0, 1] (if clamp),
depth and alpha are (num_envs, H, W, 1).
Source code in eden/utils/batch_splat_renderer/renderer.py
Functions:¶
Gaussian-splat parameter containers (single scene and per-env batch).
Vendored from https://github.com/discoverse-dev/GaussianRenderer.
Classes¶
GaussianData
dataclass
¶
A single set of Gaussians (shared across all environments).
Source code in eden/utils/batch_splat_renderer/gaussiandata.py
Attributes¶
Methods:¶
to_cuda
¶
Source code in eden/utils/batch_splat_renderer/gaussiandata.py
GaussianBatchData
dataclass
¶
Per-environment Gaussians produced by :func:batch_update_gaussians.
Batched gsplat rasterization: single-scene multi-camera (batch_render) and per-env
(batch_env_render).
Vendored from https://github.com/discoverse-dev/GaussianRenderer with Eden edits (tagged
# EDEN): torch.as_tensor camera inputs for zero-copy torch-cuda tensors, an optional
return_alpha for depth/alpha compositing, and the shared genesis/eden geom helpers
in place of the vendored quaternion/point math.
Classes¶
Functions:¶
batch_render
¶
batch_render(gaussians: GaussianData, cam_pos: Tensor, cam_xmat: Tensor, height: int, width: int, fovy: ndarray, bg_imgs: Optional[Tensor] = None, y_up: bool = True, return_alpha: bool = False) -> Tuple[Tensor, ...]
Render one shared Gaussian set from Ncam cameras (shared-scene mode).
Source code in eden/utils/batch_splat_renderer/batch_rasterization.py
batch_env_render
¶
batch_env_render(gaussians: GaussianBatchData, cam_pos: Tensor, cam_xmat: Tensor, height: int, width: int, fovy: ndarray, bg_imgs: Optional[Tensor] = None, minibatch: Optional[int] = None, y_up: bool = True, return_alpha: bool = False) -> Tuple[Tensor, ...]
Render per-environment Gaussians (per-env mode).
Each environment carries its own transformed Gaussian set, so the leading
Nenv axis is a genuine batch dimension for gsplat (not a camera axis).
Source code in eden/utils/batch_splat_renderer/batch_rasterization.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | |
batch_update_gaussians
¶
batch_update_gaussians(gaussian_template: GaussianData, body_pos: Tensor, body_quat: Tensor, point_to_body_idx: Optional[Tensor], dynamic_mask: Optional[Tensor], scalar_first: bool = True) -> GaussianBatchData
Rigidly transform template Gaussians by each env's per-body poses.
xyz/rot are materialized per env: static points (dynamic_mask == False,
e.g. background) keep the template's, dynamic points are moved by their
body's pose. Scale, opacity and SH are broadcast across envs as strided views.
Source code in eden/utils/batch_splat_renderer/batch_rasterization.py
PLY files¶
Load standard 3DGS / 2DGS .ply files into a :class:GaussianData.
Adapted from https://github.com/discoverse-dev/GaussianRenderer (load_ply_3dgs),
trimmed to the standard binary 3DGS/2DGS path (the upstream SuperSplat loader is not vendored).
Classes¶
Functions:¶
load_ply
¶
load_ply(path: str, *, activate: bool = True) -> GaussianData
Load a 3DGS/2DGS PLY into a numpy :class:GaussianData (sh as (N, K, 3)).
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Path to a standard binary 3DGS/2DGS
TYPE:
|
activate
|
Apply the stored-parameter activations (
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
GaussianData
|
|
Source code in eden/utils/batch_splat_renderer/ply_io.py
Utility to convert gaussian splat PLY files to a renderer-compatible format.
The Nyx light-field importer requires SH degree 3 PLY files with
f_rest_0..f_rest_44. Some PLY files (e.g. from WorldLabs) only carry SH
degree 0 (f_dc_0..f_dc_2, no f_rest_*). This utility pads the missing
SH coefficients with zeros so the resulting PLY is accepted by the renderer.
Functions:¶
needs_sh_padding
¶
Check if a PLY file needs SH coefficient padding for renderer compatibility.
Source code in eden/utils/splat_ply_converter.py
convert_to_sh3_ply
¶
Convert a gaussian splat PLY to SH-degree-3 format by padding SH coefficients.
The input must be a binary little-endian PLY (the output header always declares that format).
| PARAMETER | DESCRIPTION |
|---|---|
input_path
|
Path to the input PLY file.
TYPE:
|
output_path
|
Path to write the output PLY. If None, writes to a temp file.
TYPE:
|
sh_degree
|
Target SH degree. Default 3 (requires f_rest_0..f_rest_44).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
Path to the output PLY file (same as input if no conversion needed). |
Source code in eden/utils/splat_ply_converter.py
Background shadows¶
Carry the shadows the sim casts on background surfaces over to a composited background.
A camera's background composite (:class:~eden.entities.camera_overlays.BackgroundComposite) replaces the pixels
of its entities with another image (a static background image or a Gaussian-splat render). The replacement is by
segmentation mask, so the shadows the robot and objects cast onto those surfaces in the sim pass are thrown away with
them, and the objects look like they float above the real table.
:func:background_shadow_factor recovers those shadows from the sim pass itself. Each background
surface is still rendered (with shadows) before it is replaced, so its brightness divided by its
brightness without shadows is the fraction of light that reached it. The result is a per-pixel
multiplier for the replacement image. The brightness without shadows comes from one of two places:
- a second render of the same view with shadows turned off (
unshadowed_rgb). This is exact for any surface, texture and lighting, and is what the rasterizer camera uses; - otherwise, an estimate: a high quantile of each surface's brightness in view, per segmentation id
and per env. This is exact for a flat, untextured surface with diffuse shading under directional
lights (every lit pixel is then equally bright), which is what the batch renderer draws; a
brightness falloff across the surface (a point light, a highlight, a texture) is read as shadow.
It also needs part of each surface to be lit in every view: the reference is taken from the
brightest
1 - _LIT_QUANTILE(3 %) of the surface's pixels, so when more of the surface than that is in shadow (a wrist camera looking at the table under the gripper) a shadowed pixel becomes the reference and the shadow is lost. The camera rejects a textured surface at build time, since that one is known up front; the lit fraction depends on the view and cannot be.
Functions:¶
background_shadow_factor
¶
background_shadow_factor(rgb: Tensor, segm: Tensor, surface_ids: Sequence[int] | Tensor, strength: float, unshadowed_rgb: Tensor | None = None) -> Tensor
Per-pixel multiplier that puts the sim's shadows on a replacement background.
| PARAMETER | DESCRIPTION |
|---|---|
rgb
|
Sim-pass colour,
TYPE:
|
segm
|
Segmentation ids matching
TYPE:
|
surface_ids
|
Segmentation ids of the background surfaces that receive shadows. Each id is normalised on
its own, so surfaces of different colours can share a frame. A tensor on |
strength
|
How much a fully shadowed pixel is darkened, in
TYPE:
|
unshadowed_rgb
|
The same view rendered without shadows, shaped like
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Float multiplier in |