diff --git a/tools/gpr_poc/main.cpp b/tools/gpr_poc/main.cpp index cf46e2d..5bdc757 100644 --- a/tools/gpr_poc/main.cpp +++ b/tools/gpr_poc/main.cpp @@ -2895,12 +2895,13 @@ vtkSmartPointer makeVariantProperty( prop->SetGradientOpacity(grad); } - // 光照:ShadeOn + Ambient/Diffuse/Specular,让层界面带立体明暗(区别于纯平涂)。 + // 光照:ShadeOn + Ambient/Diffuse,保留立体明暗;Specular 压到 0.05(近乎关)避免 + // 旋转时视角相关的高光在体表游走形成「移动白斑」。保留 ambient/diffuse 立体感。 if (v.useShade) { prop->ShadeOn(); prop->SetAmbient(0.3); prop->SetDiffuse(0.7); - prop->SetSpecular(0.2); + prop->SetSpecular(0.05); prop->SetSpecularPower(10.0); } return prop; @@ -2942,6 +2943,7 @@ constexpr int kViewMax3DTex = 16384; struct ViewState { geopro::render::ViewAdaptiveVolumeSource* source = nullptr; vtkSmartVolumeMapper* mapper = nullptr; // 高清层:单 SmartVolumeMapper(叠在底图上) + vtkSmartVolumeMapper* baseMapper = nullptr; // 常驻底图层 mapper(用于按高清块挖空 cropping) vtkRenderer* ren = nullptr; vtkCamera* cam = nullptr; vtkTextActor* fpsText = nullptr; @@ -2954,6 +2956,28 @@ struct ViewState { bool inCb = false; }; +// C3-8 底图按高清块挖空:用 vtkVolumeMapper 的 Cropping 在【底图 mapper】上裁掉高清块 +// 覆盖的那一块,使任意时刻 = 底图(高清区外) + 高清(高清区内),无空间重叠 → 不再双渲发白。 +// +// 裁剪平面用高清单图的【模型坐标包围盒】(GetBounds)。底图与高清两 actor 用同一份 +// SetScale(1,1,exagg),且高清单图自带绝对世界 origin(buildLocalLevel0Image 沿 X 偏移), +// 与底图同坐标系 → 高清块的模型盒平面直接作底图 cropping 平面即对齐(两层 scale 一致)。 +// +// CroppingRegionFlags:6 个平面把空间分成 3×3×3=27 区,中心区(盒内)= bit 0x0002000 +// (VTK_CROP_SUBVOLUME)。要「渲盒外、挖掉盒内」→ 全 27 区减中心区 = 0x7ffffff & ~0x0002000。 +void viewSyncBaseCropping(ViewState* st) { + if (st->baseMapper == nullptr) return; + if (st->currentImg == nullptr) { // 高清未就绪:底图不裁剪,全渲(绝不空白) + st->baseMapper->SetCropping(0); + return; + } + double b[6]; + st->currentImg->GetBounds(b); // 模型坐标盒(含绝对 X origin),与底图同系 + st->baseMapper->SetCroppingRegionPlanes(b[0], b[1], b[2], b[3], b[4], b[5]); + st->baseMapper->SetCroppingRegionFlags(0x7ffffff & ~VTK_CROP_SUBVOLUME); + st->baseMapper->SetCropping(1); +} + // C3-2 非阻塞拉取:把最新已就绪单图喂 mapper(若有新结果)。不阻塞主线程—— // 后台 builder 没新结果就沿用上一帧(拖动跟手的关键)。返回 1=喂了新图,0=无变化。 std::size_t viewPickLatest(ViewState* st) { @@ -2964,6 +2988,7 @@ std::size_t viewPickLatest(ViewState* st) { st->lastLevel = st->source->lastLevel(); st->mapper->SetInputData(st->currentImg); st->mapper->Update(); + viewSyncBaseCropping(st); // 高清块换位 → 同步更新底图挖空盒,保持无缝无重叠 return 1; } @@ -3270,6 +3295,10 @@ int cmdView(int argc, char** argv) { }; const bool smoke = hasFlag("smoke"); const bool preview = hasFlag("preview"); + // C3-8 验收用:--preview --shots 额外从【真 view 场景(base+hires+cropping)】多旋转角 + // 离屏出图,用于人工核对「路细长不胖 / 拼接无缝无白 / 旋转无移动白斑」。只加图,不改 + // 默认行为(无 --shots 时与原 preview 完全一致)。 + const bool shots = hasFlag("shots"); // C3-6 底图预览:--preview --base(或 --base)模拟「交互态:只渲常驻粗底图」—— // 隐去高清叠加层、只渲整卷最粗 ≤16384 层单纹理 → 整卷概览、盖全、绝不空白。 const bool basePreview = hasFlag("base"); @@ -3360,7 +3389,7 @@ int cmdView(int argc, char** argv) { baseMapper->Update(); baseVolume->SetMapper(baseMapper); baseVolume->SetProperty(prop); // 与高清层共用传函(同配色/不透明度) - baseVolume->SetScale(1.0, exagg, exagg); // 同垂向夸张 → 与高清层空间对齐 + baseVolume->SetScale(1.0, 1.0, exagg); // 垂向夸张只放大深度(Z);横向路宽(Y)不动 → 与高清层空间对齐 ren->AddVolume(baseVolume); // 先加底图 → 底层常渲 } @@ -3380,7 +3409,7 @@ int cmdView(int argc, char** argv) { auto volume = vtkSmartPointer::New(); volume->SetMapper(mapper); volume->SetProperty(prop); - volume->SetScale(1.0, exagg, exagg); // 垂向夸张(默认 var4 exagg) + volume->SetScale(1.0, 1.0, exagg); // 垂向夸张只放大深度(Z);横向路宽(Y)保持真实比例(修 GPR 路被压胖) ren->AddVolume(volume); // 后加高清 → 叠在底图上 // 屏幕左上角实时 fps 文本。 @@ -3398,6 +3427,7 @@ int cmdView(int argc, char** argv) { ViewState st; st.source = &source; st.mapper = mapper.Get(); + st.baseMapper = baseMapper.Get(); // 供 viewSyncBaseCropping 按高清块挖空底图 st.ren = ren.Get(); st.fpsText = fpsText.Get(); st.rw = rw.Get(); @@ -3414,6 +3444,7 @@ int cmdView(int argc, char** argv) { // 拖动/缩放时即使高清全部缺位,底图也独立盖住整个体(永不空白的命脉)。 if (preview && basePreview) { volume->SetVisibility(0); // 隐去高清层 → 只剩常驻底图 + baseMapper->SetCropping(0); // 高清隐了 → 底图取消挖空,整卷全渲(证明永不空白) // 框整卷(无参 ResetCamera 按场景中可见 actor 包围盒;高清隐了 → 框底图全卷)。 ren->ResetCamera(); st.cam = ren->GetActiveCamera(); @@ -3495,6 +3526,32 @@ int cmdView(int argc, char** argv) { (shotDir / (nearPreview ? "view-near.png" : "view-default.png")) .string(); savePng(rw.Get(), pngPath); + // C3-8:多旋转角出图(同一真 view 场景:base+hires 共用 SetScale(1,1,exagg) + 底图 + // 按高清块 cropping 挖空)。从默认相机起取若干 (azimuth,elevation) 离屏存图,供人工 + // 核对路细长比例 / 拼接无缝 / 旋转无移动白斑。无 --shots 不执行。 + if (shots) { + const struct { + const char* name; + double az; + double el; + } kAngles[] = { + {"view-shot-az0", 0.0, 0.0}, {"view-shot-az30", 30.0, 0.0}, + {"view-shot-az60", 60.0, 0.0}, {"view-shot-az-30", -30.0, 0.0}, + {"view-shot-el20", 0.0, 20.0}, {"view-shot-az45el15", 45.0, 15.0}, + }; + for (const auto& s : kAngles) { + st.cam->Azimuth(s.az); + st.cam->Elevation(s.el); + ren->ResetCameraClippingRange(); + rw->Render(); + const std::string sp = (shotDir / (std::string(s.name) + ".png")).string(); + savePng(rw.Get(), sp); + std::cout << "[view] 旋转角出图: " << sp << " (az=" << s.az + << " el=" << s.el << ")\n"; + st.cam->Elevation(-s.el); // 复位到默认朝向,下一角从默认起算 + st.cam->Azimuth(-s.az); + } + } // 结构像素计数:背景为深蓝灰(R/G≈10,B≈20),countNonBlackPixels(>10) 会把整屏 // 背景都算「非空」,对验证「画面有结构」无意义。改为只数明显亮于背景的像素 // (任一通道 >50),作为「确有渲出的体结构」的诚实判据。