基于 Rerun C++ SDK 构建三维模拟时钟:Boxes3D、Points3D 与 Arrows3D 时间轴动画实战
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
本指南以仓库中的 examples/cpp/clock 示例为核心,讲解如何使用 Rerun 的 C++ SDK(rerun_cpp)仅用Boxes3D、Points3D、Arrows3D三种 3D 原语,构建一个带时分秒指针、在sim_time时间轴上持续动画的模拟时钟。读完本文,你将掌握 Rerun C++ 示例的 CMake 构建流程、RecordingStream数据流的基本用法、时间轴(Timeline)驱动动画的机制,以及如何用最小代码实现"少量图元、大量事件"的高频可视化场景。
一、示例概览:一个由三种 3D 原语组成的时钟
examples/cpp/clock是一个极简但完整的 Rerun C++ 示例。它没有导入任何外部模型或纹理,而是纯程序化地生成一个表盘与三根指针:
- 表盘(frame):由
Boxes3D(半尺寸盒体)构成; - 指针端点(
_pt后缀实体):由Points3D构成; - 指针本身(
hand后缀实体):由Arrows3D(带起点、方向向量、半径的箭头)构成。
三根指针分别是秒针(seconds)、分针(minutes)和时针(hours),它们围绕world坐标系的原点旋转。整个动画在sim_time这一时间轴上推进,每帧同时写入 6 个实体(每个指针各 2 个:端点和箭头),共循环 10,000 步。
与 Python 版 examples/python/clock/clock.py(同样记录world/seconds_pt、world/seconds_hand等实体)和 Rust 版 examples/rust/clock/src/main.rs(cargo run -p clock -- --help可运行)相比,C++ 版在实现逻辑上完全一致,本文以 C++ 版源码 main.cpp 为准展开讲解。
二、构建与运行:三种方式任选
2.1 从仓库根目录构建(推荐)
原文档给出的构建方式要求先克隆仓库并具备 Rust 工具链(因为rerun_cpp需要编译其 Rust 核心),然后在仓库根目录执行:
cmake . cmake --build . --target example_clock ./examples/cpp/clock/example_clock- 第一条命令
cmake .会读取仓库根目录的CMakeLists.txt,其中通过add_subdirectory(clock)将 examples/cpp/clock/CMakeLists.txt 纳入构建;根级 examples/cpp/CMakeLists.txt 中还有add_dependencies(examples example_clock),即example_clock也是examples聚合目标的一部分。 - 第二条命令
cmake --build . --target example_clock只编译时钟这一个目标,避免全量编译整个仓库。 - 第三条命令直接运行生成的可执行文件。程序启动时会调用
rec.spawn()自动拉起一个 Rerun Viewer 窗口并建立 gRPC 连接,因此无需手动开启查看器。
2.2 作为独立 CMake 项目构建
如果脱离仓库单独使用该示例,CMakeLists.txt提供了备选路径:当未定义RERUN_REPOSITORY变量时,它会通过FetchContent从预编译的rerun_cpp_sdk.zip(URL 由RERUN_CPP_URL变量指定,默认指向最新 release 的rerun_cpp_sdk.zip)下载并链接rerun_sdk:
set(RERUN_CPP_URL "https://github.com/rerun-io/rerun/releases/latest/download/rerun_cpp_sdk.zip" CACHE STRING "URL to the rerun_cpp zip.") include(FetchContent) FetchContent_Declare(rerun_sdk URL ${RERUN_CPP_URL}) FetchContent_MakeAvailable(rerun_sdk)两种分支最终都通过target_link_libraries(example_clock PRIVATE rerun_sdk)链接 SDK。在仓库内构建时,rerun_strict_warning_settings(example_clock)还会为该目标启用严格的编译警告设置,保证示例代码质量符合仓库规范。
说明:
CMakeLists.txt中的RERUN_CPP_URL默认指向外部下载地址,文章不再展开外部链接;在仓库内构建时不会触发该分支。
三、核心实现拆解:从数据流到图元
完整源码见 examples/cpp/clock/main.cpp,逻辑分三段:常量与工具函数、静态场景初始化、时间轴循环写入。
3.1 常量定义:表盘与指针的几何参数
const float LENGTH_S = 20.0f; // 秒针长度 const float LENGTH_M = 10.0f; // 分针长度 const float LENGTH_H = 4.0f; // 时针长度 const float WIDTH_S = 0.25f; // 秒针半径(绘制时取一半) const float WIDTH_M = 0.4f; // 分针半径 const float WIDTH_H = 0.6f; // 时针半径- 秒针最长(20 个单位)且最细(宽度 0.25),分针次之,时针最短最粗,符合真实钟表的视觉比例;
- 表盘用秒针长度作为半尺寸,即表盘半径恰好等于秒针长度,指针旋转到任意角度都不会超出表盘边界;
- 循环总步数
num_steps = 10000,即生成 10,000 帧时间序列数据。
3.2log_hand:单根指针的写入逻辑
void log_hand( const rerun::RecordingStream& rec, const char* name, seconds step, float angle, float length, float width, uint8_t blue ) { const auto tip = rerun::Vec3D{length * sinf(angle * TAU), length * cosf(angle * TAU), 0.0f}; const auto c = static_cast<uint8_t>(angle * 255.0f); const auto color = rerun::Color{static_cast<uint8_t>(255 - c), c, blue, std::max<uint8_t>(128, blue)}; rec.set_time_duration("sim_time", step); rec.log( std::string("world/") + name + "_pt", rerun::Points3D(rerun::Position3D(tip)).with_colors(color) ); rec.log( std::string("world/") + name + "hand", rerun::Arrows3D::from_vectors(rerun::Vector3D(tip)) .with_origins({{0.0f, 0.0f, 0.0f}}) .with_colors(color) .with_radii({width * 0.5f}) ); }该函数体现了三个关键设计:
1. 角度 → 端点的三角函数映射:指针端点坐标由tip = (length·sin(angle·TAU), length·cos(angle·TAU), 0)计算。TAU(= 2π)被定义为常量,angle取值为 0~1 的归一化角度,乘上TAU后即得完整圆周的弧度。这样只需传入"指针在一圈中的比例位置",即可得到三维空间中的端点坐标。z分量恒为 0,说明整个时钟平面位于z=0平面内。
2. 角度驱动的渐变色:c = angle * 255,颜色为(255-c, c, blue, max(128, blue))。随着指针旋转,红绿通道沿色环平滑过渡(从红到绿再到红),形成彩虹渐变效果;blue参数用于区分三根指针的基色——秒针为 0(红绿渐变)、分针为 128、时针为 255,同时透明度始终不低于 128,保证在深色背景下清晰可见。这与 Python 版中color_s = (255 - scaled*255, scaled*255, 0, 128)的计算完全一致。
3. 实体路径命名与图元选择:每根指针在world下写入两个实体:
world/seconds_pt:Points3D,只含端点位置Position3D(tip),并附颜色;world/seconds_hand:Arrows3D,from_vectors从端点向量直接构造箭头,原点固定在(0,0,0),半径取width * 0.5(因为Arrows3D的半径是直径语义)。
值得注意的是,Rust 版 main.rs 中同样使用.with_radii([width * 0.5]),三个语言版本的参数语义保持一致。
3.3 时间轴:动画的核心机制
rec.set_time_duration("sim_time", step);set_time_duration为后续的所有log调用设置一个双时间点(timeline)——名为sim_time,类型为持续时间(Duration),数值为step秒。这一行必须出现在该时间步的log之前,即"先设时间,再写数据"。- 循环中每次迭代都会推进
sim_time,于是 Rerun Viewer 底部的时间轴会出现可拖动的进度条,用户可以像播放视频一样回放/快进整个时钟动画。 - 由于
step类型为std::chrono::seconds,时间单位是秒;Rust 版对应rec.set_duration_secs("sim_time", step as f64),Python 版对应rr.set_time("sim_time", duration=t_secs),三者语义等价。
3.4 静态场景:log_static与ViewCoordinates
在进入动画循环前,代码写入两个一次性静态实体:
rec.log_static("world", rerun::ViewCoordinates::RIGHT_HAND_Y_UP); rec.log_static("world/frame", rerun::Boxes3D::from_half_sizes({{LENGTH_S, LENGTH_S, 1.0f}}));ViewCoordinates::RIGHT_HAND_Y_UP声明world坐标系为右手系、Y 轴向上(即 Y 是世界"上"方向),这保证 3D 视角下时钟垂直于地面摆放,视觉观感正确;其他 C++ 示例如 examples/cpp/dna/main.cpp 也使用同类坐标声明。Boxes3D::from_half_sizes({{LENGTH_S, LENGTH_S, 1.0f}})以原点为中心、半尺寸为(20, 20, 1)生成一个很薄的方形表盘。log_static与log的区别在于:静态数据不属于任何时间点,在整个录制期间恒定存在,不会在时间轴上产生"变化",因此只需记录一次。Python 版对应rr.log(..., static=True),Rust 版对应rec.log_static(...),是各语言 SDK 的统一约定。
四、指针的运动学:取模运算驱动的三种转速
在main的循环中,三根指针以不同的模数计算归一化角度:
for (int step = 0; step < num_steps; step++) { log_hand(rec, "seconds", seconds(step), (step % 60) / 60.0f, LENGTH_S, WIDTH_S, 0); log_hand(rec, "minutes", seconds(step), (step % 3600) / 3600.0f, LENGTH_M, WIDTH_M, 128); log_hand(rec, "hours", seconds(step), (step % 43200) / 43200.0f, LENGTH_H, WIDTH_H, 255); }| 指针 | 模数(周期) | 归一化角度 | 说明 |
|---|---|---|---|
| 秒针 | 60 | (step % 60) / 60 | 每 60 秒转一圈,走一圈需 60 步 |
| 分针 | 3,600 | (step % 3600) / 3600 | 每 3,600 秒转一圈,转速为秒针的 1/60 |
| 时针 | 43,200 | (step % 43200) / 43200 | 每 43,200 秒(12 小时)转一圈,转速为分针的 1/12 |
三者的周期之比为 60 : 3600 : 43200 = 1 : 60 : 720,恰好对应真实时钟秒、分、时的传动比。angle始终落在[0, 1)区间内,保证指针每转完一圈就平滑复位,不会产生跳变。
从性能角度看,每步迭代只写入 6 个小图元(3 个Points3D+ 3 个Arrows3D),Rust 版源码注释中明确将此类场景定位为"many small events"(大量小事件的基准测试),这意味着该示例也适合用于测试 Rerun 在高频、小数据量日志场景下的吞吐表现。
五、运行结果与 Rerun Viewer 中的交互
运行./examples/cpp/clock/example_clock后:
rec.spawn().exit_on_failure()自动启动 Rerun Viewer 进程并通过 gRPC 接收数据;若启动失败程序直接退出(exit_on_failure),避免无头环境下挂死。- Viewer 中呈现一个位于
z=0平面、Y 轴向上的表盘,秒/分/时针由红绿渐变色箭头从原点指向各自端点,端点处有同色的小圆点。 - 底部时间轴显示
sim_time(单位为秒),可拖动进度条逐帧观察指针运动,也可使用播放按钮连续回放 10,000 帧动画。 - 由于所有实体均挂在
world/层级下,可在左侧实体树中单独隐藏/显示某根指针或表盘。
六、拓展:同一思路在 Python 与 Rust 中的实现
该示例在仓库中三语言齐备,可作为学习 Rerun 多语言 API 一致性的对照素材:
- Python:examples/python/clock/clock.py —— 通过
rr.script_setup(args, "rerun_example_clock")初始化,--steps命令行参数控制步数(默认 10,000),用rr.set_time("sim_time", duration=t_secs)设置时间点; - Rust:examples/rust/clock/src/main.rs —— 通过
cargo run -p clock -- --help运行,支持--steps参数(默认 10000),用rec.set_duration_secs("sim_time", step as f64)设置时间点。
三份实现共享同一套实体路径(world/frame、world/seconds_pt等)、同一组几何常量与取模周期,只是 API 形态不同——这为"同一数据集在三种语言间互换"提供了直观样例。若读者想进一步了解 Rerun 的时间轴与数据模型机制,可参考仓库根目录的 ARCHITECTURE.md 与 docs/content/concepts 目录下的概念文档。
七、小结
examples/cpp/clock用约 60 行代码完整演示了 Rerun C++ SDK 的核心用法:RecordingStream创建与spawn拉起 Viewer、log_static写入静态场景、set_time_duration驱动时间轴、Points3D/Arrows3D/Boxes3D三种图元组合、以及基于归一化角度与取模运算的指针运动学。它既是新手入门 Rerun 的"Hello World",也是评估 SDK 高频小事件写入性能的轻量基准,值得在动手编写自己的可视化应用前先行跑通。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考