WorldRepresentation / render_scripts /render_scene_overview.py
Nirav Madhani
Import World Representation Space
5af572e
Raw
History Blame Contribute Delete
7.18 kB
"""
Render Scene Overview: Multi-object tabletop
Rotating camera showing the full scene with 5 objects of different materials.
This is the "hero" shot for the playbook.
"""
import mujoco
import numpy as np
import imageio
WIDTH, HEIGHT = 1280, 720
FPS = 60
OUTPUT_DIR = "."
DURATION_SEC = 6.0 # Full rotation
XML = """
<mujoco model="tabletop_scene">
<option timestep="0.002" gravity="0 0 -9.81"/>
<visual>
<global offwidth="{w}" offheight="{h}"/>
<quality shadowsize="4096"/>
<headlight ambient="0.25 0.25 0.28" diffuse="0.5 0.5 0.55"/>
</visual>
<asset>
<texture name="grid" type="2d" builtin="checker" rgb1="0.32 0.32 0.35" rgb2="0.28 0.28 0.31"
width="256" height="256"/>
<material name="grid_mat" texture="grid" texrepeat="10 10" reflectance="0.15"/>
<texture name="wood" type="2d" builtin="flat" rgb1="0.52 0.38 0.24" width="1" height="1"/>
<material name="wood_mat" texture="wood" specular="0.15" shininess="0.3"/>
<!-- Object materials -->
<material name="mat_wood" rgba="0.65 0.45 0.25 1" specular="0.2" shininess="0.3"/>
<material name="mat_rubber" rgba="0.18 0.18 0.20 1" specular="0.05" shininess="0.1"/>
<material name="mat_metal" rgba="0.72 0.73 0.76 1" specular="0.8" shininess="0.95"/>
<material name="mat_plastic" rgba="0.2 0.55 0.85 1" specular="0.4" shininess="0.6"/>
<material name="mat_ceramic" rgba="0.92 0.90 0.85 1" specular="0.3" shininess="0.5"/>
</asset>
<worldbody>
<light pos="0.3 -0.5 1.8" dir="-0.15 0.3 -1" castshadow="true" diffuse="0.85 0.82 0.78"/>
<light pos="-0.5 0.3 1.2" dir="0.3 -0.15 -1" castshadow="true" diffuse="0.3 0.32 0.38"/>
<geom name="floor" type="plane" size="3 3 0.1" material="grid_mat"/>
<!-- Table -->
<body name="table" pos="0 0 0.4">
<geom type="box" size="0.45 0.3 0.025" material="wood_mat"/>
<geom type="cylinder" size="0.018 0.19" pos="-0.40 -0.25 -0.21" rgba="0.42 0.32 0.22 1"/>
<geom type="cylinder" size="0.018 0.19" pos=" 0.40 -0.25 -0.21" rgba="0.42 0.32 0.22 1"/>
<geom type="cylinder" size="0.018 0.19" pos="-0.40 0.25 -0.21" rgba="0.42 0.32 0.22 1"/>
<geom type="cylinder" size="0.018 0.19" pos=" 0.40 0.25 -0.21" rgba="0.42 0.32 0.22 1"/>
</body>
<!-- Wood block -->
<body name="wood_block" pos="-0.18 0.08 0.46">
<joint type="free"/>
<geom type="box" size="0.035 0.035 0.035" mass="0.6" material="mat_wood"
friction="0.4 0.005 0.0001"/>
</body>
<!-- Rubber ball -->
<body name="rubber_ball" pos="0.05 -0.1 0.455">
<joint type="free"/>
<geom type="sphere" size="0.03" mass="1.1" material="mat_rubber"
friction="0.8 0.005 0.0001"/>
</body>
<!-- Metal cylinder -->
<body name="metal_cylinder" pos="0.2 0.05 0.455">
<joint type="free"/>
<geom type="cylinder" size="0.02 0.03" mass="3.0" material="mat_metal"
friction="0.2 0.005 0.0001"/>
</body>
<!-- Plastic cup (hollow cylinder approximation) -->
<body name="plastic_cup" pos="-0.05 0.12 0.46">
<joint type="free"/>
<geom type="cylinder" size="0.025 0.035" mass="0.12" material="mat_plastic"
friction="0.35 0.005 0.0001"/>
</body>
<!-- Ceramic bowl -->
<body name="ceramic_bowl" pos="0.15 -0.08 0.45">
<joint type="free"/>
<geom type="cylinder" size="0.04 0.015" mass="0.3" material="mat_ceramic"
friction="0.25 0.005 0.0001"/>
</body>
</worldbody>
</mujoco>
""".format(w=WIDTH, h=HEIGHT)
def render_orbit(filename, radius=0.75, height=0.65, n_frames=None):
"""Render a smooth camera orbit around the tabletop."""
model = mujoco.MjModel.from_xml_string(XML)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH)
mujoco.mj_forward(model, data)
# Settle
for _ in range(500):
mujoco.mj_step(model, data)
if n_frames is None:
n_frames = int(DURATION_SEC * FPS)
frames = []
scene = mujoco.MjvScene(model, maxgeom=1000)
camera = mujoco.MjvCamera()
option = mujoco.MjvOption()
# Camera target: center of table
camera.lookat[:] = [0, 0, 0.45]
camera.distance = radius
camera.elevation = -25
for i in range(n_frames):
t = i / n_frames
angle = t * 360 # full rotation
camera.azimuth = angle + 135 # start from a nice angle
# Gentle vertical oscillation
camera.elevation = -25 + 5 * np.sin(t * 2 * np.pi)
mujoco.mjv_updateScene(model, data, option, None, camera,
mujoco.mjtCatBit.mjCAT_ALL, scene)
renderer.update_scene(data)
# Use the movable camera
renderer._scene.camera[0].pos[:] = [
radius * np.cos(np.radians(angle + 135)),
radius * np.sin(np.radians(angle + 135)),
height + 0.05 * np.sin(t * 2 * np.pi)
]
# Alternative: just render with default scene and move camera
# Simpler approach using lookat
cam = mujoco.MjvCamera()
cam.lookat[:] = [0, 0, 0.43]
cam.distance = radius
cam.azimuth = angle + 135
cam.elevation = -25 + 5 * np.sin(t * 2 * np.pi)
renderer.update_scene(data, camera=cam)
frame = renderer.render()
frames.append(frame.copy())
if i % FPS == 0:
print(f" Frame {i}/{n_frames} ({t*100:.0f}%)")
output_path = f"{OUTPUT_DIR}/{filename}"
writer = imageio.get_writer(output_path, fps=FPS, codec='libx264',
output_params=['-crf', '18', '-preset', 'medium'])
for frame in frames:
writer.append_data(frame)
writer.close()
print(f" Saved: {output_path} ({len(frames)} frames, {len(frames)/FPS:.1f}s)")
renderer.close()
def render_static_views(prefix="scene"):
"""Render a few static views as individual frames (PNG)."""
model = mujoco.MjModel.from_xml_string(XML)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH)
mujoco.mj_forward(model, data)
for _ in range(500):
mujoco.mj_step(model, data)
views = [
("front", 0.7, 180, -20),
("angle", 0.75, 135, -25),
("top", 0.9, 90, -80),
("close", 0.45, 160, -15),
]
for name, dist, azimuth, elev in views:
cam = mujoco.MjvCamera()
cam.lookat[:] = [0, 0, 0.43]
cam.distance = dist
cam.azimuth = azimuth
cam.elevation = elev
renderer.update_scene(data, camera=cam)
frame = renderer.render()
output_path = f"{OUTPUT_DIR}/{prefix}_{name}.png"
imageio.imwrite(output_path, frame)
print(f" Saved: {output_path}")
renderer.close()
if __name__ == "__main__":
print("=== Rendering Scene Overview ===\n")
print("--- Static views ---")
render_static_views()
print("\n--- Orbit video ---")
render_orbit("scene_overview.mp4")
print("\nDone!")