fix(vtk): 垂直夸张只放大深度Z(修路被压胖) + 底图按高清块cropping挖空消双渲发白 + 降高光

三处真视觉 bug 修复(tmp/geo 全路段体上复现):
- exagg 轴误用:view 的底图/高清两 actor 原 SetScale(1,exagg,exagg) 把横向路宽(Y)
  与深度(Z)一起放大,2237m 长路被压成胖块;改为 SetScale(1,1,exagg) 只放大深度,
  路恢复真实细长(长:宽≈86:1,4474:52 cells)。两 actor scale 保持一致。
- 两层重叠双渲:底图(整卷)与高清(局部)vtkVolume 空间重叠,重叠区双渲发白且随相机
  移动;给底图 mapper 开 Cropping,裁剪平面=高清单图模型盒(GetBounds,与底图同坐标系),
  CroppingRegionFlags=0x7ffffff&~VTK_CROP_SUBVOLUME(挖掉中心盒、渲盒外),高清换位时
  同步更新;高清未就绪/base 预览时关 cropping 全渲(永不空白)。
- 降高光:var4 光照 Specular 0.2→0.05,消除旋转时视角相关高光游走形成的移动白斑,
  保留 ambient/diffuse 立体感。
- 验收:--preview --shots 多旋转角离屏出图;398 测试全过。
This commit is contained in:
gaozheng 2026-06-24 14:04:00 +08:00
parent d028994324
commit 90abeaa9b6
1 changed files with 61 additions and 4 deletions

View File

@ -2895,12 +2895,13 @@ vtkSmartPointer<vtkVolumeProperty> 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),且高清单图自带绝对世界 originbuildLocalLevel0Image 沿 X 偏移),
// 与底图同坐标系 → 高清块的模型盒平面直接作底图 cropping 平面即对齐(两层 scale 一致)。
//
// CroppingRegionFlags6 个平面把空间分成 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<vtkVolume>::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),作为「确有渲出的体结构」的诚实判据。