// 离屏渲染验证:把真实数据渲成 PNG,肉眼核对方向/2D-3D 差异(不靠"进程活着"自欺)。 // 产出(D:/dev/spike_data/): // verify_section.png —— 平面反演剖面(#18, 数据详情), 浅部在上、深部在下, 纵向夸张填满 // verify_curtain_top.png —— 帘面俯视(二维) // verify_curtain_3d.png —— 帘面透视(三维) #include #include #include #include #include #include #include #include #include #include #include #include #include "CameraPreset.hpp" #include "ColorLutBuilder.hpp" #include "VoxelFromScatters.hpp" #include "actors/AnomalyActor.hpp" #include "actors/CurtainActor.hpp" #include "actors/ElectrodeActor.hpp" #include "actors/GridContourActor.hpp" #include "actors/MapLineActor.hpp" #include "actors/ScatterActor.hpp" #include "actors/TerrainActor.hpp" #include "geo/CrsTransform.hpp" #include "geo/GeoLocalFrame.hpp" #include "parse/SampleParsers.hpp" #include static std::string slurp(const char* p) { std::ifstream f(p); std::stringstream s; s << f.rdbuf(); return s.str(); } static void renderToPng(vtkRenderer* ren, const char* path, int w, int h) { vtkNew rw; rw->SetOffScreenRendering(1); rw->AddRenderer(ren); rw->SetSize(w, h); rw->Render(); vtkNew w2i; w2i->SetInput(rw); vtkNew png; png->SetFileName(path); png->SetInputConnection(w2i->GetOutputPort()); png->Write(); } int main() { using namespace geopro; const std::string dir = "D:/dev/spike_data/"; core::Grid g = data::parseGrid(slurp((dir + "grid.json").c_str())); core::ColorScale cs = data::parseColorScale(slurp((dir + "colorbar.json").c_str())); // 1) 平面剖面 (#18) — 俯视看 XY 平面(face-on); 纵向夸张 1.5x 填满宽面板(物探惯例) { auto a = render::buildGridContour(g, cs); const double exag = 1.5; vtkNew ren; ren->SetBackground(1, 1, 1); if (a.bands) { a.bands->SetScale(1.0, exag, 1.0); ren->AddActor(a.bands); } if (a.edges) { a.edges->SetScale(1.0, exag, 1.0); ren->AddActor(a.edges); } render::applyTop2D(ren); renderToPng(ren, (dir + "verify_section.png").c_str(), 1100, 360); } // GeoLocalFrame: 原点取 lat/lon 均值 double lat0 = std::accumulate(g.lat.begin(), g.lat.end(), 0.0) / (g.lat.empty() ? 1 : g.lat.size()); double lon0 = std::accumulate(g.lon.begin(), g.lon.end(), 0.0) / (g.lon.empty() ? 1 : g.lon.size()); core::GeoLocalFrame frame(lat0, lon0); // 2) 二维地图:测线折线俯视(浅灰底) { auto line = render::buildSurveyLine(g, frame); vtkNew ren; ren->SetBackground(0.96, 0.97, 0.99); ren->AddActor(line); render::applyTop2D(ren); renderToPng(ren, (dir + "verify_map.png").c_str(), 700, 500); } // 3) 帘面透视(三维);纵向夸张 z*3,使 15m 深度相对 62m 长度更像一道墙 { auto a = render::buildCurtain(g, cs, frame); a->SetScale(1.0, 1.0, 3.0); vtkNew ren; ren->SetBackground(1, 1, 1); ren->AddActor(a); render::applyFree3D(ren); renderToPng(ren, (dir + "verify_curtain_3d.png").c_str(), 700, 500); } // 4) 散点 (#17) — 剖面原数据彩色方块散点; x=距离, y=深度(取负向下), face-on 俯视 { core::ScatterField s = data::parseScatter(slurp((dir + "scatter.json").c_str())); core::ColorScale scs = data::parseColorScale(slurp((dir + "scatter_colorbar.json").c_str())); auto a = render::buildScatter(s, scs, 4.0F); vtkNew ren; ren->SetBackground(1, 1, 1); ren->AddActor(a); render::applyTop2D(ren); renderToPng(ren, (dir + "verify_scatter.png").c_str(), 1100, 360); std::printf("SCATTER pts=%zu\n", s.v.size()); } // 5) 异常叠加 — #18 平面剖面 + 异常 dashed 折线叠加(同纵向夸张 1.5x 对齐) { std::vector anomalies = data::parseAnomalies(slurp((dir + "anomaly.json").c_str())); auto a = render::buildGridContour(g, cs); const double exag = 1.5; vtkNew ren; ren->SetBackground(1, 1, 1); if (a.bands) { a.bands->SetScale(1.0, exag, 1.0); ren->AddActor(a.bands); } if (a.edges) { a.edges->SetScale(1.0, exag, 1.0); ren->AddActor(a.edges); } auto anomActors = render::buildAnomalies(anomalies); for (auto& act : anomActors) { act->SetScale(1.0, exag, 1.0); ren->AddActor(act); } // 顶部电极标记 ▼(同纵向夸张对齐)。 auto elec = render::buildElectrodes(g); if (elec) { elec->SetScale(1.0, exag, 1.0); ren->AddActor(elec); } render::applyTop2D(ren); renderToPng(ren, (dir + "verify_section_anomaly.png").c_str(), 1100, 360); std::printf("ANOMALY n=%zu\n", anomalies.size()); } // 6) dd_voxel — 两交叉剖面散点经 EPSG:4547→4326→GeoLocalFrame 配准 + IDW 成体素, // 叠一条帘面(grid1,同系)做空间参照; 透视核对"十字片"与帘面配准(profile1 片应贴合帘面)。 { std::vector profs; profs.push_back(data::parseScatter(slurp((dir + "scatter.json").c_str()))); profs.push_back(data::parseScatter(slurp((dir + "scatter2.json").c_str()))); core::ColorScale scs = data::parseColorScale(slurp((dir + "scatter_colorbar.json").c_str())); core::CrsTransform crs("EPSG:4547", "EPSG:4326"); auto vr = render::buildVoxelFromScatters(profs, scs, crs, frame); // 灰底使半透明体绘制可见(白底会冲淡)。3D:体素 + 帘面(同系)核对 profile1 片贴合帘面。 vtkNew ren; ren->SetBackground(0.22, 0.22, 0.26); if (vr.valid()) ren->AddVolume(vr.volume); auto curt = render::buildCurtain(g, cs, frame); // 原始米(不夸张)与体素同系 if (curt) ren->AddActor(curt); render::applyFree3D(ren); renderToPng(ren, (dir + "verify_voxel_3d.png").c_str(), 700, 500); // 俯视:体素 footprint 应呈两测线相交的"X"(十字片)。 vtkNew renTop; renTop->SetBackground(0.22, 0.22, 0.26); if (vr.valid()) renTop->AddVolume(vr.volume); render::applyTop2D(renTop); renderToPng(renTop, (dir + "verify_voxel_top.png").c_str(), 600, 500); int dims[3] = {0, 0, 0}; if (vr.valid()) vr.image->GetDimensions(dims); std::printf("VOXEL valid=%d dims=%dx%dx%d pts=%zu\n", vr.valid() ? 1 : 0, dims[0], dims[1], dims[2], profs[0].v.size() + profs[1].v.size()); // [实测诊断] 体绘制实际渲染模式 + OpenGL 渲染器(查是否软件渲染/CPU 回退)。 if (vr.valid()) { vtkNew rd; rd->AddVolume(vr.volume); vtkNew rwd; rwd->SetOffScreenRendering(1); rwd->AddRenderer(rd); rwd->SetSize(400, 400); rwd->Render(); auto* svm = vtkSmartVolumeMapper::SafeDownCast(vr.volume->GetMapper()); const int mode = svm ? svm->GetLastUsedRenderMode() : -99; std::printf("VOXEL_RENDER_MODE=%d (0=Default,1=RayCast/CPU,2=GPU,3=OSPRay)\n", mode); auto* gl = vtkOpenGLRenderWindow::SafeDownCast(rwd.Get()); if (gl) std::printf("GL_CAPS:\n%s\n", gl->ReportCapabilities()); } } // 7) DEM 地形 + 影像贴图 — GDAL 读 + 重投影到世界系 + warp 面 + 纹理 { auto terr = render::buildTerrain(dir + "dem.tif", dir + "image.tif", frame, 0.0, 1.0); vtkNew ren; ren->SetBackground(0.50, 0.60, 0.72); if (terr) ren->AddActor(terr); render::applyFree3D(ren); renderToPng(ren, (dir + "verify_terrain_3d.png").c_str(), 800, 600); std::printf("TERRAIN actor=%d\n", terr ? 1 : 0); } std::printf("RENDER_VERIFY_DONE grid=%dx%d lat0=%.5f lon0=%.5f\n", g.nx(), g.ny(), lat0, lon0); return 0; }