spike(S3): VTK banded contour 管线跑通真实网格样本(图#18)

vtkImageData(规则栅格)->vtkDataSetSurfaceFilter->vtkBandedPolyDataContourFilter(GenerateContourEdges) 离屏渲染成功, 验证设计§4.3管线选型(B-1/B-2修正)。
颜色精确映射(colorBar非均匀值)留 render 层用 vtkColorTransferFunction 做。
This commit is contained in:
gaozheng 2026-06-07 19:26:58 +08:00
parent bb32e9890c
commit 85e4dbea56
4 changed files with 160 additions and 1 deletions

View File

@ -8,3 +8,5 @@ target_link_libraries(geopro_tests PRIVATE GTest::gtest GTest::gtest_main)
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(geopro_tests) gtest_discover_tests(geopro_tests)
add_subdirectory(spike) # spike S3: banded contour

View File

@ -0,0 +1,15 @@
# Spike S3: banded contour §4.3 线
find_package(nlohmann_json CONFIG REQUIRED)
find_package(VTK REQUIRED COMPONENTS
CommonCore
CommonDataModel
CommonColor
FiltersGeometry
FiltersModeling
RenderingOpenGL2
IOImage
)
add_executable(grid_contour_spike grid_contour_spike.cpp)
target_link_libraries(grid_contour_spike PRIVATE ${VTK_LIBRARIES} nlohmann_json::nlohmann_json)
vtk_module_autoinit(TARGETS grid_contour_spike MODULES ${VTK_LIBRARIES})

View File

@ -0,0 +1,141 @@
// Spike S3: 用真实网格样本验证 VTK banded contour 管线(设计 §4.3 B-1/B-2 修正)。
// vtkImageData(规则栅格) -> vtkDataSetSurfaceFilter -> vtkBandedPolyDataContourFilter
// (GenerateContourEdgesOn 同出 banded 面 + 等值线) -> 离屏渲染 PNG。
// 目视对照 docs/_validate/ref_18_grid.png。
//
// 数据由 build 脚本拷成 ASCII 路径(Windows 下 ifstream 读中文路径会失败):
// D:/dev/spike_data/grid.json, colorbar.json -> spike_grid_contour.png
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include <vtkActor.h>
#include <vtkBandedPolyDataContourFilter.h>
#include <vtkCamera.h>
#include <vtkDataSetSurfaceFilter.h>
#include <vtkDoubleArray.h>
#include <vtkImageData.h>
#include <vtkLookupTable.h>
#include <vtkNew.h>
#include <vtkPNGWriter.h>
#include <vtkPointData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkWindowToImageFilter.h>
using json = nlohmann::json;
static const std::string kDir = "D:/dev/spike_data/";
struct Rgb { unsigned char r, g, b; };
static Rgb parseHexOrRgb(const std::string& s) {
if (!s.empty() && s[0] == '#') {
auto h = [&](int i) { return (unsigned char)std::stoi(s.substr(i, 2), nullptr, 16); };
return Rgb{h(1), h(3), h(5)};
}
int r = 0, g = 0, b = 0;
auto p = s.find('(');
if (p != std::string::npos) std::sscanf(s.c_str() + p, "(%d,%d,%d", &r, &g, &b);
return Rgb{(unsigned char)r, (unsigned char)g, (unsigned char)b};
}
int main() {
// ---- 读网格 ----
json g = json::parse(std::ifstream(kDir + "grid.json"))["data"];
auto x = g["x"].get<std::vector<double>>(); // 100, 规则
auto y = g["y"].get<std::vector<double>>(); // 22, 规则
auto v = g["v"].get<std::vector<std::vector<double>>>(); // [22][100] = [j=y][i=x]
const int nx = (int)x.size(), ny = (int)y.size();
const double dx = x[1] - x[0], dy = y[1] - y[0];
vtkNew<vtkImageData> img;
img->SetDimensions(nx, ny, 1);
img->SetOrigin(x[0], y[0], 0.0);
img->SetSpacing(dx, dy, 1.0);
vtkNew<vtkDoubleArray> sc;
sc->SetName("v");
sc->SetNumberOfTuples((vtkIdType)nx * ny);
for (int j = 0; j < ny; ++j)
for (int i = 0; i < nx; ++i) sc->SetValue((vtkIdType)j * nx + i, v[j][i]); // i 最快
img->GetPointData()->SetScalars(sc);
// ---- 色阶 stops ----
json cb = json::parse(std::ifstream(kDir + "colorbar.json"))["data"]["properties"]["colorBar"];
std::vector<std::pair<double, Rgb>> stops;
for (auto& pr : cb)
stops.emplace_back(std::stod(pr[0].get<std::string>()), parseHexOrRgb(pr[1].get<std::string>()));
std::sort(stops.begin(), stops.end(), [](auto& a, auto& b) { return a.first < b.first; });
const double vmin = stops.front().first, vmax = stops.back().first;
// 离散 LUT阶梯取下界
vtkNew<vtkLookupTable> lut;
lut->SetNumberOfTableValues((vtkIdType)stops.size());
lut->SetTableRange(vmin, vmax);
for (size_t k = 0; k < stops.size(); ++k) {
auto c = stops[k].second;
lut->SetTableValue((vtkIdType)k, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
}
lut->Build();
// ---- 管线imageData -> surface(polydata) -> banded contour(+edges) ----
vtkNew<vtkDataSetSurfaceFilter> surf;
surf->SetInputData(img);
vtkNew<vtkBandedPolyDataContourFilter> banded;
banded->SetInputConnection(surf->GetOutputPort());
std::vector<double> levels;
for (auto& s : stops) levels.push_back(s.first);
banded->SetNumberOfContours((int)levels.size());
for (int k = 0; k < (int)levels.size(); ++k) banded->SetValue(k, levels[k]);
banded->GenerateContourEdgesOn();
banded->SetScalarModeToValue();
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(banded->GetOutputPort());
mapper->SetScalarModeToUseCellData();
mapper->SetLookupTable(lut);
mapper->SetScalarRange(vmin, vmax);
vtkNew<vtkActor> bands;
bands->SetMapper(mapper);
// 等值线边(黑)
vtkNew<vtkPolyDataMapper> edgeMapper;
edgeMapper->SetInputConnection(banded->GetOutputPort(1)); // contour edges
edgeMapper->ScalarVisibilityOff();
vtkNew<vtkActor> edges;
edges->SetMapper(edgeMapper);
edges->GetProperty()->SetColor(0, 0, 0);
edges->GetProperty()->SetLineWidth(0.5);
// ---- 离屏渲染 ----
vtkNew<vtkRenderer> ren;
ren->AddActor(bands);
ren->AddActor(edges);
ren->SetBackground(1, 1, 1);
ren->GetActiveCamera()->ParallelProjectionOn();
ren->ResetCamera();
vtkNew<vtkRenderWindow> rw;
rw->SetOffScreenRendering(1);
rw->AddRenderer(ren);
rw->SetSize(1100, 320);
rw->Render();
vtkNew<vtkWindowToImageFilter> w2i;
w2i->SetInput(rw);
vtkNew<vtkPNGWriter> png;
png->SetFileName((kDir + "spike_grid_contour.png").c_str());
png->SetInputConnection(w2i->GetOutputPort());
png->Write();
std::printf("SPIKE_S3_DONE grid=%dx%d stops=%zu vmin=%.1f vmax=%.1f\n", nx, ny, stops.size(),
vmin, vmax);
return 0;
}

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。", "description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。",
"dependencies": [ "dependencies": [
"gtest" "gtest",
"nlohmann-json"
] ]
} }