From b36215636423c933e4ba9b10da4c692f1ebe676c Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 11:12:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(render):=20VoxelActor=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20buildVoxelI16=20=E9=87=8F=E5=8C=96=E5=9F=9F=E4=BD=93?= =?UTF-8?q?=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit int16 量化体经 vtkShortArray 填 vtkImageData,vtkSmartVolumeMapper GPU 体绘制。 传递函数在量化域取控制点:qmin/qmax=q.toQ(vmin/vmaxPhys),颜色对每量化级用 q.toPhys 反查物理值再经 ColorScale 取色;kBlank→不透明度0(透明)。抽 assembleVolume 公用 mapper/property 配置,double 版 buildVoxel 行为不变。附无窗冒烟测试。 --- src/render/actors/VoxelActor.cpp | 97 ++++++++++++++++++++++----- src/render/actors/VoxelActor.hpp | 13 ++++ tests/CMakeLists.txt | 2 + tests/render/test_voxel_i16_smoke.cpp | 40 +++++++++++ 4 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 tests/render/test_voxel_i16_smoke.cpp diff --git a/src/render/actors/VoxelActor.cpp b/src/render/actors/VoxelActor.cpp index 4dfd7ef..8c50e59 100644 --- a/src/render/actors/VoxelActor.cpp +++ b/src/render/actors/VoxelActor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,30 @@ constexpr double kMaxOpacity = 0.15; // NaN/留空格的哨兵:落在 [vmin,vmax] 之外,传递函数把它映射为完全透明。 double sentinel(double vmin) { return vmin - 1.0; } +// double/int16 两版公用的 mapper+property+volume 组装(行为与原 double 版一致)。 +vtkSmartPointer assembleVolume(vtkImageData* img, + vtkColorTransferFunction* color, + vtkPiecewiseFunction* opacity) +{ + // SmartVolumeMapper:有 GPU 走 GPU ray cast,否则自动回退 CPU,避免无 GPU 时卡死/失败。 + vtkNew mapper; + mapper->SetInputData(img); + // 全程统一全质量(GPU 足够快, 实测 ~7ms/帧):关掉交互降采样, 避免"停手补高清"那一帧突跳停顿。 + mapper->SetAutoAdjustSampleDistances(0); + mapper->SetInteractiveAdjustSampleDistances(0); + + vtkNew prop; + prop->SetColor(color); + prop->SetScalarOpacity(opacity); + prop->SetInterpolationTypeToLinear(); + prop->ShadeOff(); + + auto volume = vtkSmartPointer::New(); + volume->SetMapper(mapper); + volume->SetProperty(prop); + return volume; +} + } // namespace vtkSmartPointer buildVoxel(const geopro::core::ScalarVolume& vol, @@ -71,23 +96,65 @@ vtkSmartPointer buildVoxel(const geopro::core::ScalarVolume& vol, opacity->AddPoint(vmin, 0.0); opacity->AddPoint(vmax, kMaxOpacity); - // SmartVolumeMapper:有 GPU 走 GPU ray cast,否则自动回退 CPU,避免无 GPU 时卡死/失败。 - vtkNew mapper; - mapper->SetInputData(img); - // 全程统一全质量(GPU 足够快, 实测 ~7ms/帧):关掉交互降采样, 避免"停手补高清"那一帧突跳停顿。 - mapper->SetAutoAdjustSampleDistances(0); - mapper->SetInteractiveAdjustSampleDistances(0); + return assembleVolume(img, color, opacity); +} - vtkNew prop; - prop->SetColor(color); - prop->SetScalarOpacity(opacity); - prop->SetInterpolationTypeToLinear(); - prop->ShadeOff(); +vtkSmartPointer buildVoxelI16(const geopro::core::ScalarVolumeI16& vol, + const geopro::core::Quant& q, + const geopro::core::ColorScale& cs, + double ox, double oy, double oz, + double dx, double dy, double dz, + double vminPhys, double vmaxPhys, + vtkSmartPointer& outImage) +{ + const int nx = vol.nx(), ny = vol.ny(), nz = vol.nz(); - auto volume = vtkSmartPointer::New(); - volume->SetMapper(mapper); - volume->SetProperty(prop); - return volume; + // vmin/vmax 退化兜底,避免传递函数区间为零。 + if (vminPhys >= vmaxPhys) vmaxPhys = vminPhys + 1.0; + + auto img = vtkSmartPointer::New(); + img->SetDimensions(nx, ny, nz); + img->SetOrigin(ox, oy, oz); + img->SetSpacing(dx, dy, dz); + + vtkNew sc; + sc->SetName("v"); + sc->SetNumberOfTuples(static_cast(nx) * ny * nz); + // 点序 i 最快、j 次之、k 最慢(匹配 vtkImageData 与 ScalarVolumeI16::idx)。 + // kBlank 原样保留,由量化域传递函数映射为透明。 + for (int k = 0; k < nz; ++k) + for (int j = 0; j < ny; ++j) + for (int i = 0; i < nx; ++i) { + const std::int16_t qv = vol.at(i, j, k); + const vtkIdType id = (static_cast(k) * ny + j) * nx + i; + sc->SetValue(id, qv); + } + img->GetPointData()->SetScalars(sc); + outImage = img; + + // 传递函数在量化域取(标量本身是 int16 量化值)。 + const std::int16_t qmin = q.toQ(vminPhys); + const std::int16_t qmax = q.toQ(vmaxPhys); + const double qminD = static_cast(qmin); + const double qmaxD = static_cast(qmax); + + // 颜色传递函数:对每个量化级 qv,物理值 phys=q.toPhys(qv),用 double 版相同方式取色。 + vtkNew color; + for (int t = 0; t < kTransferSamples; ++t) { + const double qd = qminD + (qmaxD - qminD) * t / (kTransferSamples - 1); + const auto qvLevel = static_cast(std::lround(qd)); + const double phys = q.toPhys(qvLevel); + const auto c = cs.colorAt(phys); + color->AddRGBPoint(qd, c.r / 255.0, c.g / 255.0, c.b / 255.0); + } + + // 不透明度传递函数(量化域):kBlank → 0(透明);[qmin,qmax] 线性递增到 kMaxOpacity。 + vtkNew opacity; + opacity->AddPoint(static_cast(geopro::core::ScalarVolumeI16::kBlank), 0.0); + opacity->AddPoint(qminD, 0.0); + opacity->AddPoint(qmaxD, kMaxOpacity); + + return assembleVolume(img, color, opacity); } vtkSmartPointer buildVoxel(const geopro::core::ScalarVolume& vol, diff --git a/src/render/actors/VoxelActor.hpp b/src/render/actors/VoxelActor.hpp index 4a840b9..d16c003 100644 --- a/src/render/actors/VoxelActor.hpp +++ b/src/render/actors/VoxelActor.hpp @@ -4,6 +4,7 @@ #include #include "model/Field.hpp" #include "model/ColorScale.hpp" +#include "model/ScalarVolumeI16.hpp" namespace geopro::render { // 把 core 规则标量体(IDW 输出,含 NaN 留空)转 vtkImageData,再建 GPU 光线投射体绘制。 @@ -23,4 +24,16 @@ vtkSmartPointer buildVoxel(const geopro::core::ScalarVolume& vol, double vmin, double vmax, vtkSmartPointer& outImage); +// int16 量化体 → vtkImageData(vtkShortArray) → GPU 体绘制(与 double 版并列)。 +// 传函在量化域取:qmin=q.toQ(vminPhys)、qmax=q.toQ(vmaxPhys);kBlank → 不透明度 0(透明)。 +// color 按 ColorScale 在 [vminPhys,vmaxPhys] 采样(对每个量化级 qv 用 q.toPhys(qv) 查色)。 +// outImage 暴露内部 vtkImageData,供建交互切片 widget。返回 vtkVolume。 +vtkSmartPointer buildVoxelI16(const geopro::core::ScalarVolumeI16& vol, + const geopro::core::Quant& q, + const geopro::core::ColorScale& cs, + double ox, double oy, double oz, + double dx, double dy, double dz, + double vminPhys, double vmaxPhys, + vtkSmartPointer& outImage); + } // namespace geopro::render diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 17cf11c..ece1302 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -91,6 +91,8 @@ target_sources(geopro_tests PRIVATE render/test_color_lut.cpp) target_sources(geopro_tests PRIVATE render/test_contour_bands.cpp) # dd_voxel:buildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) 构建不崩 + dims 正确。 target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp) +# dd_voxel int16:buildVoxelI16(ScalarVolumeI16->vtkImageData(vtkShortArray)) 类型/dims/值/blank。 +target_sources(geopro_tests PRIVATE render/test_voxel_i16_smoke.cpp) # dd_voxel 回归:buildVoxelFromScatters(散点 projX/Y -EPSG:4547-> 世界系 + IDW) 配准+充填(需 PROJ_DATA)。 target_sources(geopro_tests PRIVATE render/test_voxel_register.cpp) # Curtain:buildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) 非空 actor + 点数=nx*ny。 diff --git a/tests/render/test_voxel_i16_smoke.cpp b/tests/render/test_voxel_i16_smoke.cpp new file mode 100644 index 0000000..bc07c9b --- /dev/null +++ b/tests/render/test_voxel_i16_smoke.cpp @@ -0,0 +1,40 @@ +#include + +#include + +#include "actors/VoxelActor.hpp" +#include "model/ColorScale.hpp" +#include "model/ScalarVolumeI16.hpp" + +using namespace geopro; + +// buildVoxelI16 无窗冒烟:int16 量化体 → vtkImageData(vtkShortArray)。 +// 仅验 image 构造/类型(VTK_SHORT)/dims/逐体素值/blank 原样保留;不实际 GPU 渲染。 +TEST(VoxelActorI16, BuildsShortImageWithBlank) { + core::ScalarVolumeI16 v(2, 2, 2); + for (auto& x : v.data()) x = 10; + v.at(0, 0, 0) = core::ScalarVolumeI16::kBlank; + + core::Quant q{1.0, 0.0}; + + core::ColorScale cs; + cs.addStop(0.0, core::Rgba{0, 0, 255, 255}); + cs.addStop(20.0, core::Rgba{255, 0, 0, 255}); + + vtkSmartPointer img; + auto vol = render::buildVoxelI16(v, q, cs, 0, 0, 0, 1, 1, 1, 0.0, 20.0, img); + + ASSERT_NE(vol.Get(), nullptr); + ASSERT_NE(img.Get(), nullptr); + EXPECT_EQ(img->GetScalarType(), VTK_SHORT); + + int dims[3]; + img->GetDimensions(dims); + EXPECT_EQ(dims[0], 2); + EXPECT_EQ(dims[1], 2); + EXPECT_EQ(dims[2], 2); + + EXPECT_EQ(*static_cast(img->GetScalarPointer(1, 1, 1)), 10); + EXPECT_EQ(*static_cast(img->GetScalarPointer(0, 0, 0)), + core::ScalarVolumeI16::kBlank); +}