性能与采样频率
需要:uBotSensor 0.1.x(含依赖 uBotCore)
了解各 uBot 传感器每次采样的工作量和开销高的节点,以便选择合适的采样频率。
| 传感器 | 每次采样做什么 | 工作量随什么增长 |
|---|---|---|
| 位姿、里程计、IMU | 读一次组件变换,做一次差分;IMU 另有噪声、滤波和限幅 | 固定,只有少量向量运算 |
| 2D 激光雷达 | Horizontal Samples 次射线检测,默认 541 次 |
射线数,范围 2 到 4096 |
| 3D 激光雷达 | Number Of Channels × Horizontal Samples 次射线检测,默认 16 × 360 = 5760 次 |
射线数,上限由 Maximum Rays Per Scan 给出,默认 65536 |
| RGB 相机 | 把场景渲染一次到 render target | 分辨率(默认 640 × 480,每边 16 到 4096)和场景复杂度 |
| 深度相机 | 同 RGB 相机,渲染到深度格式的 render target | 同 RGB 相机 |
采样在组件的 tick 里触发。激光雷达的射线检测在游戏线程上一条接一条同步执行,耗时直接计入帧时间。相机每次采样会让引擎多渲染一次场景。
- 3D 激光雷达的射线数超过
Maximum Rays Per Scan时,这次扫描报错,不会截断。 Trace Complex默认关闭,射线只与简单碰撞求交。打开后每条射线更贵。Enable Smoke Interference默认打开:每次采样都要收集场景里的介质,接口路径和旧标签路径各遍历一遍世界里的所有 Actor(旧标签路径找带Smoke Volume Actor Tag的烟雾 Actor)。不用烟雾时把它关掉。Debug Visualization每次采样画调试线,2D 激光雷达约 32 条,3D 激光雷达最多 128 条。正式运行时关掉。- 每秒射线数是采样频率乘以每次的射线数:默认的 2D 激光雷达是 541 × 10 = 5410 条,3D 激光雷达是 5760 × 10 = 57600 条。
High Cost 节点
Section titled “High Cost 节点”名字里带 (High Cost) 的节点每次调用都返回整个数组的副本,数组越大越慢。只要几个值时,用按索引或按坐标读取的节点:
| 数据 | High Cost 节点 | 读单个值用 |
|---|---|---|
| 2D 激光雷达帧 | Copy All Ranges、Copy All Hit Flags |
Get Range by Index、Get Sample Count |
| 点云帧 | Copy All Points、Copy All Ring Indices、Copy All Time Offsets、Copy All Intensities |
Get Point by Index、Get Point Count |
| 图像帧(RGB) | Copy Pixel Bytes |
Get BGRAPixel by Coordinates |
| 图像帧(深度) | Copy Depth Values |
Get Depth Meters by Coordinates |
数组有多大:一帧 2D 扫描默认 541 个值;3D 激光雷达的点云不会超过 Maximum Rays Per Scan(默认 65536)个点;深度相机的 Generate Point Cloud 为每个有效像素生成一个点,640 × 480 最多 307200 个。一帧 640 × 480 的图像有 307200 个像素。
相机每次采样都会渲染一次场景,但不会把像素读回 CPU。图像留在 GPU 的 render target 里,帧对象 UBot Image Frame 只引用它。
需要像素时调用 Request Pixel Readback,它是同步的,开销高:
- 读回会等渲染完成,在游戏线程上阻塞。
- 只能读最新一次采集的帧。render target 已经被再次采集时,旧帧读回会返回错误。
- 帧上已有像素数据时,再次调用直接返回成功,不会重复读回。
- 读回的像素占 CPU 内存:BGRA8 和深度每个像素都是 4 字节,640 × 480 的一帧是 1228800 字节,1920 × 1080 的一帧是 8294400 字节。用完后调用
Clear CPUPixel Data释放。 - 烟雾叠加发生在读回时,介质存在时要对每个像素求交,不影响 render target 本身。
Update Depth Preview和Generate Point Cloud都要遍历整帧像素;前者在没有像素数据时会先读回。
所以只在真正需要像素的帧上读回,其余时候只用帧头和相机内参。
| 传感器 | 默认频率 | 要注意 |
|---|---|---|
| 位姿 | 30 Hz | 没有特别的限制 |
| 里程计 | 30 Hz | 不要低于 2 Hz,否则间隔超过 Maximum Valid Delta Seconds(0.5 s),每个样本都无效 |
| IMU | 100 Hz | 不要低于 2 Hz,原因同上;帧率低于 100 FPS 时,实际频率等于帧率 |
| 2D 激光雷达 | 10 Hz | 开销随射线数和频率成正比 |
| 3D 激光雷达 | 10 Hz | 开销随射线数和频率成正比 |
| RGB 相机 | 30 Hz | 每个采样时刻渲染一次场景 |
| 深度相机 | 30 Hz | 每个采样时刻渲染一次场景 |
Update Rate Hz的范围是 0.1 到 240 Hz,「Details」面板的滑块到 120 Hz。- 每个 world tick 最多采一个样本,所以采样频率不会超过帧率。多出来的采样时刻会被跳过,不会补采。
- 暂停时不自动采样。
- 默认值是各传感器的起点;先用默认值,再按需要的数据量调整。
怎么发现跟不上
Section titled “怎么发现跟不上”Get Dropped Sample Count 返回被跳过的采样次数。它不为 0,说明有的帧比采样间隔还长,传感器没能按设定的频率采样:
连线:
- Lidar 的 Lidar,连到 Get Dropped Sample Count 的 Target
- Get Dropped Sample Count 的 Return Value,连到 To String (Integer64) 的 In Int
- To String (Integer64) 的 Return Value,连到 Print String 的 In String
- Event Tick 的 执行,连到 Print String 的 执行
Event Tick
UE 引擎 · 中文界面:事件Tick
Event called every frame, if ticking is enabled
输出
- 执行
- Delta Seconds
Float
查看参考AActor.ReceiveTick
Lidar
组件或变量引用。在 Components 面板里把 Lidar 拖进事件图即可得到它。
输出
- Lidar
uBot 3D LiDAR Sensor对象引用
Get Dropped Sample Count
Target is UBot Sensor Component · UBotSensor
因为一帧跨过了多个采样时刻而跳过的采样次数。Start Sensor 时清零。
输入
- Target
UBot Sensor Component对象引用
输出
- Return Value
Integer64
查看参考UUBotSensorComponent.GetDroppedSampleCount
To String (Integer64)
UE 引擎 · 中文界面:转换为字符串(Integer64)
Converts an 64-bit integer value to a string
输入
- In Int
Integer64
输出
- Return Value
String
查看参考UKismetStringLibrary.Conv_Int64ToString
Print String
UE 引擎 · 中文界面:打印字符串
Prints a string to the log, and optionally, to the screen If Print To Log is true, it will be visible in the Output Log window. Otherwise it will be logged only as 'Verbose', so it generally won't show up.
输入
- 执行
- In String
String - Print to Screen
Boolean - Print to Log
Boolean - Text Color
Linear Color结构体 - Duration
Float - Key
Name
输出
- 执行
查看参考UKismetSystemLibrary.PrintString
- 计数在每次
Start Sensor时清零。 - 3D 激光雷达的帧里还有
Capture Duration Milliseconds,是这次扫描花的毫秒数。 - 相机协调器有自己的
Get Dropped Capture Count,见相机采集协调器。