feat(vtk): C4 体绘制梯度不透明度+光照,内部分层界面透出

view --gallery 改为 4 组 C4 对照(纯标量/+梯度不透明度/+光照/全开):
- GalleryVariant 增 useGradientOpacity/useShade 字段(默认关,不改既有行为)
- makeVariantProperty 叠加 SetGradientOpacity(按实测梯度分位 median/p90/p99
  标定:均匀层透明、界面/异常显形)+ ShadeOn(Ambient/Diffuse/Specular 立体明暗)
- 纯标量组 max0.45;开梯度门的组 max0.6(净不透明度=标量×梯度,层面才浮实)
- 交互 view 默认变体(var4 全开)从常驻底图实测梯度分布标定阈值

tmp/geo 真实全路段体(4474x52x82)实测:纯标量糊成半透明实心块仅端面可读;
开梯度不透明度后均匀块透出内部水平分层界面,光照令界面带立体感。
fps:纯标量155→梯度门48(画廊),交互默认98静态/267拖动态,均交互级。
398 测试全过。
This commit is contained in:
gaozheng 2026-06-24 13:45:17 +08:00
parent 8edc1f30ac
commit d028994324
1 changed files with 102 additions and 37 deletions

View File

@ -1164,6 +1164,13 @@ geopro::core::ColorScale makeStructuralColorScale(double vmin, double vmax) {
// 地震高对比色阶seismic 红-白-蓝):两端饱和亮色(强正=亮红、强负=亮蓝), // 地震高对比色阶seismic 红-白-蓝):两端饱和亮色(强正=亮红、强负=亮蓝),
// 零附近白。比 structural 更亮、对比更狠,正负反射一眼分开。 // 零附近白。比 structural 更亮、对比更狠,正负反射一眼分开。
// 前置声明(实现在 polish 段):梯度幅值分位统计,供 C4 画廊的梯度不透明度标定。
struct GradStats {
double median = 0, p75 = 0, p90 = 0, p99 = 0, mx = 0;
std::size_t samples = 0;
};
GradStats sampleGradientMagnitude(vtkImageData* img);
geopro::core::ColorScale makeSeismicColorScale(double vmin, double vmax) { geopro::core::ColorScale makeSeismicColorScale(double vmin, double vmax) {
geopro::core::ColorScale cs; geopro::core::ColorScale cs;
const double span = (vmax > vmin) ? (vmax - vmin) : 1.0; const double span = (vmax > vmin) ? (vmax - vmin) : 1.0;
@ -2803,32 +2810,45 @@ struct GalleryVariant {
double zoom; // 再 Zoom 填满画面 double zoom; // 再 Zoom 填满画面
double bg[3]; // 背景 RGB double bg[3]; // 背景 RGB
const char* desc; // 报告用中文说明 const char* desc; // 报告用中文说明
// ---- C4 视觉调优(梯度不透明度 + 光照),默认关 → 不改既有变体行为 ----
bool useGradientOpacity = false; // SetGradientOpacity均匀层透明、界面/异常显形
bool useShade = false; // SetShade层界面带立体明暗
}; };
// 4 组视觉参数。值经离屏实跑挑出(详见报告)。 // C4 视觉调优4 组对照(纯标量 / +梯度不透明度 / +光照 / 全开)。
// 同一局部段、同一 seismic 配色、同一斜穿俯视取景El45/Az30视线穿进体内部而非
// 只看端面)——唯一变量是「梯度不透明度 / 光照」,供肉眼直读"内部结构是否透出"。
// maxOpacity 字段:纯标量组用低峰值(0.45,避免均匀积分糊成实心);开了梯度门的组用
// 高峰值(0.6)——均匀区被梯度门压透明后,层界面的净不透明度(标量×梯度)才够高、浮成实面。
// 末项 var4(全开) = kViewDefaultVariant → 交互窗口默认走全开。
const GalleryVariant kGalleryVariants[] = { const GalleryVariant kGalleryVariants[] = {
// var1高不透明度实体感——seismic 亮配色 + V 形包络(中段 0.40/两端 0.85) // var1纯标量不透明度基线——无梯度门、无光照。GPR 均匀层段沿射线积分糊成
// floor 压到 0.04:近零层间隙近透明,亮层面浮出 → 内部层状结构可读。 // 半透明实心块,只外表面/端面可读,内部水平分层难分辨(对照基准)
{"var1", OpacityProfile::kSolid, ColorChoice::kSeismic, {"var1", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.04, 0.40, 0.85, 8.0, 22.0, 28.0, 1.9, {0.05, 0.05, 0.09}, 0.04, 0.30, 0.45, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
"高不透明度实体感V形包络(floor0.04/mid0.40/max0.85)+seismic 亮配色," "纯标量不透明度(基线)V形包络(floor0.04/mid0.30/max0.45)+seismic"
"半透明实心、内部层次可见"}, "无梯度门/无光照,均匀层积分糊成半透明块、仅端面可读",
// var2高对比配色——jet 全程高饱和 + 中等不透明度 V 形包络。 /*useGradientOpacity=*/false, /*useShade=*/false},
{"var2", OpacityProfile::kSolid, ColorChoice::kJet, // var2+梯度不透明度——低梯度(均匀层)透明、高梯度(层界面/异常)显形,射线穿透
0.04, 0.32, 0.70, 8.0, 22.0, 28.0, 1.9, {0.06, 0.06, 0.10}, // 均匀雾、内部界面浮出。标量峰值提到 0.6(梯度门压透明后层面才够实)。
"高对比配色jet 蓝青绿黄红全程高饱和 + 中等 V 形包络(mid0.32/max0.70)"}, {"var2", OpacityProfile::kSolid, ColorChoice::kSeismic,
// var3居中正对纵截面——低 Elevation/Azimuth 摆平、正对 X-Z 长侧面(层状反射沿 0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
// X 延展最清晰)、Zoom2.0 填满 ~70%floor 压更低让层间隙透明、层面立体。 "+梯度不透明度:低梯度(均匀层)透明、高梯度(界面/异常)显形标量峰值0.6"
"射线穿透均匀雾、内部界面浮出",
/*useGradientOpacity=*/true, /*useShade=*/false},
// var3+光照——在梯度门基础上 ShadeOn层界面带立体明暗(非纯平涂)。
{"var3", OpacityProfile::kSolid, ColorChoice::kSeismic, {"var3", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.03, 0.38, 0.82, 9.0, 10.0, 12.0, 2.0, {0.05, 0.05, 0.09}, 0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
"居中正对纵截面:低 El10/Az12 摆平正对 X-Z 长侧面、Zoom2.0 填满视野," "+光照(梯度门+Shade):层界面带立体明暗(Ambient0.3/Diffuse0.7/Specular0.2)"
"floor0.03 凸显层面exagg9 放大薄轴"}, "界面起伏更有体积感",
// var4最像真实 GPR 三维图——seismic + 略提背景亮 + 微立体角 + 实体包络。 /*useGradientOpacity=*/true, /*useShade=*/true},
// var4全开——梯度不透明度 + 光照 + seismic 双端斜坡配色 + 略亮冷灰背景。
// 综合最佳选作交互窗口默认kViewDefaultVariant // 综合最佳选作交互窗口默认kViewDefaultVariant
{"var4", OpacityProfile::kSolid, ColorChoice::kSeismic, {"var4", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.035, 0.38, 0.84, 8.0, 18.0, 22.0, 2.0, {0.07, 0.08, 0.11}, 0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.07, 0.08, 0.11},
"综合最佳seismic + 实体包络(floor0.035/mid0.38/max0.84) + 微立体取景" "全开(综合最佳):梯度不透明度+光照+seismic+略亮冷灰背景,内部分层界面/异常"
"(El18/Az22/Zoom2.0) + 略亮冷灰背景"}, "从均匀块透出 → 交互窗口默认",
/*useGradientOpacity=*/true, /*useShade=*/true},
}; };
// 交互窗口(无 flag 的 view)的默认视觉变体 = var4kGalleryVariants 末项)。 // 交互窗口(无 flag 的 view)的默认视觉变体 = var4kGalleryVariants 末项)。
@ -2847,15 +2867,43 @@ geopro::core::ColorScale pickColor(ColorChoice c, double vmin, double vmax) {
// 按变体的不透明度包络建体属性gallery / 交互默认共用DRY。kSolid 走 V 形实体 // 按变体的不透明度包络建体属性gallery / 交互默认共用DRY。kSolid 走 V 形实体
// 包络(floor/mid/max)kStructural 走双端斜坡(仅 maxOpacity)。 // 包络(floor/mid/max)kStructural 走双端斜坡(仅 maxOpacity)。
//
// C4 视觉调优:若变体开了 useGradientOpacity 且传入了实测梯度统计 gs再叠加
// 1) 梯度不透明度(SetGradientOpacity):低梯度(均匀层)→透明、高梯度(界面/异常)→显形,
// 让射线穿透均匀雾、内部界面浮出(阈值按 gs 的 median/p90/p99 标定,不靠猜);
// 2) 光照(useShade → SetShade + Ambient/Diffuse/Specular):层界面带立体明暗。
vtkSmartPointer<vtkVolumeProperty> makeVariantProperty( vtkSmartPointer<vtkVolumeProperty> makeVariantProperty(
const GalleryVariant& v, const geopro::core::Quant& q, const GalleryVariant& v, const geopro::core::Quant& q,
const geopro::core::ColorScale& cs, double vmin, double vmax, const geopro::core::ColorScale& cs, double vmin, double vmax,
double maxOpacity) { double maxOpacity, const GradStats* gs = nullptr) {
vtkSmartPointer<vtkVolumeProperty> prop;
if (v.profile == OpacityProfile::kSolid) { if (v.profile == OpacityProfile::kSolid) {
return makeSolidVolumeProperty(q, cs, vmin, vmax, v.floorOpacity, prop = makeSolidVolumeProperty(q, cs, vmin, vmax, v.floorOpacity,
v.midOpacity, maxOpacity); v.midOpacity, maxOpacity);
} else {
prop = makeTunedVolumeProperty(q, cs, vmin, vmax, maxOpacity);
} }
return makeTunedVolumeProperty(q, cs, vmin, vmax, maxOpacity);
// 梯度不透明度:均匀层(低梯度)透明,层界面/异常边缘(高梯度)显形。阈值按实测分布:
// median 处仍 0压住均匀积分雾p90 升到 0.5p99 到 0.9。无 gs未测则跳过。
if (v.useGradientOpacity && gs != nullptr && gs->samples > 0) {
vtkNew<vtkPiecewiseFunction> grad;
grad->AddPoint(0.0, 0.0);
grad->AddPoint(std::max(1.0, gs->median), 0.0);
grad->AddPoint(std::max(2.0, gs->p90), 0.5);
grad->AddPoint(std::max(3.0, gs->p99), 0.9);
prop->SetGradientOpacity(grad);
}
// 光照ShadeOn + Ambient/Diffuse/Specular让层界面带立体明暗区别于纯平涂
if (v.useShade) {
prop->ShadeOn();
prop->SetAmbient(0.3);
prop->SetDiffuse(0.7);
prop->SetSpecular(0.2);
prop->SetSpecularPower(10.0);
}
return prop;
} }
// ============================================================================ // ============================================================================
@ -3066,14 +3114,31 @@ int runGalleryVariant(const std::string& dir, const GalleryVariant& v,
const double vmin = m.vminPhys, vmax = m.vmaxPhys; const double vmin = m.vminPhys, vmax = m.vmaxPhys;
const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax); const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax);
vtkSmartPointer<vtkVolumeProperty> prop =
makeVariantProperty(v, m.quant, cs, vmin, vmax, v.maxOpacity);
auto rw = makeOffscreenWindow(winW, winH); auto rw = makeOffscreenWindow(winW, winH);
vtkNew<vtkRenderer> ren; vtkNew<vtkRenderer> ren;
ren->SetBackground(v.bg[0], v.bg[1], v.bg[2]); ren->SetBackground(v.bg[0], v.bg[1], v.bg[2]);
rw->AddRenderer(ren); rw->AddRenderer(ren);
// 局部段(沿线中段,同 viewSetupDefaultFrame 的取段法)。先建体 → 实测梯度分布
// 用于 C4 梯度不透明度标定(仅开了 useGradientOpacity 的变体需要)。
const int totBx = store.bricksX(0);
const int localBx = std::min(kViewDefaultLocalBricks, totBx);
const int bx0 = std::max(0, totBx / 2 - localBx / 2);
vtkSmartPointer<vtkImageData> locImg =
buildLocalLevel0Image(store, m, bx0, localBx);
GradStats gs;
if (v.useGradientOpacity) {
gs = sampleGradientMagnitude(locImg.Get());
std::cout << "[gallery " << v.name << "] 梯度幅值分布(量化域,样本 " << gs.samples
<< "): median=" << gs.median << " p90=" << gs.p90
<< " p99=" << gs.p99 << " max=" << gs.mx << "\n";
}
vtkSmartPointer<vtkVolumeProperty> prop = makeVariantProperty(
v, m.quant, cs, vmin, vmax, v.maxOpacity,
v.useGradientOpacity ? &gs : nullptr);
vtkNew<vtkMultiBlockVolumeMapper> mapper; vtkNew<vtkMultiBlockVolumeMapper> mapper;
mapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode); mapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
auto volume = vtkSmartPointer<vtkVolume>::New(); auto volume = vtkSmartPointer<vtkVolume>::New();
@ -3082,12 +3147,6 @@ int runGalleryVariant(const std::string& dir, const GalleryVariant& v,
volume->SetScale(1.0, v.exagg, v.exagg); volume->SetScale(1.0, v.exagg, v.exagg);
ren->AddVolume(volume); ren->AddVolume(volume);
// 局部段(沿线中段,同 viewSetupDefaultFrame 的取段法)。
const int totBx = store.bricksX(0);
const int localBx = std::min(kViewDefaultLocalBricks, totBx);
const int bx0 = std::max(0, totBx / 2 - localBx / 2);
vtkSmartPointer<vtkImageData> locImg =
buildLocalLevel0Image(store, m, bx0, localBx);
std::vector<vtkSmartPointer<vtkImageData>> one{locImg}; std::vector<vtkSmartPointer<vtkImageData>> one{locImg};
auto mb = makeMultiBlock(one); auto mb = makeMultiBlock(one);
mapper->SetInputDataObject(mb); mapper->SetInputDataObject(mb);
@ -3261,8 +3320,17 @@ int cmdView(int argc, char** argv) {
const double vmin = m.vminPhys, vmax = m.vmaxPhys; const double vmin = m.vminPhys, vmax = m.vmaxPhys;
// 配色/不透明度包络取自 var4seismic + V 形实体包络(floor/mid + opacity 作峰值)。 // 配色/不透明度包络取自 var4seismic + V 形实体包络(floor/mid + opacity 作峰值)。
const geopro::core::ColorScale cs = pickColor(dv.color, vmin, vmax); const geopro::core::ColorScale cs = pickColor(dv.color, vmin, vmax);
vtkSmartPointer<vtkVolumeProperty> prop = // C4默认变体(var4)开了梯度不透明度 → 从常驻底图实测梯度分布标定阈值。底图恒非空。
makeVariantProperty(dv, m.quant, cs, vmin, vmax, opacity); GradStats dvGs;
if (dv.useGradientOpacity && source.baseImage() != nullptr) {
dvGs = sampleGradientMagnitude(source.baseImage());
std::cout << "[view] 梯度幅值分布(底图,量化域,样本 " << dvGs.samples
<< "): median=" << dvGs.median << " p90=" << dvGs.p90
<< " p99=" << dvGs.p99 << "\n";
}
vtkSmartPointer<vtkVolumeProperty> prop = makeVariantProperty(
dv, m.quant, cs, vmin, vmax, opacity,
dv.useGradientOpacity ? &dvGs : nullptr);
// 渲染窗口preview/smoke 走离屏,否则真窗口。 // 渲染窗口preview/smoke 走离屏,否则真窗口。
vtkSmartPointer<vtkRenderWindow> rw; vtkSmartPointer<vtkRenderWindow> rw;
@ -3616,10 +3684,7 @@ constexpr double kPolishMaxOpacityGrad = 0.6; // b/c梯度门控后可提
// 在 VTK_SHORT 局部体上采样梯度幅值分布(量化域,中心差分),返回有序的若干分位数。 // 在 VTK_SHORT 局部体上采样梯度幅值分布(量化域,中心差分),返回有序的若干分位数。
// 跳过 kBlank 体素及其邻居(空值不参与梯度)。返回 {median, p75, p90, p99, max}。 // 跳过 kBlank 体素及其邻居(空值不参与梯度)。返回 {median, p75, p90, p99, max}。
struct GradStats { // GradStats 已在文件上方前置声明,供 C4 画廊共用。)
double median = 0, p75 = 0, p90 = 0, p99 = 0, mx = 0;
std::size_t samples = 0;
};
GradStats sampleGradientMagnitude(vtkImageData* img) { GradStats sampleGradientMagnitude(vtkImageData* img) {
int dims[3]; int dims[3];
img->GetDimensions(dims); img->GetDimensions(dims);