快速开始 Lambda Sim
本文从生成第一张图像开始,介绍 Lambda Sim 的场景导入、运行时对象、USD 动画回放和图像导出,再按需体验机器人动力学示例。
1. 本教程的目标与范围
| 章节 | 完成后得到什么 | 是否必需 |
|---|---|---|
| 开始前的准备 | 可导入的依赖、明确的运行设备和完整资产 | 是 |
| 生成第一张图像 | 一张来自 USD 相机的 PNG | 是 |
| 理解 Python 工作流程 | 理解场景、模型、状态与渲染对象的关系 | 建议 |
| 动画帧与多相机图像 | Franka 动画帧、视频及多相机扩展说明 | 按需 |
| 机器人动力学示例 | FR3 瓶子抓取放置与结果判定 | 按需 |
| 单求解器与耦合求解器 | 创建求解器、注册到场景并推进仿真 | 按需 |
| 结果验收与常见问题 | 判断输出是否完整、内容是否合理 | 是 |
本教程直接使用 Lambda Sim,不要求准备 RoboEvolve 训练容器、策略 checkpoint 或八卡集群。RoboEvolve 是上层任务编排和数据采集平台之一;策略训练、任务评测及成功率统计不属于本页内容。
2. 开始前的准备
开始前,请确认 Lambda Sim 的运行环境(依赖、USD 资产、渲染器、Python 与 GPU)已准备就绪。
3. 快速体验:生成第一张图像
在已 安装 Lambda Sim 的环境中,进入 LambdaSim 仓库根目录,通过 src/lambdasim/lambdasim_main.py 执行渲染。以下命令使用 Linux / Bash:
python src/lambdasim/lambdasim_main.py \
tests/assets/shaderball.usda \
--output-dir outputs/quickstart/static \
--camera-indices 0 \
--render-outputs color \
--width 640 \
--height 480 \
--spp 32 \
--device musa:0
| 参数 | 含义 |
|---|---|
tests/assets/shaderball.usda | 输入 USD 场景,作为位置参数传入,不使用--usd-path |
--output-dir | 图像保存目录,不存在时会创建 |
--camera-indices 0 | 使用导入后索引为0 的传感器相机 |
--render-outputs color | 只生成彩色图像,不生成深度或分割结果 |
--width、--height | 输出图像宽度和高度 |
--spp | 每像素采样数;先用32 验证流程,再按画质需要提高 |
--device musa:0 | 请求使用 MUSA 设备0 |
本例显式选择一个相机和 color 输出,且不传 --sequence,因此生成一张静态彩色图像。输出按相机索引和通道分目录保存,默认文件名为输入 USD 的文件名主体加 .png:
outputs/quickstart/static/
`-- camera_000/
`-- color/
`-- shaderball.png
成功后会输出 Rendered outputs (color) to: .../outputs/quickstart/static。打开 outputs/quickstart/static/camera_000/color/shaderball.png,确认分辨率为 640×480,材质球与地面正常显示。
可以通过 --output-name 修改静态图像的文件名,但文件仍保存在相机和通道子目录中。若省略 --camera-indices,会渲染所有传感器相机;若省略 --render-outputs,会生成 color、depth、segmentation 三类输出,其中深度与分割还会保存 .npy 原始数据,PNG 仅用于预览。
命令会导入 USD 场景中的相机和灯光,并使用相机索引 0 渲染。首次运行可能涉及原生组件初始化或内核编译,不应把首次耗时当作稳定渲染性能。
4. 理解最小 Python 工作流程
4.1 导入场景并创建运行时对象
导入 USD 前,应根据用途选择接口:
| 接口 | 适用场景 | 关键区别 |
|---|---|---|
Scene.add_usd() | 场景展示、图像渲染和已有 USD 动画回放 | 通用导入入口,默认namespace="auto"、rigid_read_only=True。自动导入可区分普通几何与具有时变几何的网格,但不等于自动装配可控制的机器人或配置动力学求解器。 |
Scene.add_rigid_usd() | 用于需要mujoco物理求解的刚体和关节机器人 | 使用刚体装配路径,读取刚体、碰撞形状、关节和驱动等信息。通过prim_path 选择导入根节点,并可设置 floating、collapse_fixed_joints、enable_self_collisions 等装配参数。 |
Scene.add_solid_usd() | 布料、软体等可变形体的几何与约束导入,用于Alphacore Vera Solver | 使用粒子和网格数据路径,支持分别提供geometry_usd 和 constraint_usd,并通过 geometry_prim_path、constraint_prim_path 选择几何与约束节点。这里的 solid 指固体力学。 |
使用 add_solid_usd() 时,默认 constraint_mode="usd",必须提供 constraint_usd;若明确只导入几何、不加载约束,可使用 constraint_mode="none"。当前 constraint_mode="generated" 尚未实现,不能依赖它自动生成约束。
这三个接口负责导入和组织数据,调用后都不会自动开始物理仿真。需要物理运动时,还要在 finalize() 后注册与场景数据匹配的求解器,再调用 scene.step(dt);已有 USD 动画的回放则使用 scene.update_by_frame()。第 6 节的机器人示例采用 add_rigid_usd()。
本节使用材质球场景验证导入和渲染,因此选择 add_usd()。
在仓库根目录对应的 Python 环境中执行以下完整示例:
from pathlib import Path
from lambdasim.scene.scene import Scene
usd_path = Path("tests/assets/shaderball.usda")
scene = Scene()
try:
scene.add_usd(str(usd_path), prim_path="/", namespace="auto")
scene.finalize(device="musa:0", env_num=1)
model = scene.get_model()
simdata = scene.get_simdata()
render_scene = scene.get_render_scene()
print(type(model).__name__)
print(type(simdata).__name__)
print(type(render_scene).__name__)
print("cameras:", len(render_scene.cameras_sensor))
print("lights:", len(render_scene.lights))
finally:
scene.close()
前三项预期分别为 Model、SimData、RenderScene,该入门场景应有一个传感器相机和一个灯光。
| 对象或调用 | 作用 |
|---|---|
Scene() | 创建场景管理对象 |
add_usd() | 导入 USD;namespace="auto" 根据场景内容组织导入 |
finalize() | 为指定设备和环境数量创建运行时对象 |
Model | 保存场景结构及模型参数 |
SimData | 保存运行时状态与控制数据 |
RenderScene | 保存渲染使用的几何、材质、相机与灯光 |
close() | 结束使用后释放求解器和运行时资源 |
finalize() 不会自动创建动力学求解器,也不会自动推进物体运动。本例仅验证场景导入和运行时对象创建,不生成图像。
4.2 手动创建渲染器并渲染
下面给出从场景初始化到图像保存的完整示例,按照当前 lambdasim_main.py 的调用方式,手动创建渲染器、设置输出通道、选择相机并渲染当前场景。
本例使用支持 RenderOutput、set_camera_index() 和 enable_taa 的新版 path-tracer,依赖版本应与当前 LambdaSim 匹配:
from pathlib import Path
import imageio.v3 as iio
from path_tracer.config import EnvironmentLightMode, RenderConfig, RenderOutput
from path_tracer.lambda_renderer import LambdaRenderer
from lambdasim.scene.scene import Scene
usd_path = Path("tests/assets/shaderball.usda")
output_path = Path("outputs/quickstart/python_static/shaderball.png")
output_path.parent.mkdir(parents=True, exist_ok=True)
scene = Scene()
try:
scene.add_usd(str(usd_path), prim_path="/", namespace="auto")
scene.finalize(device="musa:0", env_num=1)
render_scene = scene.get_render_scene()
renderer = LambdaRenderer(render_scene, 0, device_id=0)
config = RenderConfig()
config.enable_path_tracing = True
config.environment_light_mode = EnvironmentLightMode.DOME
config.width = 640
config.height = 480
config.spp = 32
config.output_mask = RenderOutput.COLOR
config.use_env_light = True # 开启环境光
# 关闭 ReLAX 降噪和 TAA,启用 OIDN 降噪
config.enable_denoising = False
config.enable_taa = False
config.enable_oidn_denoising = True
renderer.set_render_config(config)
camera_index = 0
light_indices = scene.get_light_list()
renderer.update_scene(render_scene, light_indices)
renderer.set_camera_index(camera_index)
image = renderer.render()["color"]
iio.imwrite(output_path, image)
print("Rendered image to:", output_path)
finally:
scene.close()
| 调用 | 作用 |
|---|---|
LambdaRenderer(render_scene, 0, device_id=0) | 为环境索引0 创建渲染器,使用 GPU 编号 0,与本例的 musa:0 对应 |
RenderConfig() | 配置路径追踪、输出通道、环境光、分辨率、采样数和降噪 |
renderer.set_render_config(config) | 将配置应用到渲染器 |
scene.get_light_list() | 获取场景中的灯光索引 |
renderer.update_scene(render_scene, light_indices) | 向渲染器更新当前场景和灯光,不选择相机 |
renderer.set_camera_index(camera_index) | 选择本次渲染使用的传感器相机 |
renderer.render() | 执行渲染,返回以输出通道名为键的字典 |
renderer.render()["color"] | 取得返回结果中的彩色图像数据 |
iio.imwrite(...) | 将图像保存为 PNG |
config.output_mask 决定启用哪些输出通道。本例只启用 RenderOutput.COLOR,因此取返回字典的 "color" 数据保存,不能将整个字典直接传给 iio.imwrite()。
降噪配置中,enable_denoising 对应 ReLAX,enable_taa 控制 TAA,enable_oidn_denoising 控制 OIDN。本例仅启用 OIDN;不再使用旧的 use_temp_denoiser 字段控制时序处理。使用 OIDN 时还需要渲染器构建包含对应支持。
预期生成 outputs/quickstart/python_static/shaderball.png。这里的文件路径由代码中的 output_path 显式指定,不会自动套用第 3 节命令行入口的相机和通道目录规则。打开图像,确认分辨率为 640×480,材质球与地面正常显示。
后续需要渲染物理推进后的状态时,可在 scene.step(dt) 同步渲染状态后,依次调用 renderer.update_scene()、renderer.set_camera_index() 和 renderer.render()["color"];第 6 节现有机器人示例展示了这种用法。
5. 生成动画帧与多相机图像(可选)
本节使用 scripts/example_franka_mujoco_musa.py 展示如何把机器人仿真过程渲染成动画帧。机器人运动由求解器推进产生,不是回放 USD 中预先写入的动画。
该脚本当前固定渲染相机索引 0,没有多相机命令行参数。下面先运行现有单视角示例,再说明扩展多相机时需要调整的位置。
5.1 运行 Franka 动画示例
开始前需要准备匹配的 Franka USD、外部资产及 MuJoCo Warp MUSA 环境,并确认资产引用完整。脚本默认引用的 tests/assets/local_test/robotwin_asset/franka-panda/franka-panda.usda 在本稿核对的本地目录中不存在,下面用参数显式指定资产位置。本节使用的 Franka Panda 与第 6 节的 FR3 资产不同,不要混用。
在 LambdaSim 仓库根目录执行,将资产路径替换为实际位置:
export FRANKA_USD="/path/to/franka-panda/franka-panda.usda"
export FRANKA_PRIM="/World/Embodies/franka_panda"
python scripts/example_franka_mujoco_musa.py \
--usd "$FRANKA_USD" \
--prim-path "$FRANKA_PRIM" \
--device musa:0 \
--env-num 1 \
--steps 23 \
--fps 24 \
--sim-substeps 10 \
--width 640 \
--height 480 \
--spp 32 \
--output-dir outputs/quickstart/franka_animation
脚本先渲染初始帧 frame_0000.png,再在每次外层推进后渲染一帧,因此 --steps 23 对应 24 张图像,编号为 0~23。
| 参数 | 对动画输出的影响 |
|---|---|
--steps 23 | 执行 23 次外层推进,加上初始状态共渲染 24 帧 |
--fps 24 | 视频以 24 fps 编码;未指定--dt 时,也参与计算仿真子步时长 |
--sim-substeps 10 | 每次外层推进包含 10 个物理子步 |
--width 640 --height 480 | 每帧图像的分辨率 |
--spp 32 | 每像素采样数 |
--output-dir | 中间帧和最终视频的输出目录 |
本例没有设置 --dt,物理子步时长为 1 / 24 / 10 秒。仿真总推进时长为 23/24 秒;包含初始帧的视频共 24 帧,播放时长约为 1 秒。
5.2 理解动画帧的生成过程
脚本中的主要 调用关系如下:
| 函数或操作 | 作用 |
|---|---|
build_scene(args) | 使用add_rigid_usd() 导入机器人,配置控制模式、地面和相机,并完成 finalize() |
run_simulation(scene, args) | 注册求解器,更新控制目标,推进物理状态并逐帧调用render_frame() |
scene.upload_control()、scene.step(sim_dt) | 在物理子步内上传控制并推进仿真,同步渲染状态 |
render_frame(scene, args, frame_name) | 配置灯光和渲染器,渲染相机索引0,保存一张 PNG |
compose_video(...) | 按文件名顺序读取 PNG,并按args.fps 编码视频 |
clear_frame_outputs(...) | 视频生成成功后删除中间 PNG |
直接运行第 5.1 节命令,成功后最终保留:
outputs/quickstart/franka_animation/
`-- franka_mujoco_musa.mp4
直接运行脚本时,中间 PNG 写在输出目录根部,不会自动生成 camera_000/ 子目录;视频生成后,脚本会删除该目录中的所有 frame_*.png。
编码会读取输出目录中已有的匹配帧,同名视频会被覆盖。应使用没有旧帧的独立目录,不要在其中存放需要保留的 PNG。
5.3 多相机扩展的位置
当前 build_scene() 显式添加一个传感器相机,render_frame() 更新场景后通过 renderer.set_camera_index(0) 固定选择相机索引 0,再取 renderer.render()["color"] 保存图像。
第 3 节的 lambdasim_main.py 支持 --camera-indices,但本节的 example_franka_mujoco_musa.py 没有该参数,不能直接给这个示例脚本添加 --camera-indices 0 1。
若要基于这个示例扩展多相机,需要:
- 在
build_scene()的finalize()之前添加其他传感器相机,并分别设置视角。 - 每个待输出的物理时刻先调用一次
renderer.update_scene(render_scene, light_indices),再遍历相机索引,逐一调用renderer.set_camera_index(camera_index)和renderer.render()["color"]。 - 将结果按
camera_000/frame_0000.png、camera_001/frame_0000.png等路径保存,并保留这些帧供后续拼接。