Rerun 2D 图层叠放控制指南:DrawOrder 组件的语义、编码与渲染原理
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
在 Rerun 的二维视图中(如Spatial2DView),多个实体常常会互相遮挡:标注框要盖在图像上、点云要浮在底图上、分割结果要与原图叠加。DrawOrder组件就是 Rerun 为这类 2D 场景提供的"层叠顺序"控制机制——值越大,绘制得越靠上。本文以 Rerun 官方类型文档 draw_order.md 为主体,结合仓库中的类型定义、渲染上下文系统与测试用例,系统讲解该组件的语义约定、数据编码、三语言 SDK 用法以及从DrawOrder到最终深度偏移(DepthOffset)的底层实现原理,帮助你精确编排 2D 可视化中的图层关系。
核心语义:数值越大越靠上层
DrawOrder的官方定义非常明确:
2D 元素的绘制顺序。值越高的元素绘制在值越低的元素之上(Draw order of 2D elements. Higher values are drawn on top of lower values.)
在 crates/build/re_type_definitions/rerun/components/draw_order.def.rs 的类型定义文件中,这一段语义被原样保留,并额外标注了三条重要的使用约束:
- 一个实体只能有一个 draw order 组件(An entity can have only a single draw order component);
- 实体内部(即同一实体内多个可视化层之间)的绘制顺序由组件顺序决定(Within an entity draw order is governed by the order of the components);
- 拥有相同 draw order 值的实体之间,先后顺序一般是未定义的(Draw order for entities with the same draw order is generally undefined)。
这三点意味着:DrawOrder适合用来做实体之间(不同 Entity Path 之间)的明确分层;如果希望精确控制同层实体的遮挡关系,需要为它们分配不同的数值,而不能依赖相等值时的内部顺序。
类型定义与数据编码
Rerun 编码:Float32
DrawOrder在 Rerun 侧的数据编码为单精度 32 位 IEEE 754 浮点数(Float32),即一个普通的f32标量。在 Arrow 序列化层面,其 datatype 同样直接映射为:
Float32三语言绑定:同一份定义,三种生成产物
该组件不是手写代码,而是由re_types_builder从draw_order.def.rs这一个"类型定义源"自动生成 Rust、Python 与 C++ 三套绑定(def 文件头部注释明确说明:"It is parsed byre_types_builderto generate the Rust, Python and C++ bindings")。
- Rust:在 crates/store/re_sdk_types/src/components/draw_order.rs 中,
DrawOrder被实现为一个#[repr(transparent)]的元组结构体pub struct DrawOrder(pub crate::encodings::Float32),组件类型名为"rerun.components.DrawOrder"。它实现了Deref/DerefMut(可直接当作Float32使用)以及From<T: Into<Float32>>,因此可以用任意可转换为Float32的值直接构造。 - Python:在 rerun_py/rerun_sdk/rerun/components/draw_order.py 中,
DrawOrder直接继承自encodings.Float32,并组合ComponentMixin;类型定义中的#[python(aliases = "float")]与#[python(array_aliases = "float | npt.NDArray[np.float32]")]注解(见 draw_order.def.rs)意味着 Python 侧既可以传普通float,也可以传numpy的float32数组。 - C++:同样由该 def 文件生成
rerun::components::DrawOrder,用法与 Rust 侧一致,通过构造器传入一个浮点值。
另外,def 文件中标注了#[rerun(state = "stable")],说明DrawOrder属于稳定 API,可以放心在长期项目中依赖。
支持 DrawOrder 的 2D 原型(Archetype)全览
根据原文档的 "Used by" 列表,共有 13 个 archetype 支持DrawOrder(在各自文档中均作为可选字段draw_order出现,例如 Image、Points2D、Boxes2D):
| Archetype | 说明 | 典型叠加场景 |
|---|---|---|
Image | 单色/彩色图像 | 作为底图,值最小 |
EncodedImage | 压缩图像(JPEG/PNG) | 作为底图或覆盖层 |
DepthImage/EncodedDepthImage | 深度图像 | 与其他 2D 标注叠加 |
SegmentationImage | 分割图像 | 半透明叠加在底图上 |
GridMap | 栅格地图数据 | 地图与路径/点位叠加 |
Points2D | 2D 点云 | 标注点盖在图像之上 |
Boxes2D | 2D 矩形框 | 检测框盖在图像/点上 |
LineStrips2D | 2D 折线 | 轨迹线覆盖底图 |
Ellipses2D | 2D 椭圆 | 高亮/区域标注 |
Arrows2D | 2D 箭头 | 方向标注(如光流) |
VideoFrameReference/VideoStream | 视频帧引用与视频流 | 视频画面上叠加标注 |
需要注意的是,DrawOrder只对2D 元素生效(文档措辞为 "Draw order of 2D elements")。虽然在Spatial3DView中这些 2D archetype 若挂载在投影(projection)下也可以显示(见各 archetype 文档的 "Can be shown in"),但层叠优先级主要由DepthOffset机制换算处理,详见下文。
实战:在 SDK 中设置 draw_order
Python 示例
在 Python SDK 中,draw_order是各 archetype 构造器的关键字参数。以 Points2DExt 为例,其参数文档明确写着:"An optional floating point value that specifies the 2D drawing order. Objects with higher values are drawn on top of those with lower values."(可选的浮点值,用于指定 2D 绘制顺序;值越高的对象绘制在值越低的对象之上。)
import rerun as rr rr.init("draw_order_demo", spawn=True) # 1) 底层:一张底图,draw_order = 0.0 rr.log("scene/background", rr.Image(..., draw_order=0.0)) # 2) 中层:检测框,draw_order = 10.0,盖在底图之上 rr.log( "scene/detections", rr.Boxes2D( centers=[(320.0, 240.0), (100.0, 100.0)], half_sizes=[(120.0, 60.0), (40.0, 40.0)], draw_order=10.0, # 比底图大即可 ), ) # 3) 顶层:关键点,draw_order = 20.0,盖在框之上 rr.log( "scene/keypoints", rr.Points2D( positions=[(320.0, 240.0)], colors=[(255, 0, 0)], draw_order=20.0, ), )由于DrawOrder是Float32浮点标量,取值可以任意(正负、小数均可),只要相互之间有大小区分即可;为便于维护,建议按 10 的倍数预留档位,方便日后插入新图层。
Rust 示例
Rust 侧使用.with_draw_order(...)构造器方法,这与仓库中的官方测试写法完全一致(见下文测试章节)。例如:
use re_sdk_types::archetypes::Image; let image = Image::from_color_model_and_tensor( re_sdk_types::encodings::ColorModel::RGB, tensor_data, )?; // 指定该实体绘制在 draw_order 更大的实体之下 let image = image.with_draw_order(0.0);渲染原理:从 DrawOrder 到 DepthOffset
DrawOrder并不会直接作为渲染参数,而是由空间视图(re_view_spatial)中的视图上下文系统EntityDepthOffsets换算为渲染器可用的深度偏移(re_renderer::DepthOffset)。其核心实现在 crates/views/re_view_spatial/src/contexts/depth_offsets.rs:
- 收集:
collect_draw_order_per_visualizer遍历所有"处理 draw order 的可视化器"(visualizers_processing_draw_order()),对每个实体的可视化指令执行latest_at查询,读取DrawOrder组件;若实体未显式设置,则通过typed_fallback_for惰性计算一个默认值(determine_default_draworder,位于同文件)。 - 排序:所有
(可视化器, 实体路径哈希)对按DrawOrder值放入BTreeMap(天然按值升序),每个值对应一个BTreeSet的实体集合。 - 换算:为保证深度偏移尽量紧贴 0,起始偏移被设为
-(实体总数 / 2),然后为每个实体分配一个连续递增的DepthOffset。
源码中有两处值得注意的实现细节(可直接从 depth_offsets.rs 确认):
- 相同 DrawOrder 也会被拆开:注释明确写道 "We give objects with the same
DrawOrderstill a different depth offset in order to avoid z-fighting artifacts when rendering in 3D. (for pure 2D this isn't necessary)"——即为了避免 3D 渲染下的 z-fighting(深度冲突)伪影,相同DrawOrder的实体仍会被分配不同但相邻的深度偏移。这与文档中"相同 draw order 的实体顺序一般未定义"的语义并不冲突:偏移不同是为了消除伪影,而非承诺绘制先后。 - 默认值兜底:未设置
draw_order的实体也会走同一套机制获得默认层级,因此新旧数据混用时不会出现层级断裂。
官方测试用例:一份可复现的"分层示范"
仓库在 crates/views/re_view_spatial/tests/draw_order.rs 中提供了test_draw_order集成测试,用 8 个实体直观演示了完整的层叠效果(每个实体都在2d_layering前缀下):
| 实体路径 | Archetype | draw_order | 视觉效果 |
|---|---|---|---|
2d_layering/background | Image(512×256 灰色) | 0.0 | 最底层背景 |
2d_layering/middle_gradient | Image(256×256 渐变) | 1.0 | 中间层 |
2d_layering/middle_blue | Image(192×192 蓝色) | 1.1 | 略高于渐变层 |
2d_layering/arrow2d_between | Arrows2D | 1.12 | 夹在中间层之间 |
2d_layering/lines_behind_rect | LineStrips2D | 1.25 | 在矩形之下 |
2d_layering/rect_between_top_and_middle | Boxes2D | 1.5 | 中上层 |
2d_layering/points_between_top_and_middle | Points2D | 1.51 | 紧贴矩形之上 |
2d_layering/top | Image(128×128 白色) | 2.0 | 最顶层 |
该测试通过TestContext将上述实体依次写入SpatialView2D,并生成名为draw_order的渲染快照。它同时印证了两点:一是with_draw_order是 Rust API 的标准链式方法(各 archetype 均可用);二是小数粒度完全受支持(如1.1、1.12、1.51),可以在相邻图层之间精确插入新层,这正是Float32编码带来的灵活性。
注意事项与最佳实践
综合原文档语义与源码实现,在实际使用DrawOrder时建议遵循以下规则:
- 跨实体分层靠 DrawOrder,实体内分层靠组件顺序:一个实体只能携带一个
DrawOrder值;若需要在一个实体路径下表达多层,应通过组件排列顺序控制,或拆分为多个实体。 - 避免依赖相等值:相同
DrawOrder的实体之间先后顺序"一般未定义";虽然在 3D 投影场景下渲染器会为它们分配不同的深度偏移以避免 z-fighting,但这不能作为可靠的分层依据。需要明确遮挡关系时,请使用不同的值。 - 数值只论大小、不论绝对值:任意浮点值均可(正、负、小数),系统内部会自动将收集到的值映射为紧贴 0 的连续深度偏移,因此无需刻意从 0 开始计数。
- 未设置时自动兜底:未显式指定
draw_order的实体会获得默认层级(由typed_fallback_for提供),与显式设置的值混用是安全的。 - 仅作用于 2D 元素:
DrawOrder的语义限定在 2D 元素(图像、点、框、线、箭头等)的叠放;三维物体的遮挡由常规深度测试决定,不受该组件控制。
延伸阅读
- 组件类型定义:draw_order.def.rs
- Rust 生成绑定:crates/store/re_sdk_types/src/components/draw_order.rs
- Python 生成绑定:rerun_py/rerun_sdk/rerun/components/draw_order.py
- 渲染换算实现:depth_offsets.rs
- 层叠效果集成测试:tests/draw_order.rs
- 编码类型说明:Float32
- 相关 archetype:以 Points2D、Boxes2D、Image 为代表,其余见上文表格。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考