perf(vtk): view-all 每条体接入视野自适应引擎修拉近纹理墙

每条线改用 ViewAdaptiveVolumeSource(LOD+视锥裁剪+异步重组,复用未重写),
恒产 ≤16384 单纹理 → 根除拉近时 GL MAX_3D_TEXTURE_SIZE 纹理维度错误。
相机逐线逆变换到局部帧喂引擎选层选区;按世界 AABB 与相机视锥面比对裁屏外线
(base+hires 两层皆隐);mapper 开 Auto/InteractiveAdjustSampleDistances 拖动降采样。
世界摆放 T(Scale-RotateZ-Translate) 与原 makePlacedVolume 同口径,CGCS2000 定位不变。

实测(20条全分辨率,exagg=8):spread 60 线条分离时拉近裁掉 13/20、提速 1.72x
(概览 29.7fps→拉近 50.9fps);两场景纹理维度错误均消除。
This commit is contained in:
gaozheng 2026-06-25 14:57:41 +08:00
parent 572fbf8d7b
commit 46f0144287
1 changed files with 482 additions and 132 deletions

View File

@ -19,7 +19,9 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <limits>
#include <map> #include <map>
#include <memory>
#include <set> #include <set>
#include <string> #include <string>
#include <thread> #include <thread>
@ -3984,61 +3986,209 @@ int cmdViewGallery(const std::string& dir, int frames,
// //
// --preview 离屏出俯视(top) + 斜视(oblique)两张图展示 20 条并排成测区;否则开真窗口可转可缩。 // --preview 离屏出俯视(top) + 斜视(oblique)两张图展示 20 条并排成测区;否则开真窗口可转可缩。
// 一条线在世界中的摆放(刚体变换 + 该线已加载的整卷体)。 // 一条线在世界中的摆放(刚体变换 + 该线的视野自适应引擎源)。
struct LinePlacement { //
std::string name; // 明星路_NNN // P11每条线不再整卷加载固定层而是各持一个 ViewAdaptiveVolumeSourceLOD+视锥
vtkSmartPointer<vtkImageData> img; // 该线整卷 VTK_SHORT线局部坐标 // 裁剪+异步重组引擎,与单条 view 完全同款)。引擎 exagg=1.0(不烘几何),垂向夸张/
geopro::data::StoreMeta meta; // 量化/几何 // 航向/平移全由世界变换 T 承担(与单条 view 把 exagg 放 actor SetScale 同理)。每帧
double startX = 0, startY = 0; // 起点局部米(相对公共原点) // 把世界相机逆变换到该线局部帧喂引擎 → selectLod 选层选区(视锥外→引擎不提交)。
double headingDeg = 0; // 航向角(度,相对 +X 东向,逆时针) struct PlacedSource {
double spreadX = 0, spreadY = 0; // 可选横向铺开偏移(垂直航向,--spread>0 时) std::string name; // 明星路_NNN
std::unique_ptr<geopro::render::ViewAdaptiveVolumeSource> source;
geopro::data::StoreMeta meta; // 量化/几何
double startX = 0, startY = 0; // 起点局部米(相对公共原点)
double headingDeg = 0; // 航向角(度,相对 +X 东向)
double spreadX = 0, spreadY = 0; // 可选横向铺开偏移
vtkSmartPointer<vtkTransform> world; // TScale(1,1,exagg)→RotateZ→Translate
vtkSmartPointer<vtkTransform> worldInv; // T⁻¹相机逆变换到局部帧
vtkSmartPointer<vtkVolumeProperty> prop; // 逐线 2/98 分位标定的传函(底图+高清共用)
vtkSmartPointer<vtkVolume> baseVolume; // 常驻粗底图(永在场,套 T
vtkSmartPointer<vtkSmartVolumeMapper> baseMapper;
vtkSmartPointer<vtkVolume> hiresVolume; // 高清叠加(就绪后局部覆盖,套 T
vtkSmartPointer<vtkSmartVolumeMapper> hiresMapper;
vtkSmartPointer<vtkImageData> currentImg; // 持当前高清单图引用mapper 仅持裸指针)
double worldBounds[6] = {0, 0, 0, 0, 0, 0}; // 该线(含 T+底图盒)的世界 AABB视锥裁剪用
bool culled = false; // 本帧是否被视锥裁掉(两层皆隐 → 真跳过)
}; };
// 由该线整卷体 + 公共世界原点 + 航向 + Z 夸张 → vtkVolume套刚体变换 + 传函)。 // 由 起点+航向+Z 夸张 → 世界刚体变换 T。
// 变换合成VTK先加先内层作用于点的顺序为 后加先施Translate→RotateZ→Scale(1,1,exagg) // 合成顺序VTK PostMultiply先加先施于点Scale(1,1,exagg)→RotateZ→Translate
// 即点先按 exagg 拉伸 Z局部再绕 Z 旋到真实航向,再平移到世界起点。 // 即点先按 exagg 拉伸 Z局部深度再绕竖直轴转真实航向再平移到世界起点。
vtkSmartPointer<vtkVolume> makePlacedVolume(const LinePlacement& lp, double exagg, vtkSmartPointer<vtkTransform> makeLineTransform(double startX, double startY,
double& vminOut, double& vmaxOut) { double headingDeg,
const GalleryVariant& v = kViewDefaultVariant; // P4 默认醒目版var4 double spreadX, double spreadY,
const geopro::data::StoreMeta& m = lp.meta; double exagg) {
// 逐体 2/98 分位标定(裁离群,自适应该体值域;退化则回退全量化域)。
double vmin = m.vminPhys, vmax = m.vmaxPhys;
const ScalarPercentiles pc =
sampleScalarPercentiles(lp.img.Get(), m.quant, 0.02, 0.98);
if (pc.samples > 0) {
vmin = pc.lo;
vmax = pc.hi;
}
vminOut = vmin;
vmaxOut = vmax;
const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax);
GradStats gs;
if (v.useGradientOpacity) gs = sampleGradientMagnitude(lp.img.Get());
vtkSmartPointer<vtkVolumeProperty> prop = makeVariantProperty(
v, m.quant, cs, vmin, vmax, v.maxOpacity,
v.useGradientOpacity ? &gs : nullptr);
vtkNew<vtkSmartVolumeMapper> mapper;
mapper->SetInputData(lp.img.Get());
mapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
mapper->SetAutoAdjustSampleDistances(0);
mapper->SetInteractiveAdjustSampleDistances(0);
auto volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(mapper);
volume->SetProperty(prop);
// 刚体摆放 + Z 夸张(一并烘进 UserTransform
auto xf = vtkSmartPointer<vtkTransform>::New(); auto xf = vtkSmartPointer<vtkTransform>::New();
xf->PostMultiply(); xf->PostMultiply();
xf->Scale(1.0, 1.0, exagg); // 只 Z 夸张(局部深度) xf->Scale(1.0, 1.0, exagg);
xf->RotateZ(lp.headingDeg); // 绕竖直轴转航向 xf->RotateZ(headingDeg);
// 平移到世界起点(+ 可选横向铺开偏移,垂直航向,让重叠的同路多趟可分辨)。 xf->Translate(startX + spreadX, startY + spreadY, 0.0);
xf->Translate(lp.startX + lp.spreadX, lp.startY + lp.spreadY, 0.0); return xf;
volume->SetUserTransform(xf); }
return volume;
// 逐线传函:从该线常驻底图(整卷代表)实测 2/98 分位标定色阶/不透明度端点 + 梯度门,
// 与单条 view 的传函标定同口径(底图非空恒可标定;退化回退全量化域)。
vtkSmartPointer<vtkVolumeProperty> buildLineProperty(
const geopro::data::StoreMeta& m, vtkImageData* basis) {
const GalleryVariant& v = kViewDefaultVariant; // P4 默认醒目版var4
double vmin = m.vminPhys, vmax = m.vmaxPhys;
if (basis != nullptr) {
const ScalarPercentiles pc =
sampleScalarPercentiles(basis, m.quant, 0.02, 0.98);
if (pc.samples > 0) {
vmin = pc.lo;
vmax = pc.hi;
}
}
const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax);
GradStats gs;
if (v.useGradientOpacity && basis != nullptr) gs = sampleGradientMagnitude(basis);
return makeVariantProperty(v, m.quant, cs, vmin, vmax, v.maxOpacity,
(v.useGradientOpacity && gs.samples > 0) ? &gs
: nullptr);
}
// 把世界相机参数逆变换到某线局部帧T⁻¹pos/focal 是点含平移逆up 是方向
// 仅旋转逆TransformVector 不含平移)。再调引擎 updateView 选层选区(视锥外→引擎
// 内部 selectLod 判 empty → 不提交,保留上一就绪/无图)。
void viewAllSubmitOneLine(PlacedSource& ps, vtkCamera* worldCam,
double aspect, int viewportH) {
if (ps.culled || worldCam == nullptr) return;
double wp[3], wf[3], wu[3];
worldCam->GetPosition(wp);
worldCam->GetFocalPoint(wf);
worldCam->GetViewUp(wu);
geopro::render::CameraView c{};
ps.worldInv->TransformPoint(wp, c.pos);
ps.worldInv->TransformPoint(wf, c.focal);
ps.worldInv->TransformVector(wu, c.up);
c.fovYDeg = worldCam->GetViewAngle();
c.aspect = aspect;
c.viewportH = viewportH;
// 引擎 exagg=1.0:局部帧几何无夸张(夸张在 T 里),故喂引擎 volumeView 默认即可。
ps.source->setAspect(aspect);
ps.source->setViewportHeight(viewportH);
ps.source->updateView(c, geopro::render::VolumeView{
ps.meta.nx, ps.meta.ny, ps.meta.nz,
ps.meta.brick, ps.source->levelCount(),
{ps.meta.origin[0], ps.meta.origin[1],
ps.meta.origin[2]},
{ps.meta.spacing[0], ps.meta.spacing[1],
ps.meta.spacing[2]},
1.0});
}
// 非阻塞拉取该线后台已就绪的高清单图,喂高清 mapper无新结果→沿用上一帧
// 返回 1=换上新图。
int viewAllPickOneLine(PlacedSource& ps) {
if (ps.culled) return 0;
auto imgs = ps.source->currentImages(); // 内部 takeLatest非阻塞
if (imgs.empty() || imgs[0] == nullptr) return 0;
if (imgs[0] == ps.currentImg) return 0;
ps.currentImg = imgs[0];
ps.hiresMapper->SetInputData(ps.currentImg);
ps.hiresMapper->Update();
ps.hiresVolume->SetVisibility(1);
return 1;
}
// 视锥裁剪(#2把该线世界 AABB 与相机 6 个视锥面比对,整盒在任一面外侧 → 裁掉
// base+hires 两层皆隐 → 该线本帧完全不渲,省下解压/重组/ray-march。盒 8 角全在
// 某面负半空间才裁(保守,绝不误裁部分可见的线)。
bool aabbOutsideFrustum(const double b[6], const double planes[24]) {
for (int p = 0; p < 6; ++p) {
const double a = planes[p * 4 + 0], bb = planes[p * 4 + 1],
cc = planes[p * 4 + 2], dd = planes[p * 4 + 3];
bool allOut = true;
for (int cx = 0; cx < 2 && allOut; ++cx)
for (int cy = 0; cy < 2 && allOut; ++cy)
for (int cz = 0; cz < 2 && allOut; ++cz) {
const double x = b[cx], y = b[2 + cy], z = b[4 + cz];
if (a * x + bb * y + cc * z + dd >= 0.0) allOut = false; // 该角在面内
}
if (allOut) return true; // 全 8 角在该面外 → 整盒在视锥外
}
return false;
}
// view-all 每帧驱动共享状态(挂 interactor 回调)。
struct ViewAllState {
std::vector<PlacedSource>* lines = nullptr;
vtkRenderer* ren = nullptr;
vtkCamera* cam = nullptr;
vtkRenderWindow* rw = nullptr;
vtkTextActor* fpsText = nullptr;
double aspect = 1400.0 / 900.0;
int viewportH = 900;
bool inCb = false;
};
// 重算各线视锥可见性(裁屏外线)+ 对可见线提交引擎目标非阻塞。culled 线两层皆隐。
void viewAllRefreshFrustum(ViewAllState* st) {
double planes[24];
st->cam->GetFrustumPlanes(st->aspect, planes);
for (PlacedSource& ps : *st->lines) {
const bool outside = aabbOutsideFrustum(ps.worldBounds, planes);
ps.culled = outside;
ps.baseVolume->SetVisibility(outside ? 0 : 1);
if (outside) {
ps.hiresVolume->SetVisibility(0);
} else {
// 可见:提交引擎目标(局部帧),高清可见性由 pick 决定(有就绪图才显)。
viewAllSubmitOneLine(ps, st->cam, st->aspect, st->viewportH);
ps.hiresVolume->SetVisibility(ps.currentImg != nullptr ? 1 : 0);
}
}
}
// 交互进行中:只重算视锥可见性 + 提交目标(非阻塞),主线程立即继续响应输入。
void viewAllOnInteracting(vtkObject*, unsigned long, void* clientData, void*) {
auto* st = static_cast<ViewAllState*>(clientData);
viewAllRefreshFrustum(st);
}
// 定时器:非阻塞拉取各可见线后台已就绪的新高清纹理换上 → 有新图才重渲。
void viewAllOnTimer(vtkObject*, unsigned long, void* clientData, void*) {
auto* st = static_cast<ViewAllState*>(clientData);
if (st->inCb) return;
int changed = 0;
for (PlacedSource& ps : *st->lines) changed += viewAllPickOneLine(ps);
if (changed > 0) {
st->ren->ResetCameraClippingRange();
st->rw->Render();
}
}
// 交互结束:重算视锥 + 提交 + 拉取 + 刷新 fps仅松手触发一次
void viewAllOnInteract(vtkObject*, unsigned long, void* clientData, void*) {
auto* st = static_cast<ViewAllState*>(clientData);
if (st->inCb) return;
st->inCb = true;
viewAllRefreshFrustum(st);
int culledN = 0, visN = 0;
for (PlacedSource& ps : *st->lines) {
viewAllPickOneLine(ps);
if (ps.culled) ++culledN; else ++visN;
}
st->ren->ResetCameraClippingRange();
constexpr int kFpsProbeFrames = 3;
Stopwatch swR;
for (int i = 0; i < kFpsProbeFrames; ++i) st->rw->Render();
const double fps = swR.elapsedMs() > 0
? 1000.0 * kFpsProbeFrames / swR.elapsedMs()
: 0.0;
char buf[256];
std::snprintf(buf, sizeof(buf),
"fps: %.1f | visible lines: %d | culled: %d", fps, visN,
culledN);
if (st->fpsText) st->fpsText->SetInput(buf);
st->rw->Render();
st->inCb = false;
} }
int cmdViewAll(int argc, char** argv) { int cmdViewAll(int argc, char** argv) {
@ -4159,12 +4309,15 @@ int cmdViewAll(int argc, char** argv) {
"如需把各趟铺开成可分辨的并排测区,加 --spread 60。\n"; "如需把各趟铺开成可分辨的并排测区,加 --spread 60。\n";
} }
// 3) 逐线:算起点局部米 + 航向 + 加载整卷体 → LinePlacement。 // 3) 逐线:算起点局部米 + 航向 + 建 ViewAdaptiveVolumeSource 引擎 → PlacedSource。
std::vector<LinePlacement> placements; // P11不再整卷固定层加载撞 GL 16384 纹理墙),改为每线一个视野自适应引擎,
// 引擎恒产 ≤16384 单纹理LOD+视野选区exagg/航向/平移由世界变换 T 承担。
const int winW = 1400, winH = 900;
const double aspect = static_cast<double>(winW) / winH;
std::vector<PlacedSource> lines;
int lineIdx = 0; int lineIdx = 0;
for (const LineGps& lg : gpsList) { for (const LineGps& lg : gpsList) {
// 轨迹 → CGCS2000 局部米投影到公共带号后减公共原点。XY 约定 x=东、y=北, // 轨迹 → CGCS2000 局部米投影到公共带号后减公共原点。XY 约定 x=东、y=北。
// 与下游航向 atan2(hy,hx) / 刚体平移 Translate(x,y) 一致。
std::vector<geopro::io::gpr::XY> trackM; std::vector<geopro::io::gpr::XY> trackM;
trackM.reserve(lg.track.pts.size()); trackM.reserve(lg.track.pts.size());
for (const auto& p : lg.track.pts) { for (const auto& p : lg.track.pts) {
@ -4175,11 +4328,9 @@ int cmdViewAll(int argc, char** argv) {
} }
const geopro::io::gpr::XY& start = trackM.front(); const geopro::io::gpr::XY& start = trackM.front();
// 航向:起→止主方向(直接首尾向量,稳健于逐点抖动)。
const geopro::io::gpr::XY& end = trackM.back(); const geopro::io::gpr::XY& end = trackM.back();
const double hx = end.x - start.x, hy = end.y - start.y; const double hx = end.x - start.x, hy = end.y - start.y;
const double headingDeg = std::atan2(hy, hx) * 180.0 / 3.14159265358979323846; const double headingDeg = std::atan2(hy, hx) * 180.0 / 3.14159265358979323846;
// 垂直航向的左法向单位向量 (-hy,hx)/|h|,用于 --spread 横向铺开。
const double hlen = std::hypot(hx, hy); const double hlen = std::hypot(hx, hy);
double spreadX = 0, spreadY = 0; double spreadX = 0, spreadY = 0;
if (spread > 0.0 && hlen > 0.0) { if (spread > 0.0 && hlen > 0.0) {
@ -4187,34 +4338,95 @@ int cmdViewAll(int argc, char** argv) {
spreadX = (-hy / hlen) * off; spreadX = (-hy / hlen) * off;
spreadY = (hx / hlen) * off; spreadY = (hx / hlen) * off;
} }
// 加载该线整卷体(按 --level夹到该 store 实际层数)。
const std::string storePath = (fs::path(storesDir) / lg.name).string();
geopro::data::ChunkedVolumeStore store(storePath);
const int lv = std::max(0, std::min(level, store.levels() - 1));
vtkSmartPointer<vtkImageData> img =
buildLevelImage(store, lv, store.meta());
LinePlacement lp;
lp.name = lg.name;
lp.img = img;
lp.meta = store.meta();
lp.startX = start.x;
lp.startY = start.y;
lp.headingDeg = headingDeg;
lp.spreadX = spreadX;
lp.spreadY = spreadY;
++lineIdx; ++lineIdx;
std::cout << "[view-all] " << lg.name << " level=" << lv << " 维度="
<< img->GetDimensions()[0] << "x" << img->GetDimensions()[1]
<< "x" << img->GetDimensions()[2] << " 起点局部米=(" << start.x
<< ", " << start.y << ") 航向=" << headingDeg << "°\n";
placements.push_back(std::move(lp));
}
std::cout << "[view-all] 加载并定位线数=" << placements.size() << "\n";
// 4) 同一 renderer 加全部 placed volume。 const std::string storePath = (fs::path(storesDir) / lg.name).string();
const int winW = 1400, winH = 900;
PlacedSource ps;
ps.name = lg.name;
// 引擎 exagg=1.0:垂向夸张放进世界变换 T与单条 view 把 exagg 放 actor 同理)。
ps.source = std::make_unique<geopro::render::ViewAdaptiveVolumeSource>(
storePath, /*exagg=*/1.0);
ps.source->setAspect(aspect);
ps.source->setViewportHeight(winH);
ps.meta = ps.source->meta();
ps.startX = start.x;
ps.startY = start.y;
ps.headingDeg = headingDeg;
ps.spreadX = spreadX;
ps.spreadY = spreadY;
// 世界变换 T + 逆变换(相机逆变换到局部帧)。
ps.world = makeLineTransform(start.x, start.y, headingDeg, spreadX, spreadY,
exagg);
ps.worldInv = vtkSmartPointer<vtkTransform>::New();
ps.worldInv->DeepCopy(ps.world);
ps.worldInv->Inverse();
// 逐线传函(从常驻底图标定)+ 底图层 + 高清层,两层皆套世界变换 T。
ps.prop = buildLineProperty(ps.meta, ps.source->baseImage());
ps.baseMapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
ps.baseMapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
ps.baseMapper->SetAutoAdjustSampleDistances(1);
ps.baseMapper->SetInteractiveAdjustSampleDistances(1);
ps.baseVolume = vtkSmartPointer<vtkVolume>::New();
if (ps.source->baseImage() != nullptr) {
ps.baseMapper->SetInputData(ps.source->baseImage());
ps.baseMapper->Update();
}
ps.baseVolume->SetMapper(ps.baseMapper);
ps.baseVolume->SetProperty(ps.prop);
ps.baseVolume->SetUserTransform(ps.world);
ps.hiresMapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
ps.hiresMapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
// #1 拖动降采样:交互式采样距离自适应(拖动→大步长降采样跟手,松手→全质量)。
ps.hiresMapper->SetAutoAdjustSampleDistances(1);
ps.hiresMapper->SetInteractiveAdjustSampleDistances(1);
ps.hiresVolume = vtkSmartPointer<vtkVolume>::New();
ps.hiresVolume->SetMapper(ps.hiresMapper);
ps.hiresVolume->SetProperty(ps.prop);
ps.hiresVolume->SetUserTransform(ps.world);
ps.hiresVolume->SetVisibility(0); // 无就绪高清前不显(底图兜底)
// 该线世界 AABB底图模型盒经 T 变换的 8 角包络)→ 视锥裁剪用。
if (ps.source->baseImage() != nullptr) {
double mb[6];
ps.source->baseImage()->GetBounds(mb);
double lo[3] = {std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
double hi[3] = {-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
for (int cx = 0; cx < 2; ++cx)
for (int cy = 0; cy < 2; ++cy)
for (int cz = 0; cz < 2; ++cz) {
double in[3] = {mb[cx], mb[2 + cy], mb[4 + cz]}, out[3];
ps.world->TransformPoint(in, out);
for (int d = 0; d < 3; ++d) {
lo[d] = std::min(lo[d], out[d]);
hi[d] = std::max(hi[d], out[d]);
}
}
ps.worldBounds[0] = lo[0]; ps.worldBounds[1] = hi[0];
ps.worldBounds[2] = lo[1]; ps.worldBounds[3] = hi[1];
ps.worldBounds[4] = lo[2]; ps.worldBounds[5] = hi[2];
}
int bd[3] = {0, 0, 0};
if (ps.source->baseImage()) ps.source->baseImage()->GetDimensions(bd);
std::cout << "[view-all] " << lg.name << " 引擎底图 level="
<< ps.source->baseLevel() << " 底图维度=" << bd[0] << "x" << bd[1]
<< "x" << bd[2] << " 起点局部米=(" << start.x << ", " << start.y
<< ") 航向=" << headingDeg << "°\n";
lines.push_back(std::move(ps));
}
std::cout << "[view-all] 加载并定位线数=" << lines.size()
<< "(每线一个 ViewAdaptiveVolumeSource 引擎)\n";
// 4) 同一 renderer 加全部线的底图层 + 高清层。
auto rw = preview ? makeOffscreenWindow(winW, winH) auto rw = preview ? makeOffscreenWindow(winW, winH)
: vtkSmartPointer<vtkRenderWindow>::New(); : vtkSmartPointer<vtkRenderWindow>::New();
if (!preview) rw->SetSize(winW, winH); if (!preview) rw->SetSize(winW, winH);
@ -4226,30 +4438,47 @@ int cmdViewAll(int argc, char** argv) {
auto capWin = vtkSmartPointer<CapturingOutputWindow>::New(); auto capWin = vtkSmartPointer<CapturingOutputWindow>::New();
vtkOutputWindow::SetInstance(capWin); vtkOutputWindow::SetInstance(capWin);
int placed = 0; for (PlacedSource& ps : lines) {
for (const LinePlacement& lp : placements) { ren->AddVolume(ps.baseVolume); // 先加底图 → 底层常渲
double vmin = 0, vmax = 0; ren->AddVolume(ps.hiresVolume); // 后加高清 → 叠在底图上
vtkSmartPointer<vtkVolume> vol = makePlacedVolume(lp, exagg, vmin, vmax);
ren->AddVolume(vol);
++placed;
} }
std::cout << "[view-all] 已加入场景体数=" << placed << "\n"; std::cout << "[view-all] 已加入场景线数=" << lines.size()
<< "(底图常驻 + 高清叠加,各 ≤16384 单纹理,绝不撞 GL 纹理墙)\n";
// 渲一帧 → 验非空 + 闸门纹理错。 ViewAllState st;
st.lines = &lines;
st.ren = ren.Get();
st.rw = rw.Get();
st.aspect = aspect;
st.viewportH = winH;
// 首帧ResetCamera 框全测区 → 概览(各线选粗 LOD 底图)。提交引擎目标 + 阻塞拉首图。
ren->ResetCamera(); ren->ResetCamera();
st.cam = ren->GetActiveCamera();
viewAllRefreshFrustum(&st);
// 概览阻塞拉一次(保证首帧高清就绪,离屏/真窗口都从有图起步)。
for (PlacedSource& ps : lines) {
if (ps.culled) continue;
for (int tries = 0; tries < 200; ++tries) {
if (viewAllPickOneLine(ps)) break;
if (ps.currentImg != nullptr) break;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
}
rw->Render(); rw->Render();
if (capWin->textureError()) { if (capWin->textureError()) {
std::cerr << "[view-all] 警告: 检测到 3D 纹理维度错误(某体超 GL 上限)," std::cerr << "[view-all] 警告: 仍检测到 3D 纹理维度错误(不应发生,引擎契约 "
"可调小 --level 增大 level 取更粗层。\n"; "≤16384\n";
} }
if (preview) { if (preview) {
fs::create_directories(shotDir); fs::create_directories(shotDir);
// 俯视(top):相机沿 -Z 俯看 XY 平面(看 20 条在测区平面如何并排铺开)。
// (A) 概览俯视(top):相机沿 -Z 俯看 XY 平面(看 20 条在测区平面铺开)。
{ {
ren->ResetCamera(); ren->ResetCamera();
vtkCamera* cam = ren->GetActiveCamera(); vtkCamera* cam = ren->GetActiveCamera();
double fp[3], pos[3]; double fp[3];
cam->GetFocalPoint(fp); cam->GetFocalPoint(fp);
const double* b = ren->ComputeVisiblePropBounds(); const double* b = ren->ComputeVisiblePropBounds();
const double span = std::max({b[1] - b[0], b[3] - b[2], b[5] - b[4]}); const double span = std::max({b[1] - b[0], b[3] - b[2], b[5] - b[4]});
@ -4257,67 +4486,188 @@ int cmdViewAll(int argc, char** argv) {
cam->SetFocalPoint(fp[0], fp[1], fp[2]); cam->SetFocalPoint(fp[0], fp[1], fp[2]);
cam->SetViewUp(0, 1, 0); cam->SetViewUp(0, 1, 0);
ren->ResetCameraClippingRange(); ren->ResetCameraClippingRange();
st.cam = cam;
viewAllRefreshFrustum(&st);
for (PlacedSource& ps : lines)
for (int t = 0; t < 100 && !ps.culled && ps.currentImg == nullptr; ++t) {
if (viewAllPickOneLine(ps)) break;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
rw->Render(); rw->Render();
const std::string p = (fs::path(shotDir) / "view-all-top.png").string(); savePng(rw.Get(), (fs::path(shotDir) / "view-all-top.png").string());
savePng(rw.Get(), p); std::cout << "[view-all] 俯视图存: "
std::cout << "[view-all] 俯视图存: " << p << "\n"; << (fs::path(shotDir) / "view-all-top.png").string() << "\n";
} }
// 斜视(oblique):默认 var4 取景El45/Az30 风格),看测区三维起伏。
// (B) 概览斜视(oblique)var4 取景 + 概览 fps全部可见、各选粗 LOD
int ovVisible = 0, ovCulled = 0;
double fpsOverview = 0.0;
{ {
ren->ResetCamera(); ren->ResetCamera();
vtkCamera* cam = ren->GetActiveCamera(); vtkCamera* cam = ren->GetActiveCamera();
cam->Elevation(35.0); cam->Elevation(35.0);
cam->Azimuth(30.0); cam->Azimuth(30.0);
ren->ResetCameraClippingRange(); ren->ResetCameraClippingRange();
st.cam = cam;
viewAllRefreshFrustum(&st);
for (PlacedSource& ps : lines) {
for (int t = 0; t < 100 && !ps.culled && ps.currentImg == nullptr; ++t) {
if (viewAllPickOneLine(ps)) break;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
if (ps.culled) ++ovCulled; else ++ovVisible;
}
rw->Render(); rw->Render();
const std::string p = savePng(rw.Get(),
(fs::path(shotDir) / "view-all-oblique.png").string(); (fs::path(shotDir) / "view-all-oblique.png").string());
savePng(rw.Get(), p); std::cout << "[view-all] 斜视图存: "
std::cout << "[view-all] 斜视图存: " << p << "\n"; << (fs::path(shotDir) / "view-all-oblique.png").string() << "\n";
rw->SetDesiredUpdateRate(15.0); // 拖动态:降采样
rw->Render();
Stopwatch sw;
const int frames = 60;
for (int f = 0; f < frames; ++f) {
cam->Azimuth(360.0 / frames);
rw->Render();
}
fpsOverview = sw.elapsedMs() > 0 ? frames * 1000.0 / sw.elapsedMs() : 0.0;
} }
// fps斜视取景预热后连渲计时真实多体共场景 fps不编造 // (C) 拉近一段:把焦点对准测区中段一条线的世界中心、近距正对该段 → 大部分线出
rw->Render(); // 视锥被裁掉,当前线只渲可见段的合适 LOD。报拉近 fps与概览对比
Stopwatch sw; // fps 探针用小幅 azimuth 摆动±6°模拟拉近态轻微转动观察而非整周 orbit
const int frames = 60; // ——整周 orbit 会把全测区转回视野使裁剪失效,不代表真实"拉近看一段"的交互。
for (int f = 0; f < frames; ++f) rw->Render(); int nearVisible = 0, nearCulled = 0;
const double ms = sw.elapsedMs(); double fpsNear = 0.0;
const double fps = ms > 0 ? frames * 1000.0 / ms : 0.0; {
const vtkIdType nb = countNonBlackPixels(rw.Get(), winW, winH); // 选中段一条线(取参与线的中位)作拉近目标,焦点置其世界 AABB 中心。
const PlacedSource& tgt = lines[lines.size() / 2];
const double cx = 0.5 * (tgt.worldBounds[0] + tgt.worldBounds[1]);
const double cy = 0.5 * (tgt.worldBounds[2] + tgt.worldBounds[3]);
const double cz = 0.5 * (tgt.worldBounds[4] + tgt.worldBounds[5]);
// 该段(含 exagg 后)世界跨度 → 近距视距(贴该段,使其充满视野、邻线出视锥)。
const double segLen = std::max({tgt.worldBounds[1] - tgt.worldBounds[0],
tgt.worldBounds[3] - tgt.worldBounds[2],
tgt.worldBounds[5] - tgt.worldBounds[4]});
vtkCamera* cam = ren->GetActiveCamera();
const double fovY = cam->GetViewAngle();
const double tanH =
std::max(1e-3, std::tan(0.5 * fovY * 3.14159265358979 / 180.0));
// 近距:只贴该段横截面尺度(取较短轴 ~该段 Y/Z 跨度的 1/4使邻行线出视锥。
const double shortSpan = std::max(
1.0, std::min(tgt.worldBounds[3] - tgt.worldBounds[2],
tgt.worldBounds[5] - tgt.worldBounds[4]));
const double dist = (0.25 * shortSpan) / tanH * 1.4;
cam->SetFocalPoint(cx, cy, cz);
cam->SetPosition(cx, cy + dist, cz + 0.3 * dist); // 斜上方近观该段
cam->SetViewUp(0, 0, 1);
ren->ResetCameraClippingRange();
st.cam = cam;
(void)segLen;
viewAllRefreshFrustum(&st);
for (PlacedSource& ps : lines) {
for (int t = 0; t < 120 && !ps.culled && ps.currentImg == nullptr; ++t) {
if (viewAllPickOneLine(ps)) break;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
if (ps.culled) ++nearCulled; else ++nearVisible;
}
rw->Render();
savePng(rw.Get(), (fs::path(shotDir) / "view-all-near.png").string());
std::cout << "[view-all] 拉近图存: "
<< (fs::path(shotDir) / "view-all-near.png").string() << "\n";
std::cout << "\n=== view-all --preview 测区全貌(多体共场景)===\n"; rw->SetDesiredUpdateRate(15.0); // 拖动态:降采样
std::cout << "参与线数 : " << placements.size() << "\n"; rw->Render();
std::cout << "level : " << level << "\n"; Stopwatch sw;
std::cout << "exagg(Z) : " << exagg << "\n"; const int frames = 60;
std::cout << "spread(横向铺开): " << spread << " m (0=纯真实位置)\n"; for (int f = 0; f < frames; ++f) {
std::cout << "非黑像素 : " << nb << " / " << (winW * winH) << "\n"; cam->Azimuth(f % 2 == 0 ? 0.4 : -0.4); // 小幅摆动(不转回全测区)
std::cout << "fps(" << frames << "帧连渲) : " << fps << "\n"; viewAllRefreshFrustum(&st); // 拖动中持续重算视锥裁剪
std::cout << "俯视图 : " << (fs::path(shotDir) / "view-all-top.png").string() rw->Render();
}
fpsNear = sw.elapsedMs() > 0 ? frames * 1000.0 / sw.elapsedMs() : 0.0;
rw->SetDesiredUpdateRate(0.5);
}
const vtkIdType nb = countNonBlackPixels(rw.Get(), winW, winH);
const bool texErr = capWin->textureError();
vtkOutputWindow::SetInstance(nullptr);
std::cout << "\n=== view-all --preview 测区全貌(多体共场景,引擎 LOD+视锥裁剪)===\n";
std::cout << "参与线数 : " << lines.size() << "\n";
std::cout << "exagg(Z) : " << exagg << "\n";
std::cout << "spread(横向铺开) : " << spread << " m (0=纯真实位置)\n";
std::cout << "纹理维度错误 : " << (texErr ? "是(!!)" : "否(引擎契约 ≤16384")
<< "\n"; << "\n";
std::cout << "斜视图 : " std::cout << "概览可见/裁剪线 : " << ovVisible << " / " << ovCulled << "\n";
std::cout << "概览 fps(60帧旋) : " << fpsOverview << "\n";
std::cout << "拉近可见/裁剪线 : " << nearVisible << " / " << nearCulled
<< " (视锥裁剪生效:裁掉屏外线)\n";
std::cout << "拉近 fps(60帧旋) : " << fpsNear << "\n";
const double speedup = fpsOverview > 0 ? fpsNear / fpsOverview : 0.0;
std::cout << "拉近/概览 fps 比 : " << speedup << "x\n";
std::cout << "末帧非黑像素 : " << nb << " / " << (winW * winH) << "\n";
std::cout << "俯视图 : "
<< (fs::path(shotDir) / "view-all-top.png").string() << "\n";
std::cout << "斜视图 : "
<< (fs::path(shotDir) / "view-all-oblique.png").string() << "\n"; << (fs::path(shotDir) / "view-all-oblique.png").string() << "\n";
std::cout << "拉近图 : "
<< (fs::path(shotDir) / "view-all-near.png").string() << "\n";
writeMetricLine( writeMetricLine(
"view-all,lines=" + std::to_string(placements.size()) + "view-all,lines=" + std::to_string(lines.size()) +
",level=" + std::to_string(level) + ",exagg=" + std::to_string(exagg) + ",exagg=" + std::to_string(exagg) + ",spread=" + std::to_string(spread) +
",spread=" + std::to_string(spread) + ",ovVisible=" + std::to_string(ovVisible) +
",nonBlack=" + std::to_string(nb) + ",fps=" + std::to_string(fps)); ",ovCulled=" + std::to_string(ovCulled) +
return nb > 0 ? 0 : 1; ",nearVisible=" + std::to_string(nearVisible) +
",nearCulled=" + std::to_string(nearCulled) +
",fpsOverview=" + std::to_string(fpsOverview) +
",fpsNear=" + std::to_string(fpsNear) +
",nonBlack=" + std::to_string(nb) +
",texErr=" + std::to_string(texErr ? 1 : 0));
return (nb > 0 && !texErr) ? 0 : 1;
} }
// 真窗口:可旋转/缩放。 // 真窗口:可旋转/缩放(每线引擎 LOD + 视锥裁剪 + 拖动降采样)。
rw->SetWindowName("gpr_poc view-all —— 20 条独立体按真实 GPS 摆成测区"); vtkOutputWindow::SetInstance(nullptr);
rw->SetWindowName("gpr_poc view-all —— 20 条独立体引擎LOD/视锥裁剪/拖动降采样");
// 屏幕左上角 fps + 可见/裁剪线数文本。
vtkNew<vtkTextActor> fpsText;
fpsText->SetInput("fps: -- | visible lines: -- | culled: --");
fpsText->GetTextProperty()->SetFontSize(20);
fpsText->GetTextProperty()->SetColor(1.0, 1.0, 0.4);
fpsText->SetDisplayPosition(12, winH - 30);
ren->AddViewProp(fpsText);
st.fpsText = fpsText.Get();
vtkNew<vtkRenderWindowInteractor> iren; vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(rw); iren->SetRenderWindow(rw);
vtkNew<vtkInteractorStyleTrackballCamera> style; vtkNew<vtkInteractorStyleTrackballCamera> style;
iren->SetInteractorStyle(style); iren->SetInteractorStyle(style);
ren->ResetCamera(); iren->SetDesiredUpdateRate(15.0); // 拖动态mapper 降采样
vtkCamera* cam = ren->GetActiveCamera(); iren->SetStillUpdateRate(0.5); // 静止态:全质量
cam->Elevation(35.0);
cam->Azimuth(30.0); vtkNew<vtkCallbackCommand> cbInteract;
ren->ResetCameraClippingRange(); cbInteract->SetCallback(viewAllOnInteracting);
cbInteract->SetClientData(&st);
iren->AddObserver(vtkCommand::InteractionEvent, cbInteract);
vtkNew<vtkCallbackCommand> cbEnd;
cbEnd->SetCallback(viewAllOnInteract);
cbEnd->SetClientData(&st);
iren->AddObserver(vtkCommand::EndInteractionEvent, cbEnd);
vtkNew<vtkCallbackCommand> cbTimer;
cbTimer->SetCallback(viewAllOnTimer);
cbTimer->SetClientData(&st);
iren->AddObserver(vtkCommand::TimerEvent, cbTimer);
std::cout << "[view-all] 打开真窗口。左键旋转 / 滚轮缩放 / q 退出。\n"; std::cout << "[view-all] 打开真窗口。左键旋转 / 滚轮缩放 / q 退出。\n";
iren->Initialize(); iren->Initialize();
iren->CreateRepeatingTimer(33); // ~30Hz 非阻塞拉取后台就绪纹理
rw->Render(); rw->Render();
iren->Start(); iren->Start();
std::cout << "[view-all] 窗口关闭,退出。\n"; std::cout << "[view-all] 窗口关闭,退出。\n";