🎨 渲染器#

启动与加载#

RenderApp 负责基本的场景渲染。一般的,在代码中创建一个 RenderApp 实例,通过 load_model 加载模型后调用 render.launch(model) 来为渲染器加载模型。

同步#

在 motrixsim 中,渲染器跑在一个独立的线程中,因此我们需要在 python 主线程中调用 render.sync(data) 来实现物理模拟数据与渲染器的双向同步更新。render.sync 方法实际上执行了如下的逻辑:

  • 向渲染器发送所有物理对象的位姿、用户绘制指令等数据。

  • 从渲染器获取窗口、UI 等输入事件以及相机渲染数据

其背后的线程模型如下:

thread model

  • 主线程,即 python 代码执行的线程,我们通常在若干次物理步后执行一次 render.sync(data) 来同步数据。sync 会做两件事:

    1. 从 Event 管道接受来自渲染线程的输入事件与相机数据

    2. 向 Frame 管道发送帧数据(如果 Frame 管道已满,则阻塞等待)

  • 渲染线程,负责窗口的创建与渲染循环的执行。其在每个循环中都会做两件事:

    1. 从 Frame 管道接受来自主线程的帧数据,并提交给 GPU 渲染(如果管道为空,则跳过)

    2. 处理窗口以及 UI 以及相机数据请求,并将其发送到 Event 管道

渲染配置#

当前,有配置项RenderSettings来进行全局渲染配置:

参数名

解释

默认值

simplify_render_mesh

是否自动进行网格的简化

False

enable_shadow

是否启用灯光阴影

True

enable_ssao

是否启用屏幕空间环境光遮蔽

True

enable_oit

是否启用顺序无关透明渲染

True

特别的,当且仅当全局配置开启,且场景描述文件(mjcf/msd)中也启用某些渲染项时,才会有最终的效果。

示例代码见 examples/viewer/render_settings.py

自定义 UI 组件#

目前支持按钮 add_button 与复选框 add_toggle 两种组件,通过设置回调函数的方式来响应用户的点击事件。

用户需要通过 render.opt.set_left_panel_vis(True) 来显示左侧面板,组件会按添加顺序显示在面板上。

force = 10

# button
def on_click():
    nonlocal force
    print("Button clicked!")
    model.get_actuator("actuator_slider").set_ctrl(data, force)
    force = -force

def on_toggle_changed(value: bool):
    print("toggle value:", value)

render.opt.set_left_panel_vis(True)
render.ui.add_button("Click Me", on_click)
render.ui.add_toggle("Some Toggle", False, on_toggle_changed)

完整代码见 examples/viewer/custom_ui.py

Gizmos 绘制#

Gizmos 是一种用于辅助调试的图形元素,渲染器提供了一个简单的 API 来绘制 gizmos。

方法名

释义

draw_sphere

绘制线框球形

draw_cuboid

绘制线框矩体

draw_cylinder

绘制线框圆柱体

draw_capsule

绘制线框胶囊体

draw_ray

绘制射线

draw_line

绘制线段

draw_arrow

绘制箭头

draw_rect

绘制 2D 矩形

draw_grid

绘制 3D 栅格

draw_axes

绘制坐标轴

set_draw_collider

开启/关闭碰撞体绘制

set_draw_joint

开启/关闭关节绘制

set_draw_site

开启/关闭参考点绘制

set_collider_color

设置碰撞体绘制颜色

set_joint_color

设置关节绘制颜色

set_line_width

设置绘制线段宽度

set_joint_size

设置关节绘制大小

Gizmos 采用即时模式,即使不需要更新,用户也需要在每次渲染同步时添加 gizmos。

nonlocal rot, dir, x
# gizmos is drawning in immediate mode. so you must call it every frame
gizmos = render.gizmos
gizmos.draw_sphere(0.1, np.array([x, 0, 1]), color=Color.rgb(1, 0, 0))

gizmos.draw_cuboid(
    size=np.array([0.2, 0.3, 0.4]), pos=np.array([1, 0, 1]), rot=rot.as_quat(), color=Color.rgb(0, 1, 0)
)

gizmos.draw_cuboid(
    size=np.array([0.1, 0.1, 1]), pos=np.array([3, 0, 1]), rot=rot.as_quat(), color=Color.rgb(0, 1, 0)
)

gizmos.draw_capsule(0.5, 0.5, pos=np.array([1, 1, 1]), rot=Rotation.identity().as_quat())
gizmos.draw_cylinder(0.5, 0.5, pos=np.array([2, 1, 1]), rot=Rotation.identity().as_quat())
gizmos.draw_arrow(start=np.array([3, 1, 1]), end=np.array([4, 2, 1]), color=Color.rgb(1, 1, 0))
gizmos.draw_line(start=np.array([3, 2, 1]), end=np.array([4, 3, 1]), color=Color.rgb(1, 1, 0))
gizmos.draw_ray(start=np.array([4, 1, 1]), vector=np.array([3, 0.2, 0]), color=Color.rgb(1, 1, 0.2))
gizmos.draw_grid(
    pos=np.array([0, -2, 1]),
    rot=rot.as_quat(),
    color=Color.rgb(1, 0.5, 0.2),
)
gizmos.draw_rect(
    pos=np.array([3, -2, 1]),
    rot=Rotation.identity().as_quat(),
    width=1.5,
    height=3.5,
    color=Color.rgb(0.5, 1, 0.2),
)

完整代码见 examples/viewer/gizmos.py

IO 输入事件#

通过 render.input 可以获取到Input对象,Input 对象提供了一系列方法,用于查询渲染器的鼠标、键盘以及 UI 事件。

您可以通过以下的例子来了解更详细的用法:

合法键盘输入列表#

以下是支持的键盘按键输入列表:

按键类型

示例按键

字母键

A, B, ..., Z

功能键

F1, F2, ..., F12

特殊键

Enter, Esc, Space

方向键

Up, Down, Left, Right

注意:以上均不区分大小写,表格内未提及的键位暂不支持。

系统相机控制#

渲染器提供了一个自由的系统相机控制系统,用户可以通过鼠标操作来控制系统相机的视角和焦点(始终位于屏幕中心)。

  • 鼠标左键按下并拖动:绕着焦点旋转系统相机

  • 鼠标右键按下并拖动:移动焦点(此时显示红圈为焦点)

  • 鼠标滚轮:缩放(到焦点位置不可再放大)

更多的相机控制方式请参考 相机

单模型多实例渲染#

render.launch(model) 还有 batch:int 与 render_offset:List[:3] 两个可选参数,在需要单个模型多实例渲染时,分别用于设置实例数与偏移位置。

# Create the render instance of the model
# Try to create 3 model data
batch = 3
render_offset_1 = [0, 0, 0]
render_offset_2 = [0, 1, 0]
render_offset_3 = [0, -1, 0]
render.launch(
    model,
    batch,
    [
        render_offset_1,
        render_offset_2,
        render_offset_3,
    ],
)

完整代码见 examples/physics/model.py

实例可见性#

当我们进行单模型多实例渲染时,可以按需配置每一个实例的渲染可见性。

...
# 创建多个实例
render_offsets = []
batch = 10
for i in range(batch):
    render_offsets.append([i * 2.0, 0, 0])
render.launch(model, batch, render_offsets)
data = SceneData(model, batch=(batch,))

target_scene_indices = [1, 3, 5, 7, 9] # 指定实例索引
render.set_scene_vis(target_scene_indices, False) # 隐藏目标实例
render.set_scene_vis(target_scene_indices, True) # 显示目标实例

也可以全局开启/关闭:

render.set_all_scene_vis(False) # 隐藏所有实例
render.set_all_scene_vis(True) # 显示所有实例

上述操作仅影响渲染可见性,不影响对象的物理仿真。

完整代码见 examples/viewer/partial_rendering.py