16 KiB
GPR 多通道三维体渲染性能问题 — 分析文档(供外部专家评审)
自包含技术文档。读者无需了解本代码库内部,只需具备 GPU 体绘制 / VTK 基础。 目的:把"探地雷达(GPR)多通道阵列数据渲成可交互三维体"遇到的性能问题、已试方案、实测数据、 待定关键点完整呈现,供外部专家判断方向。
1. 背景与系统
- 桌面端 C++ 应用(Qt6 + VTK 9.6.2),渲染探地雷达(GPR)采集的地下三维数据,要求可交互(旋转/缩放)。
- 渲染用 VTK 的
vtkGPUVolumeRayCastMapper(OpenGL GPU 光线投射体绘制)。 - 当前测试机 GPU:32 个着色器纹理单元(典型独显/中端)。
2. 数据特征(关键,决定一切)
多通道阵列 GPR,一次采集一条"测线(line)":
- 道(trace):一个位置一根天线的垂直回波,深度方向 821 采样。
- 通道(channel):阵列横向并排的多对天线,本数据 14 通道;相邻通道横向间距 ≈ 10.5cm(来自
.ord文件真实偏移 -0.686…+0.686m,跨度 1.37m)。 - 沿测线道间距 ≈ 4.9cm(比横向通道间距细 ~2 倍)。
- 一条测线:沿路 ~45305 道,覆盖 ~2.2km(一条南北向道路)。
- 共 20 条测线 = 同一条路来回扫 20 趟(车载,每趟阵列覆盖约 1.4m 宽,多趟铺横向)。
单条线 = 一个三维体:X=沿测线(~45305)、Y=通道(14)、Z=深度(821)。 关键业务约束(来自现场专家):
- 通道太稀(10.5cm)→ 需线内通道间插值加密(相邻真实天线之间插,物理成立);
- 绝不做"测线之间"的插值(车与车之间是真实物理空隙,插出来"信号全是假的",工程上不可接受);
- 多条测线"分开各自插值,渲染可以合到一起"。
GPR 数据的统计特征(实测,见 §6):~91% 体素近零(反射层之间是空的),但反射层横贯整个深度分布(不是集中一坨)。
3. 已建成并验证可用的功能(不是问题所在)
- 线内通道插值:读
.ord真实横向偏移,规则化到 2.5cm 网格、相邻通道线性插值(不跨线)。 实测 Y 由 14 加密到 56。有单元测试。 - 多体单遍合成:20 条独立体(各自插值)作为一个
vtkGPUVolumeRayCastMapper的多个端口注册进vtkMultiVolume,单遍 ray-cast 合成(而非每条体一遍)。已验证。 - 纹理单元上限自动退避:单个 multi-volume 同时挂的体数受 GPU 每着色器纹理单元上限制约 (每体约吃 4 个单元 → 32 单元机上一个包最多 7 体,第 8 体报错并丢体)。已实现"渲一帧→报错则 每包减 1 重建重渲"的自动退避(强制 K=12 → 自动退避到 7,无丢体)。
- 运行时换贴图边界(确定性测试结论):给某端口就地换贴图——若保持包围盒不变(同范围、
只改 Y 密度)则 multi-volume 算得对;若改包围盒(任意子区域、origin/范围/spacing 变)则破坏
其缓存
TexToBBox→ 体断开/消失。 - 通道维 LOD、统一传函、色标图例等。
4. 核心问题:性能
- 20 条密体(Y=56)总览,交互极卡:静止 ~1.7 fps,旋转/缩放掉到 < 1 fps(GPU 100%)。
- 渲染视觉正确(雷达剖面纹理清晰、横向连续、合成无误),纯属性能。
- 现有提速手段都是**"交互时降质"**方向(降屏幕分辨率、加粗采样步长)——损可见质量,治标。 用户明确要求:"有没有不损可见质量的根本性提速(业界最佳实践)?"
5. 已分析/已试的所有方案(含理由与状态)
| # | 方案 | 理由 | 状态 / 结论 |
|---|---|---|---|
| A | multi-volume 单遍合成 | 把"N 体=N 遍 ray-cast"降到分包遍数 | ✅ 已实现。但单遍内每步仍要在重叠体里逐个采样 |
| B | 纹理单元自动退避分包 | 绕开 GPU 纹理单元上限、不丢体 | ✅ 已实现(K=7/包,20 条=3 包)。代价:跨包重叠合成不正确(接缝) |
| C | 交互降屏幕采样(ImageSampleDistance) | VTK AutoAdjust 标准手段 | ✅ 已做。损质量;且 AutoAdjust 只降屏幕、不降沿光线步长 |
| D | 交互手动加粗沿光线步长(SampleDistance) | 通道插值后 Y 密→自动步长极细→巨卡;这才是大头 | ✅ 已实现(--sampleDist/--dragSampleMul)。损质量;且用户尚未实测(见 §7 待定) |
| E | 通道维 LOD(远疏近密换 Y 平面子集) | 保包围盒换贴图(#4 验证安全) | ✅ 已实现。但只减纹理内存、不减每步重叠体采样次数 → 对此瓶颈几乎无效 |
| F | 装箱单体(binning):各线先逐线插值,再把真实道摆进一个总览网格体(空隙透明、不跨线插值) | 一个体一遍、每步采 1 次 → ~20×;真实数据无假信号 | ⚠️ 技术可行、合规,但用户否决:装箱合并后总览里分不出各线,而用户要"一起渲染时仍能逐线区分/查看"→ 合并即失去意义 |
| G | 空体素跳过 ESS(换 OSPRay/ANARI 后端):跳过透明背景块 | 业界对稀疏体的头号提速、不损质量 | ❌ 实测对本数据收益有限(见 §6):保质量阈值下仅 ~2×,且ESS 跳的是空区、不解决"重叠"。VTK 库存 mapper 无自动 ESS;OSPRay 本环境未编、vcpkg 无包 |
| H | 减少同屏体数(只渲选中 ≤7 条) | 真实工作流本就是选几条,1 包 1 遍 | ✅ 免费、永远有效(使用方式,非技术) |
6. 关键实测数据
6.1 ESS(空体素跳过)潜力——零依赖实测,决定要不要上 OSPRay
对一条真实测线密体(5702×56×789),按块算 min/max,统计"整块落在近零透明带"的占比(=ESS 可跳块):
| 透明带半宽(相对半值域) | 8³ 块可跳占比 | 理论提速上限 1/(1−占比) | 说明 |
|---|---|---|---|
| 5% | 8% | 1.1× | 极保守 |
| 10%(保质量,不丢弱反射) | 52% | ~2.1× | — |
| 20% | 80% | ~4.9× | 开始把弱反射当背景丢 |
| 30%(激进) | 90% | ~10× | 明显损质量 |
- 体素层面 91% 近零,但块层面(ESS 实际粒度)保质量阈值下只能跳 ~52% → 理论 ~2×。
- 原因:反射层横贯整个深度分布,多数块里总混着信号、跳不掉。要 10× 须用激进阈值(损质量)。
- 更关键:ESS 跳"空区",不减少"重叠"——在有信号的块里仍要逐个采样所有重叠体。
6.2 其它实测
- multi-volume 纹理单元上限:本机 7 体/包(32 单元),第 8 体报 "Hardware does not support the number of textures"。
- 体维度示例:coarse 4 → ~11000×56×793/线;coarse 32 → ~1400×56×780/线。
- 全 20 条 dense(coarse 32):底图 level 0、Y=56、分 3 包,渲染正确;交互 ~1.7fps(未加 D 方案的旧构建)。
7. 关键点——已实测,结论修正(原假设两条都被推翻)
7.1 "重叠几层"——实测:平均 ~8.7 层、最大 15 层(不是 ~2–3,也不是 20)
纯几何测(各线世界 AABB 投到 X-Y 俯视 footprint、细网格统计每格覆盖层数,--overlapStat):
- footprint:横向 X≈37m、沿路 Y≈2.2km;
- 有体覆盖处平均重叠 8.74 层,最大 15 层;穿 12–14 个体的格子占 ~42%。
- 结论修正:原"~2–3 层"假设错(开发团队和外部专家都猜偏了);重叠是真实的大瓶颈。
- 且这 9 层是冗余:20 趟是同一条路反复扫,同一地下点被测了 ~9 次 → 这 9 个重叠体在该处都非空 (同一地下结构)→ ESS 在重叠区一个都跳不掉(再次印证 ESS 不解决重叠)。
7.2 "采样 vs 重叠谁是大头"——实测:采样瓶颈,fps 线性正比于步长
20 条密体、静止近景、离屏(步长越大越快越糙):
| sampleDist | fps | 相对 |
|---|---|---|
| 0.2(≈自动细) | 1.3 | 1× |
| 0.5 | 3.2 | 2.5× |
| 1.0 | 5.9 | 4.5× |
| 2.0 | 11.3 | 8.7× |
| 4.0 | 20.9 | 16× |
- 步长翻倍→fps 翻倍 → GPU 是采样瓶颈。总开销 ≈ 光线数 × (光线长/步长) × ~9 个重叠体。
- D 方案(手动步长)确实直接、强力提速;但保质量的步长(≈ Nyquist,0.5×体素)下仍只 ~2 fps ——因为 9× 冗余重叠把它乘了回去。要到交互级(10+fps) 得把步长粗到 ~2.0(欠采样、损 Z 薄层)。
7.3 合并诊断(两测合起来)
慢 = 采样密度 × ~9 倍冗余重叠,两者都真实。
- D 方案(粗化采样):提速强,但保质量步长下被 9× 重叠压回 ~2fps;要交互须损质量。
- 唯一"保质量又快"的,是去掉那 9× 冗余重叠(同路重扫的同一地下点):合并/装箱(取真实道、不跨线 插值)→ 一个体一遍 → ~9× 提速、且 Nyquist 步长下也能交互、零质量损失(冗余测量本就该合并降噪)。
- 但这与用户"保持 20 条可区分"直接冲突——而这 9 层在物理上是冗余测量(同一地下结构扫了 9 遍), 保持它们"可区分"的工程价值存疑。
7.4 CPU OSPRay vs GPU(仍未测)
ESS 对本数据 ~2× 且不解决 9× 重叠;OSPRay 主要 CPU、对手是 GPU 数千核。很可能换 OSPRay 比现状还慢, 且为 ~2× 重编整个 VTK 投入产出极差。不建议在去掉冗余重叠之前考虑。
7.5 "多线为何卡"的根因确诊(passcost,决定架构)——结论:不是固定开销,是没用 LOD
背景:最初 P11/P12 是"各线独立 mapper + 视野 LOD",实测仍 0.5fps。需确诊卡在三个嫌疑哪个: ①LOD 选区没削小 ②N 遍固定开销 ③重叠没摊掉。
passcost命令:N 个独立 GPU mapper 各渲一个 64³ 小体 (模拟 LOD 削过的小区),分"铺开/不重叠"与"叠在一起/重叠",测离屏稳态 fps vs N。
| N | 铺开(不重叠) fps | 叠加(重叠) fps |
|---|---|---|
| 1 | 177 | 204 |
| 5 | 162 | 43 |
| 10 | 144 | 22 |
| 20 | 78 | 11 |
判读(决定性):
- 嫌疑 ②(N 遍固定开销)排除:20 个独立 mapper 铺开仍 78fps(177→78,远非线性)。 → 各线独立 mapper 架构上完全可行,固定开销温和。"multi-volume 单遍 ⊥ 视野 LOD"这个不可兼得不致命—— 放弃单遍、回独立 mapper 并不慢。
- 嫌疑 ③(重叠)真实但小体下可控:叠加随 N ~1/N(每条光线乘 N),但 20 层 64³ 叠加仍 11fps(可用), 再叠屏幕降采样更快。
- 嫌疑 ①(选区没削小)= 真凶:passcost 小体 20 层叠加=11fps,而真实 view-all 只 1.7fps——差距全在 贴图大小:当前渲的是整卷底图(~11000×56×200 ≈ 上亿体素/条),根本没用视野 LOD 把它削成小区。 这是最好结局:可修,不动地基,只需真正用上 LOD。
对架构的直接含义:本会话引入的 multi-volume 单遍是错误取舍——为"单遍"关掉了 LOD、改固定整卷贴图, 导致大贴图 × 9 层重叠 = 1.7fps。而 passcost 证明独立 mapper 够快,根本不必为单遍牺牲 LOD。
8. 部署约束(硬件不确定,跨厂商)
客户机配置未知(可能无独显,或 N卡/A卡/Intel)。没有任何单一渲染器能在 N 卡和 A 卡上都做"GPU+ESS"—— GPU 体光追渲染器全厂商锁定(N 卡→NVIDIA VisRTX/OptiX/IndeX;Intel→OSPRay-GPU;A 卡→基本无成熟方案)。 跨厂商唯一通用的是 OSPRay-CPU(免显卡、任意 x86) 或 OpenGL(任意 GPU、但无 ESS=现状)。 若上多后端,需"OSPRay-CPU 保底 + 探测到 N卡/Intel独显时升对应 GPU 后端 + OpenGL 终极兜底"。
9. 关键问题——大多已被实测回答
有没有不损质量的根本性提速法有,且是通用解:LOD(视野自适应多分辨率)——让 GPU 单帧实际 ray-cast 的体素量与数据总量解耦、只与"屏幕能看清的量"挂钩(Task 12c 单体已验证 752/380fps)。 §7.5 passcost 证明它对多线也成立(独立 mapper 开销温和,20 条铺开 78fps)。"卡"的主因已确诊(§7.5):不是 N 遍固定开销(排除),是【当前根本没用 LOD、渲整卷大贴图】(嫌疑①)。 本会话引入的 multi-volume 单遍为"单遍"关掉了 LOD → 大贴图 × 9 层重叠 → 1.7fps。- 9 层重叠的正确定位(外部专家纠偏 + passcost 印证):它只是这批数据(同路重扫 20 趟)的特例倍数, 不是渲染本质问题。本质是"单帧采样量 > GPU 吞吐",通用解是 LOD(扛任意大数据:无重叠但更大也能扛)。 9 层重叠在 LOD 之上降级为"一个被摊薄的常数因子"(passcost:20 层小体叠加仍 11fps)。 不要让渲染架构围绕这个特例设计。
- ESS/OSPRay/多后端:继续埋掉——ESS 对本数据 ~2× 且不解决重叠、CPU 对手是 GPU,且它解决的是 LOD 已经解决的通用问题,投入产出差。
10. 最终结论(passcost 确诊后,架构清晰)
- 渲染架构 = LOD 中心(视野自适应、单帧量与总量解耦)。 这是扛"任意大数据"的通用根本解, Task 12c 单体已验证、§7.5 passcost 多线也成立。
- 本会话的 multi-volume 单遍是错误取舍:为"单遍合成"牺牲了 LOD、改固定整卷大贴图,正是当前 1.7fps 的 直接原因。passcost 证明独立 mapper 开销温和(20 条 78fps)→ 根本不必为单遍弃 LOD。
- 正解 = 各线独立 mapper + 视野 LOD(逐线用 Task 12c 引擎)+ 停手才重建(不每帧重建,避免 P11/P12 那种"20 条每帧重建上传"的 thrash——那才是 0.5fps 的另一半原因,与稳态 ray-cast 无关,已被 P13 思路解决)。 让每条线只渲视野内小区 → 即使 9 层叠加也可用。
- 9 层重叠 = LOD 之上的可选应用层优化(对同路重扫冗余可"合并/降噪",顺带省 9×),不进渲染地基。 用户要逐条区分就不合并(靠 LOD 摊薄),要纯总览就合并。
- 采样步长(D 方案)= LOD 框架内的质量旋钮,非独立根本解。
- ESS/OSPRay/多后端:不做(不解决 LOD 已解决的通用问题,对本数据收益差)。
→ 下一步(确诊已完成,可开工):把多线总览从"multi-volume 单遍固定整卷"改回"各线独立 mapper + 视野 LOD + 停手重建",让单帧渲染量随视野走、与 20 条总量解耦;实测多线总览是否达交互级。 这是顺着通用 LOD 框架、被 passcost 数据支撑的明确方向——不再围着 9 层重叠这个特例转。
附:相关已落地代码 / 诊断工具(如专家要复现)
- 通道插值:
src/io/gpr/GprGeometry.cpp::planChannelInterpolation+Gpr3dvVolumeBridge.cpp - 多体合成/退避/质量控制/通道 LOD:
tools/gpr_poc/main.cpp::cmdViewAll - 诊断命令(
tools/gpr_poc/main.cpp,可直接跑复现 §6/§7 的数):gpr_poc ess-stat <dir> <line>:ESS 空块潜力(§6.1)gpr_poc view-all <dir> <gps> --overlapStat:实测重叠层数(§7.1)gpr_poc view-all <dir> <gps> --sampleDist D:步长↔fps(§7.2)gpr_poc passcost --size 64 --overlap 0|1:N 遍开销 vs 重叠 隔离测(§7.5)
- 数据/插值口径 spec:
docs/superpowers/specs/2026-06-25-gpr-line-channel-interpolation-and-multivolume.md - 多后端 ESS 架构 spec(结论:不做,见本文 §10):
docs/superpowers/specs/2026-06-26-gpr-multibackend-ess-rendering.md
摘要(一页结论,供决策)
- 现象:20 条通道插值密体总览,~1.7fps、交互更卡。视觉正确,纯性能。
- 确诊(passcost 隔离测):不是 N 遍固定开销(20 独立 mapper 铺开 78fps,排除); 是当前根本没用视野 LOD、在渲整卷大贴图(× 9 层重叠)。本会话的 multi-volume 单遍为"单遍" 牺牲了 LOD,是直接原因。
- 通用根本解 = LOD(单帧渲染量与数据总量解耦),扛任意大数据;Task 12c 单体 752fps、passcost 多线 也成立。9 层重叠只是本批数据的特例倍数,是 LOD 之上一个可摊薄/可选合并的因子,不是架构核心。
- 正解:各线独立 mapper + 视野 LOD + 停手才重建(弃 multi-volume 单遍)。
- 明确否定:ESS/OSPRay/多后端(对本数据 ~2×、不解决重叠、解决的是 LOD 已解决的通用问题)。