feat(vtk): 逐线三维体调亮调清晰(gallery 4 组对照)

P3 默认(seismic+严格梯度门+低 ambient)整体偏暗、均匀层被门全透成空。
本次只调视觉(配色/不透明度/梯度门松弛/光照),不动建体/桥接/异步/LOD:
- 新增调亮版 seismic、增强灰度两套配色
- GalleryVariant 加 gradGateRelax(梯度门松弛 0~1)与 ambient 字段
- makeVariantProperty 按松弛度抬低梯度地板+左移阈值,保留弱结构;ambient 由变体控
- 4 组对照重排:var1 暗版基线 / var2 提亮 seismic / var3 jet 高对比 / var4 增强灰度(默认)
- gallery --out 让出图落在 store 目录(tmp/line001_proc),便于就地对照
- 端点仍按该体 2/98 分位自适应,非写死

实测(tmp/line001_proc):默认 var4 平均亮度 21.97→43.28、结构像素 3.9%→23.1%,
明显更亮更清晰;fps 约 51(交互级)。
This commit is contained in:
gaozheng 2026-06-25 07:36:40 +08:00
parent c94992a8d5
commit 4330e12c3e
1 changed files with 125 additions and 49 deletions

View File

@ -1318,6 +1318,34 @@ geopro::core::ColorScale makeJetColorScale(double vmin, double vmax) {
return cs;
}
// 调亮版 seismic与 makeSeismicColorScale 同红-白-蓝走向,但把蓝端提亮、整体抬白,
// 弱信号也落在更亮的色域(强负不再是深蓝、零附近纯白、强正亮红),整体更醒目不发暗。
geopro::core::ColorScale makeBrightSeismicColorScale(double vmin, double vmax) {
geopro::core::ColorScale cs;
const double span = (vmax > vmin) ? (vmax - vmin) : 1.0;
auto at = [&](double t) { return vmin + span * t; };
cs.addStop(at(0.00), geopro::core::Rgba{70, 130, 255, 255}); // 亮蓝(强负)
cs.addStop(at(0.30), geopro::core::Rgba{170, 210, 255, 255}); // 浅亮蓝
cs.addStop(at(0.50), geopro::core::Rgba{255, 255, 255, 255}); // 纯白(零)
cs.addStop(at(0.70), geopro::core::Rgba{255, 200, 150, 255}); // 亮浅橙
cs.addStop(at(1.00), geopro::core::Rgba{255, 80, 60, 255}); // 亮红(强正)
return cs;
}
// 增强灰度:黑→白单调,但中段抬亮、两端拉满,弱反射也落在中亮灰,层界面/竖纹对比醒目。
// GPR 内部水平层叠/基底反射用灰度往往最干净直读(不被多色相干扰)。
geopro::core::ColorScale makeGrayEnhancedColorScale(double vmin, double vmax) {
geopro::core::ColorScale cs;
const double span = (vmax > vmin) ? (vmax - vmin) : 1.0;
auto at = [&](double t) { return vmin + span * t; };
cs.addStop(at(0.00), geopro::core::Rgba{20, 25, 40, 255}); // 强负:近黑带冷调
cs.addStop(at(0.30), geopro::core::Rgba{120, 125, 135, 255}); // 中负:中灰(抬亮)
cs.addStop(at(0.50), geopro::core::Rgba{180, 182, 188, 255}); // 零附近:亮灰
cs.addStop(at(0.70), geopro::core::Rgba{225, 222, 210, 255}); // 中正:暖亮灰
cs.addStop(at(1.00), geopro::core::Rgba{255, 252, 240, 255}); // 强正:近白
return cs;
}
// 「实体感」不透明度包络Task 12d gallery与 structural 双端斜坡不同,这里让
// 中高值段普遍可见——背景(近零)仍压低但不归零,中高段从 floorOpacity 平滑升到
// maxOpacity使体读起来像半透明实心块、内部层次而非只剩两端薄壳可见。
@ -2916,7 +2944,13 @@ enum class OpacityProfile {
kSolid, // V 形实体感:中高值段普遍可见,半透明实心块
kStructural, // 现有双端斜坡:仅正负两端不透明(对照基线)
};
enum class ColorChoice { kStructural, kSeismic, kJet };
enum class ColorChoice {
kStructural,
kSeismic,
kJet,
kBrightSeismic, // 调亮版 seismic
kGrayEnhanced, // 增强灰度
};
struct GalleryVariant {
const char* name; // 文件名后缀view-<name>.png
@ -2934,42 +2968,55 @@ struct GalleryVariant {
// ---- C4 视觉调优(梯度不透明度 + 光照),默认关 → 不改既有变体行为 ----
bool useGradientOpacity = false; // SetGradientOpacity均匀层透明、界面/异常显形
bool useShade = false; // SetShade层界面带立体明暗
// ---- P4 调亮/调清晰 ----
// 梯度门松弛度 0~10=严格(均匀层全透、偏暗) 1=宽松(均匀层保留底不透明、更亮更满)。
// 宽松时降低梯度阈值并抬高「低梯度区」的不透明度地板,让横向层叠/基底反射等弱结构保留。
double gradGateRelax = 0.0;
double ambient = 0.30; // 光照环境项别太低否则体面偏暗useShade 时生效。
};
// C4 视觉调优4 组对照(纯标量 / +梯度不透明度 / +光照 / 全开)。
// 同一局部段、同一 seismic 配色、同一斜穿俯视取景El45/Az30视线穿进体内部而非
// 只看端面)——唯一变量是「梯度不透明度 / 光照」,供肉眼直读"内部结构是否透出"。
// maxOpacity 字段:纯标量组用低峰值(0.45,避免均匀积分糊成实心);开了梯度门的组用
// 高峰值(0.6)——均匀区被梯度门压透明后,层界面的净不透明度(标量×梯度)才够高、浮成实面。
// 末项 var4(全开) = kViewDefaultVariant → 交互窗口默认走全开。
// P4 调亮/调清晰4 组对照(暗版基线 / 提亮 / 高对比 / 灰度增强)。
// 同一局部段、同一斜穿取景El45/Az30。P3 默认seismic+严格梯度门+低 ambient整体
// 偏暗、均匀层被门全透成空。本组在「消雾」与「够亮够满」间放宽门控、抬 ambient、换更亮
// 配色,让横向层叠/竖纹/基底反射醒目。所有端点按该体 2/98 分位自适应runGalleryVariant
// 内标定),非写死单一数据。末项 = kViewDefaultVariant → 交互窗口默认取最清晰醒目组。
//
// 字段floorOpacity/midOpacity/maxOpacityV 形标量包络、gradGateRelax梯度门松弛
// 0~1、ambient光照环境项。提亮组抬高 floor/mid 让均匀层保留底不透明、抬 ambient
// 防体面发暗、放宽梯度门保留弱结构。
const GalleryVariant kGalleryVariants[] = {
// var1纯标量不透明度基线——无梯度门、无光照。GPR 均匀层段沿射线积分糊成
// 半透明实心块,只外表面/端面可读,内部水平分层难分辨(对照基准)。
// var1暗版基线(= P3 默认——seismic + 严格梯度门(relax0) + 低 ambient0.3 + 暗背景。
// 均匀层几乎全透、整体偏暗偏空,仅作对照
{"var1", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.04, 0.30, 0.45, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
"纯标量不透明度(基线)V形包络(floor0.04/mid0.30/max0.45)+seismic"
"无梯度门/无光照,均匀层积分糊成半透明块、仅端面可读",
/*useGradientOpacity=*/false, /*useShade=*/false},
// var2+梯度不透明度——低梯度(均匀层)透明、高梯度(层界面/异常)显形,射线穿透
// 均匀雾、内部界面浮出。标量峰值提到 0.6(梯度门压透明后层面才够实)。
{"var2", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
"+梯度不透明度:低梯度(均匀层)透明、高梯度(界面/异常)显形标量峰值0.6"
"射线穿透均匀雾、内部界面浮出",
/*useGradientOpacity=*/true, /*useShade=*/false},
// var3+光照——在梯度门基础上 ShadeOn层界面带立体明暗(非纯平涂)。
{"var3", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.05, 0.05, 0.09},
"+光照(梯度门+Shade):层界面带立体明暗(Ambient0.3/Diffuse0.7/Specular0.2)"
"界面起伏更有体积感",
/*useGradientOpacity=*/true, /*useShade=*/true},
// var4全开——梯度不透明度 + 光照 + seismic 双端斜坡配色 + 略亮冷灰背景。
// 综合最佳选作交互窗口默认kViewDefaultVariant
{"var4", OpacityProfile::kSolid, ColorChoice::kSeismic,
0.04, 0.30, 0.60, 8.0, 45.0, 30.0, 1.5, {0.07, 0.08, 0.11},
"全开(综合最佳):梯度不透明度+光照+seismic+略亮冷灰背景,内部分层界面/异常"
"从均匀块透出 → 交互窗口默认",
/*useGradientOpacity=*/true, /*useShade=*/true},
"暗版基线(P3默认)seismic+严格梯度门+低ambient+暗背景,均匀层近全透、偏暗偏空",
/*useGradientOpacity=*/true, /*useShade=*/true,
/*gradGateRelax=*/0.0, /*ambient=*/0.30},
// var2提亮——调亮版 seismic + 放宽梯度门(relax0.6) + 抬 floor/mid/max + 高 ambient
// + 略亮背景。均匀层保留底不透明、横向层叠透出、整体明显更亮更实。
{"var2", OpacityProfile::kSolid, ColorChoice::kBrightSeismic,
0.10, 0.45, 0.75, 8.0, 45.0, 30.0, 1.5, {0.12, 0.13, 0.17},
"提亮调亮版seismic+放宽梯度门(relax0.6)+抬不透明度(floor0.10/mid0.45/max0.75)"
"+高ambient0.5,均匀层保留、层叠透出、明显更亮",
/*useGradientOpacity=*/true, /*useShade=*/true,
/*gradGateRelax=*/0.6, /*ambient=*/0.50},
// var3高对比——jet 高饱和配色 + 放宽梯度门(relax0.5) + 抬不透明度 + ambient0.45 +
// 暗背景衬高饱和。弱信号也映到鲜明色相,层界面/异常对比最狠。
{"var3", OpacityProfile::kSolid, ColorChoice::kJet,
0.08, 0.42, 0.78, 8.0, 45.0, 30.0, 1.5, {0.04, 0.04, 0.07},
"高对比jet高饱和+放宽梯度门(relax0.5)+抬不透明度+ambient0.45+暗背景衬色,"
"弱信号映鲜明色相、层界面对比最狠",
/*useGradientOpacity=*/true, /*useShade=*/true,
/*gradGateRelax=*/0.5, /*ambient=*/0.45},
// var4灰度增强默认——增强灰度 + 放宽梯度门(relax0.7) + 抬不透明度 + 高 ambient
// + 中性背景。GPR 内部水平层叠/竖纹/基底反射用增强灰度最干净直读,选作交互默认。
{"var4", OpacityProfile::kSolid, ColorChoice::kGrayEnhanced,
0.12, 0.48, 0.80, 8.0, 45.0, 30.0, 1.5, {0.14, 0.15, 0.18},
"灰度增强(默认/综合最佳):增强灰度+放宽梯度门(relax0.7)+抬不透明度"
"(floor0.12/mid0.48/max0.80)+高ambient0.5+中性背景,层叠/竖纹/基底反射干净醒目"
" → 交互窗口默认",
/*useGradientOpacity=*/true, /*useShade=*/true,
/*gradGateRelax=*/0.7, /*ambient=*/0.50},
};
// 交互窗口(无 flag 的 view)的默认视觉变体 = var4kGalleryVariants 末项)。
@ -2981,6 +3028,10 @@ geopro::core::ColorScale pickColor(ColorChoice c, double vmin, double vmax) {
switch (c) {
case ColorChoice::kSeismic: return makeSeismicColorScale(vmin, vmax);
case ColorChoice::kJet: return makeJetColorScale(vmin, vmax);
case ColorChoice::kBrightSeismic:
return makeBrightSeismicColorScale(vmin, vmax);
case ColorChoice::kGrayEnhanced:
return makeGrayEnhancedColorScale(vmin, vmax);
case ColorChoice::kStructural:
default: return makeStructuralColorScale(vmin, vmax);
}
@ -3005,22 +3056,34 @@ vtkSmartPointer<vtkVolumeProperty> makeVariantProperty(
prop = makeTunedVolumeProperty(q, cs, vmin, vmax, maxOpacity);
}
// 梯度不透明度:均匀层(低梯度)透明,层界面/异常边缘(高梯度)显形。阈值按实测分布:
// median 处仍 0压住均匀积分雾p90 升到 0.5p99 到 0.9。无 gs未测则跳过。
// 梯度不透明度:均匀层(低梯度)半透/透明,层界面/异常边缘(高梯度)显形。阈值按实测
// 分布,并按 gradGateRelax 松弛:
// - relax=0严格暗版median→0、p90→0.5、p99→0.9,均匀层全透 → 偏暗偏空。
// - relax>0调亮低梯度地板抬到 floorG均匀层保留底不透明、更亮更满阈值
// 整体左移(更早升起),让横向层叠/基底反射等弱结构保留可见。
// 无 gs未测则跳过。
if (v.useGradientOpacity && gs != nullptr && gs->samples > 0) {
const double relax = std::clamp(v.gradGateRelax, 0.0, 1.0);
const double floorG = 0.30 * relax; // 低梯度区不透明度地板(均匀层保留度)
const double midG = 0.5 + 0.25 * relax;
const double hiG = 0.9;
// 阈值左移:松弛越大,门越早从低梯度升起(结构保留越多)。
const double tLo = std::max(1.0, gs->median * (1.0 - 0.7 * relax));
const double tMid = std::max(2.0, gs->p90 * (1.0 - 0.6 * relax));
const double tHi = std::max(3.0, gs->p99 * (1.0 - 0.4 * relax));
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);
grad->AddPoint(0.0, floorG);
grad->AddPoint(tLo, floorG);
grad->AddPoint(tMid, midG);
grad->AddPoint(tHi, hiG);
prop->SetGradientOpacity(grad);
}
// 光照ShadeOn + Ambient/Diffuse保留立体明暗Specular 压到 0.05(近乎关)避免
// 旋转时视角相关的高光在体表游走形成「移动白斑」。保留 ambient/diffuse 立体感
// 旋转时视角相关的高光在体表游走形成「移动白斑」。Ambient 由变体控(别太低否则偏暗)
if (v.useShade) {
prop->ShadeOn();
prop->SetAmbient(0.3);
prop->SetAmbient(v.ambient);
prop->SetDiffuse(0.7);
prop->SetSpecular(0.05);
prop->SetSpecularPower(10.0);
@ -3298,8 +3361,9 @@ std::size_t viewSetupDefaultFrame(ViewState* st, vtkRenderer* ren) {
}
// 渲一组画廊变体并存 PNG报告 结构像素 / 平均亮度 / fps。返回 0=OK。
// shotDirOverride 非空 → PNG 存到该目录P4 让 gallery 出图落在 store 同目录,便于对照)。
int runGalleryVariant(const std::string& dir, const GalleryVariant& v,
int frames) {
int frames, const std::string& shotDirOverride = "") {
const int winW = 1280, winH = 800;
geopro::data::ChunkedVolumeStore store(dir);
const geopro::data::StoreMeta& m = store.meta();
@ -3369,7 +3433,9 @@ int runGalleryVariant(const std::string& dir, const GalleryVariant& v,
rw->Render();
const fs::path shotDir =
fs::path("docs") / "superpowers" / "plans" / "poc-lod-shots";
shotDirOverride.empty()
? fs::path("docs") / "superpowers" / "plans" / "poc-lod-shots"
: fs::path(shotDirOverride);
fs::create_directories(shotDir);
const std::string pngPath =
(shotDir / (std::string("view-") + v.name + ".png")).string();
@ -3430,9 +3496,12 @@ int runGalleryVariant(const std::string& dir, const GalleryVariant& v,
return ok ? 0 : 1;
}
// view --gallery依次渲全部 4 组变体。
int cmdViewGallery(const std::string& dir, int frames) {
// view --gallery依次渲全部 4 组变体。shotDir 空 → 默认 docs/.../poc-lod-shots
// 否则存到 shotDirP4默认传 store 目录4 张图落在 tmp/line001_proc
int cmdViewGallery(const std::string& dir, int frames,
const std::string& shotDir = "") {
std::cout << "[view --gallery] storeDir=" << dir << " frames=" << frames
<< " shotDir=" << (shotDir.empty() ? "(默认)" : shotDir)
<< "\n[view --gallery] 离屏闸门复检...\n";
if (cmdOffscreenSmoke() != 0) {
std::cout << "[view --gallery] 闸门失败,中止。\n";
@ -3440,10 +3509,12 @@ int cmdViewGallery(const std::string& dir, int frames) {
}
int rc = 0;
for (const auto& v : kGalleryVariants) {
if (runGalleryVariant(dir, v, frames) != 0) rc = 1;
if (runGalleryVariant(dir, v, frames, shotDir) != 0) rc = 1;
}
std::cout << "\n[view --gallery] 完成4 张图存于 "
"docs/superpowers/plans/poc-lod-shots/view-var{1..4}.png\n";
const std::string outDesc =
shotDir.empty() ? "docs/superpowers/plans/poc-lod-shots" : shotDir;
std::cout << "\n[view --gallery] 完成4 张图存于 " << outDesc
<< "/view-var{1..4}.png\n";
return rc;
}
@ -3488,9 +3559,13 @@ int cmdView(int argc, char** argv) {
const bool nearPreview =
hasFlag("near") || (a.kv.count("variant") && a.get("variant", "") == "near");
// 画廊出图目录:--out <dir> 显式指定;否则默认存到 store 目录P4gallery 落在
// tmp/line001_proc便于控制方就地对照
const std::string galleryShotDir = a.get("out", dir);
// 画廊模式Task 12d渲 4 组视觉调参图供挑选。优先于其余路径。
if (hasFlag("gallery")) {
return cmdViewGallery(dir, frames);
return cmdViewGallery(dir, frames, galleryShotDir);
}
// 单变体view --preview --variant NN=1..4),只渲第 N 组near 不走此路)。
if (preview && a.kv.count("variant") && !nearPreview) {
@ -3503,7 +3578,8 @@ int cmdView(int argc, char** argv) {
}
std::cout << "[view] storeDir=" << dir << " 单变体 variant=" << vi << "\n";
if (cmdOffscreenSmoke() != 0) return 1;
return runGalleryVariant(dir, kGalleryVariants[vi - 1], frames);
return runGalleryVariant(dir, kGalleryVariants[vi - 1], frames,
galleryShotDir);
}
std::cout << "[view] storeDir=" << dir << " exagg=" << exagg