--out folder, which must not exist yet or be empty. This is the layout for the camera, lidar and radar example:
outputName. Ticks are zero-padded to eight digits.
A lidar or radar review video shows the sensor’s right on the right of the picture. Review videos rendered before 13 October 2026 are mirrored left to right; the point clouds and radar files of those renders are correct.
Camera files
--passes rgb,depth (any of rgb, id, depth, semantic) writes only the passes you need. A 1280x720 frame takes about 1 MB as RGB and 3.7 MB as depth, so 7 cameras x 200 frames with all four passes come to 6.3 GB.
The RGB image carries the preset’s camera look: exposure, tone mapping, bloom, a vignette and a slight lens distortion (0.008). Depth, id and semantic are drawn without them, so their pixels line up with an undistorted pinhole. For pixel-exact geometry in the RGB image too, turn the distortion off: --set lens.distortion=0. render.json records every setting in renderConfig.
Lidar files
<tick>.ply is an ASCII PLY point cloud with one row per point:
x, y and z are metres in the lidar’s own frame: x forward along the sensor’s line of sight, y up, and z to the right (unlike the rig’s vehicle frame, whose z points left). intensity is a reflectance proxy in 0 to 1, and instance_id uses the same ids as the cameras’ id pass.
The renderer writes one full scan at every rendered frame, the camera rate: the example’s 10 Hz lidar gives 200 point clouds in 10 seconds at 20 fps, not 100. Every point of a scan is taken at the frame’s instant (no motion during the sweep), and rotationFrequencyHz sets how dense a scan is: each ring has pointsPerSecond / (channels x rotationFrequencyHz) samples, kept between 64 and 4096. Beams that hit nothing within rangeM return no point, so the count varies from frame to frame.
Radar files
<tick>.csv has one detection per row:
Time and synchronization
Every sensor is captured at the same instants. Framen is at time clipStart + n / fps, in seconds from the scenario’s start: results.json gives each camera frame’s sampleTime.seconds, and observed-frames.jsonl each frame’s time. The rig’s clip sets clipStart (0 by default). A frame is an instantaneous snapshot: rolling shutter, exposure time and sensor clock drift are not modelled (results.json says so per frame under camera.sampling).
Poses and calibration
There is no separate calibration file: the rig you rendered with is the calibration.- Intrinsics. A pinhole camera has fx = fy = (width / 2) / tan(horizontalFovDeg / 2) and its principal point at the image centre.
results.jsongives each frame’sprojection:kind,fovYDeg,nearM,farMand the depth convention. - Extrinsics. Each source’s
transformin the rig: its position and rotation on the vehicle. - The vehicle in the world.
observed-frames.jsonlgives every actor’s pose at every frame:position[x, y, z]in metres androtationas a quaternion[x, y, z, w], in the map’s scene frame (x east, y up, z south). - Each camera in the world.
job/render-job.jsonlists every camera at every frame (rig.cameraSchedule): its mount as the renderer applied it (attach), and its position (eye) and a point 50 m along its line of sight (target) in the scene frame.
Load it in Python
The toolkit has no reader for these files; NumPy and Pillow are enough:In the cloud
A cloud 3D render (--cloud) returns each sensor’s video (video/<sensor>.mp4, lidar and radar as review videos) with render.json and results.json. It does not return the per-frame images, depth, id or semantic passes, point clouds or radar files: render locally when you need them. simforge preview frame --cloud returns the RGB frame as a PNG.