# 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. 已建成并验证可用的功能(不是问题所在) 1. **线内通道插值**:读 `.ord` 真实横向偏移,规则化到 2.5cm 网格、相邻通道线性插值(不跨线)。 实测 Y 由 14 加密到 **56**。有单元测试。 2. **多体单遍合成**:20 条独立体(各自插值)作为一个 `vtkGPUVolumeRayCastMapper` 的多个端口注册进 `vtkMultiVolume`,**单遍 ray-cast 合成**(而非每条体一遍)。已验证。 3. **纹理单元上限自动退避**:单个 multi-volume 同时挂的体数受 GPU 每着色器纹理单元上限制约 (每体约吃 4 个单元 → 32 单元机上**一个包最多 7 体**,第 8 体报错并丢体)。已实现"渲一帧→报错则 每包减 1 重建重渲"的自动退避(强制 K=12 → 自动退避到 7,无丢体)。 4. **运行时换贴图边界**(确定性测试结论):给某端口**就地换贴图**——若**保持包围盒不变**(同范围、 只改 Y 密度)则 multi-volume 算得对;若**改包围盒**(任意子区域、origin/范围/spacing 变)则破坏 其缓存 `TexToBBox` → 体断开/消失。 5. 通道维 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. 关键问题——大多已被实测回答 1. ~~有没有不损质量的根本性提速法~~ **有,且是通用解:LOD(视野自适应多分辨率)**——让 GPU 单帧实际 ray-cast 的体素量**与数据总量解耦、只与"屏幕能看清的量"挂钩**(Task 12c 单体已验证 752/380fps)。 §7.5 passcost 证明它**对多线也成立**(独立 mapper 开销温和,20 条铺开 78fps)。 2. ~~"卡"的主因~~ **已确诊(§7.5):不是 N 遍固定开销(排除),是【当前根本没用 LOD、渲整卷大贴图】(嫌疑①)。** 本会话引入的 multi-volume 单遍为"单遍"关掉了 LOD → 大贴图 × 9 层重叠 → 1.7fps。 3. **9 层重叠的正确定位**(外部专家纠偏 + passcost 印证):它只是**这批数据(同路重扫 20 趟)的特例倍数**, **不是渲染本质问题**。本质是"单帧采样量 > GPU 吞吐",通用解是 LOD(扛任意大数据:无重叠但更大也能扛)。 9 层重叠在 LOD 之上降级为"一个被摊薄的常数因子"(passcost:20 层小体叠加仍 11fps)。 **不要让渲染架构围绕这个特例设计。** 4. 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 `:ESS 空块潜力(§6.1) - `gpr_poc view-all --overlapStat`:实测重叠层数(§7.1) - `gpr_poc view-all --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` --- ## 摘要(一页结论,供决策) 1. **现象**:20 条通道插值密体总览,~1.7fps、交互更卡。视觉正确,纯性能。 2. **确诊**(passcost 隔离测):**不是 N 遍固定开销**(20 独立 mapper 铺开 78fps,排除); 是**当前根本没用视野 LOD、在渲整卷大贴图**(× 9 层重叠)。本会话的 multi-volume 单遍为"单遍" 牺牲了 LOD,是直接原因。 3. **通用根本解 = LOD**(单帧渲染量与数据总量解耦),扛任意大数据;Task 12c 单体 752fps、passcost 多线 也成立。9 层重叠只是**本批数据的特例倍数**,是 LOD 之上一个可摊薄/可选合并的因子,**不是架构核心**。 4. **正解**:各线独立 mapper + 视野 LOD + 停手才重建(弃 multi-volume 单遍)。 5. **明确否定**:ESS/OSPRay/多后端(对本数据 ~2×、不解决重叠、解决的是 LOD 已解决的通用问题)。