Isaac Sim Headless Rendering (Kit 110 / Isaac Sim 6.0+)

SkillWeb & browsing

Headless RT2/PathTracing production rendering with ACES tuning. Use when capturing frames or validating render quality.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Isaac Sim Headless Rendering (Kit 110 / Isaac Sim 6.0+) skill

What this skill tells your AI

The instructions your AI receives, as published by isaac-sim/isaacsim in skills/isaac-sim-rendering/SKILL.md and read by ahel’s review.

Purpose

Capture production-quality headless frames with RT2 or PathTracing, ACES tone mapping, warehouse lighting patterns, and quantitative validation thresholds.

Prerequisites

  • Built Isaac Sim ($ISAAC_SIM_DIR or _build/linux-x86_64/release).
  • NVIDIA GPU with a current driver (nvidia-smi).
  • Shell env contract from isaac-sim-orchestrator: $ISAAC_SIM_DIR, $ISAAC_LAB_DIR, $WORKSPACE_DIR.

Limitations

  • Targets Isaac Sim 6 / Kit 110 unless a section states otherwise.
  • Does not replace official NVIDIA documentation for unsupported edge cases.

Troubleshooting

Error / symptomCauseSolution
Extension or import not foundWrong $ISAAC_SIM_DIR or stale buildPoint env vars at _build/linux-x86_64/release or rebuild
Black or empty framesMissing lights or non-RTX render modeAdd dome/key light; confirm RTX / PathTracing settings
Hang on stage load or first renderMDL compile or oversized stageFollow isolation steps in isaac-sim-troubleshooting

Capture pipeline, lighting recipes, ACES calibration, camera math, validation. Host-agnostic; adapt paths to your environment.

Available Scripts

ScriptPurposeArguments
scripts/capture_pipeline.pyStandard Kit 110 / Isaac Sim 6.0+ headless capture pipelinesee script --help
scripts/look_at_camera.pyLook-at camera math for USD cameras (Z-up, USD -Z forward convention)see script --help
scripts/warehouse_lighting.pyMulti-layer warehouse lighting recipes for headless Isaac Sim renderingsee script --help

Running scripts

From agent runtimes that expose skill execution helpers, invoke helpers with run_script():

run_script("scripts/capture_pipeline.py", args=["--help"])

From a built Isaac Sim tree, run the same file with ./python.sh (Linux) or python.bat (Windows) from _build/*/release, or execute shell helpers directly when they do not require the simulator.

Read first

  • navigation-primitives: look-at chase camera math (cross-referenced).

Capture: SimulationApp + Replicator RGB Annotator

Standard Kit 110 / Isaac Sim 6.0+ capture pipeline. Works headless including on ARM64 / GB10 Spark (the older 5.1.0 black-frame bug is resolved).

setup_capture_pipeline(stage_path, width, height, renderer, settle_frames) — open stage, define camera, attach RGB annotator, settle, return (app, rgb_annot, render_product). capture_frame(rgb_annot) — step replicator and return (H, W, 3) uint8 RGB array.

See scripts/capture_pipeline.py.

For live controller demos where simulation remains the time authority, disable Replicator capture-on-play and capture snapshots without pausing the timeline. See scripts/capture_pipeline.py for the executable pattern.

Inside a Python-server file that is otherwise synchronous, avoid the sync rep.orchestrator.step() call in live Kit. Schedule step_async(...) and pump app_utils.update_app() until the task completes; this prevents event-loop reentrancy failures while still advancing the render product.

Capture method choice:

  • omni.replicator.core RGB annotator -> reliable, supports any resolution.
  • RtxCamera + CameraSensor (from isaacsim.sensors.experimental.rtx) for tick-rate control, OpenCV / fisheye lens distortion, ISP, tiled multi-view, or stereo depth (see isaac-camera).
  • Swapchain capture -> also works on Kit 110 if you explicitly set window size matching the render resolution.
  • Replicator render products may return empty arrays for Gaussian splat scenes; fall back to swapchain capture in that case.

RT2 vs PathTracing

settings.set("/rtx/rendermode", "RayTracedLighting")  # RT2 — real-time
# settings.set("/rtx/rendermode", "PathTracing")      # offline only
ModeConvergencePer-frame timeUse for
RayTracedLighting (RT2)~200 settle frames (~10-15s)10-15sAll iterative work, warehouse scenes, training data
PathTracingconverges over many subframes5-30 minFinal hero shots only, when explicitly requested

Default to RT2. Switch to PathTracing only after RT2 has been calibrated and the user asks for hero quality.

Headless Lighting — Add Explicit Lights

Headless Isaac Sim has NO default lighting. Without explicit lights, frames are black (RGB=0). Always inject at least a DomeLight + DistantLight baseline.

from pxr import UsdLux, UsdGeom, Gf

dome = UsdLux.DomeLight.Define(stage, "/World/DomeLight")
dome.GetIntensityAttr().Set(400.0)

sun = UsdLux.DistantLight.Define(stage, "/World/Sun")
sun.GetIntensityAttr().Set(1500.0)
UsdGeom.Xformable(sun.GetPrim()).AddRotateXYZOp().Set(Gf.Vec3f(-50, 20, 0))

Baseline Intensity Guide

Scene TypeDomeLightDistantLightNotes
Warehouse (default)4001500Good general balance
Close-up robot3001200Slightly softer
Outdoor5002000Brighter sun
Dark/moody100800Dramatic shadows

ACES Tone Mapping — The Single Biggest Quality Lever

Without ACES, no amount of intensity tuning produces balanced indoor renders. This is the single most impactful render setting after lighting.

import carb
s = carb.settings.get_settings()

s.set("/rtx/post/tonemap/op", 4)            # ACES
s.set("/rtx/post/tonemap/filmIso", 600.0)   # key parameter (see table)
s.set("/rtx/post/tonemap/whitepoint", 6500.0)
s.set("/rtx/post/tonemap/enabled", True)
s.set("/rtx/post/aa/op", 3)                 # TAA for RT2

filmIso Calibration (validated on warehouse interiors)

ScenefilmIsoNotes
General warehouse RT2200Photorealistic starting point
Deep-aisle indoor (hero camera)600Best balance across hero/overview/aisle/topdown
Aerial/overview-heavy400Avoid overexposure on open views

Anti-Recipes (don't waste time on these)

  • Wide rect lights (width=5+) → flat, no light pools
  • High dome intensity (400+) with ACES filmIso 600 → washes out shadows
  • Reinhard tonemapping → muddy, low contrast
  • PathTracing for iterative work → 5-30 min per frame, kills velocity

Warehouse Lighting Recipe (proven 7/10 → 9/10)

The biggest single quality improvement came from this lighting + fog recipe.

add_warehouse_lighting(stage, n_lights, settings) — low-ambient dome + focused rect lights + optional fog. Pass settings=carb.settings.get_settings() to enable fog.

See scripts/warehouse_lighting.py.

For 40m warehouse: fog density 0.003 adds depth without murk.

Deep-Aisle Indoor Lighting

Problem

Ground-level camera in narrow aisle = black frame (82KB / mean_RGB < 5). Ceiling rect lights at Z=10m can't illuminate a 3.5m-wide × 8m-tall aisle to ground level — RT2 struggles with deep occlusion.

Solution: Multi-Layer Lighting

# Layer 1: dense ceiling grid (6×12 across facility)
# Rect lights at ceil_z-0.3, pointing down
# intensity=200000, width=4.0, height=3.0  (wide coverage)

# Layer 2: low sphere lights IN each aisle at Z=3.5m (head-height)
# Directly in camera FOV between tier 1 and ground
for aisle_y, lx in aisle_light_positions:
    lt = UsdLux.SphereLight.Define(stage, lp)
    lt.GetRadiusAttr().Set(0.15)
    lt.GetIntensityAttr().Set(100000.0)
  • 500 settle frames for indoor aisle scenes (not 200-300)
  • Dome at 300 intensity is optional ambient fill — don't go higher or open views wash out

Dome vs Deep-Aisle Tension (fundamental conflict in enclosed scenes)

  • High dome → overview/topdown overexpose (mean > 220)
  • Low/no dome → deep aisle underexpose (mean < 10)
  • Best balance: no dome + sphere lights in aisles + 500K rect grids + 500 settle frames
    • Hero aisle: mean ~60
    • Overview (elevated 3/4): mean ~140-175
    • Cross-aisle: mean ~230

Validated ACES filmIso=600 Light Intensities

  • Ceiling rect lights: 70,000 intensity, 2.5×1.5m, warm white (1.0, 0.97, 0.92)
  • Aisle sphere lights: 15,000 intensity, radius=0.1, at Z=3.5m
  • Grid: 8×14 ceiling panels
  • No dome light — ACES handles exposure
  • Result: mean 60–155 across all view types

Camera tip: place "hero" camera at cross-aisle intersections, not deep in narrow aisles. The junction has more open space for light to reach.

Frame Quality Validation

Always validate captured frames before delivery. Don't ship black/overexposed frames.

IndicatorMeaningAction
File ~82KBBlack frame (RGBA padding only)Add explicit lights
File 200–500KBPartial render / very simple sceneCheck settle frames
File 1–2MBFull rendered frameOK
rgb.max() == 0No lighting reaching cameraAdd DomeLight + DistantLight
rgb.max() > 200, mean 60–180Good renderOK
rgb.mean() > 220OverexposedReduce light intensity or filmIso
rgb.mean() < 10UnderexposedAdd aisle-level lights or raise filmIso
import numpy as np
def validate_frame(rgb_array):
    """Returns (ok: bool, reason: str)."""
    if rgb_array.max() == 0:
        return False, "no light reaches camera — add DomeLight + DistantLight"
    if rgb_array.mean() > 220:
        return False, f"overexposed (mean={rgb_array.mean():.0f}) — reduce intensity"
    if rgb_array.mean() < 10:
        return False, f"underexposed (mean={rgb_array.mean():.0f}) — add aisle lights"
    return True, f"ok (mean={rgb_array.mean():.0f}, max={rgb_array.max()})"

Look-At Camera Math

For chase/POV/overview cameras pointing at a target, always use a look-at matrix. Don't hand-tune Euler angles — they're brittle and you'll waste hours on sign flips.

look_at_matrix(eye, target, up) — returns Gf.Matrix4d for a USD camera at eye looking at target. Handles degenerate up-vector (straight down/up).

See scripts/look_at_camera.py.

Third-Person Camera Offsets (Z-up, robot facing +X at yaw=0)

DirectionVector
Behind robot-X
Right of robot-Y
Left of robot+Y
Above robot+Z
import math

behind_dir_x = -math.cos(yaw)
behind_dir_y = -math.sin(yaw)
right_dir_x  = -math.sin(yaw)
right_dir_y  =  math.cos(yaw)

cam_x = robot_x + behind_dist * behind_dir_x + side_offset * right_dir_x
cam_y = robot_y + behind_dist * behind_dir_y + side_offset * right_dir_y
cam_z = height
  • side_offset = -2.5 → camera on robot's right
  • side_offset = +2.5 → camera on robot's left
  • Flip the offset value to change sides, NOT the trig signs.

Dynamic Camera Height (Obstacle Avoidance)

When tracking through cluttered environments, the chase camera will clip into tall geometry. Pre-compute obstacle bboxes, then raise the camera each frame as needed.

# Build obstacle lookup from USD geometry once at startup
obstacles = []
for prim in stage.Traverse():
    if prim.IsA(UsdGeom.Cube):
        # ... extract (xmin, xmax, ymin, ymax, height) ...
        obstacles.append((xmin, xmax, ymin, ymax, height))

def cam_max_height_at(cx, cy, margin=0.5):
    """Highest obstacle near (cx, cy). Camera must clear this."""
    return max((h for xmn, xmx, ymn, ymx, h in obstacles
                if xmn-margin <= cx <= xmx+margin and ymn-margin <= cy <= ymx+margin),
               default=0.0)

# Per-frame:
target_h = max(base_height, cam_max_height_at(cam_x, cam_y) + 1.0)
smooth_h = smooth_h * 0.95 + target_h * 0.05   # smooth transitions

Robot XformOp Discipline

URDF-imported robots (Spot, Carter, etc.) already have authored translate + orient + scale xformOps on the root prim.

  • Use xf.ClearXformOpOrder(); xf.MakeMatrixXform() on the root prim only for initial placement.
  • Never add ops to child body/link prims — physics drives those.

Video Assembly

ffmpeg -y -framerate 30 -i frames/frame_%05d.png \
  -c:v libx264 -pix_fmt yuv420p -crf 18 output.mp4

Frame numbering must be sequential (frame_0000.png, frame_0001.png, …) — ffmpeg skips gaps.

Session Management

For batch/iterative rendering, keep the Kit app running and switch stages in-place rather than restarting:

  • Cold start = 5-7 min wasted
  • Persistent session = 10-15s per render
  • Use stage_utils.open_stage(path) (isaacsim.core.experimental.utils.stage) to switch scenes
  • Only restart Kit if it crashes or hits OOM

Implementation is up to you (REPL, command file, IPC, etc.) — the principle is "don't pay the cold-start cost more than once."

Checklist Before Delivering Renders

  1. RT2 enabled (/rtx/rendermode = RayTracedLighting)
  2. ACES tone mapping enabled (/rtx/post/tonemap/op = 4)
  3. filmIso calibrated for scene type (200 general / 400 aerial-heavy / 600 deep-aisle)
  4. Explicit DomeLight + DistantLight (or scene-specific multi-layer setup)
  5. Settle frames sufficient (200 standard / 500 deep-aisle)
  6. Frame validation passed (rgb.mean() in 30-200 range, file size > 200KB)
  7. Frame sequence is gapless for ffmpeg

Integration Points

  • RECEIVES from: urdf-mjcf-to-usd-conversion, usd-articulation, mobility-gen, isaac-sim-robot-navigation — populated stages to render
  • PRODUCES for: data-collection-sim — validated frame sequences for SDG
  • PRODUCES for: isaac-sim-validator — outputs for final QA gate

Signals

GitHub stars
4k
Forks
539
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
isaac-sim-rendering
Source
github.com/isaac-sim/isaacsim