feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
4 changed files with 137 additions and 15 deletions
Showing only changes of commit b362156364 - Show all commits

View File

@ -6,6 +6,7 @@
#include <vtkColorTransferFunction.h>
#include <vtkDoubleArray.h>
#include <vtkNew.h>
#include <vtkShortArray.h>
#include <vtkSmartVolumeMapper.h>
#include <vtkPiecewiseFunction.h>
#include <vtkPointData.h>
@ -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<vtkVolume> assembleVolume(vtkImageData* img,
vtkColorTransferFunction* color,
vtkPiecewiseFunction* opacity)
{
// SmartVolumeMapper有 GPU 走 GPU ray cast否则自动回退 CPU避免无 GPU 时卡死/失败。
vtkNew<vtkSmartVolumeMapper> mapper;
mapper->SetInputData(img);
// 全程统一全质量(GPU 足够快, 实测 ~7ms/帧):关掉交互降采样, 避免"停手补高清"那一帧突跳停顿。
mapper->SetAutoAdjustSampleDistances(0);
mapper->SetInteractiveAdjustSampleDistances(0);
vtkNew<vtkVolumeProperty> prop;
prop->SetColor(color);
prop->SetScalarOpacity(opacity);
prop->SetInterpolationTypeToLinear();
prop->ShadeOff();
auto volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(mapper);
volume->SetProperty(prop);
return volume;
}
} // namespace
vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
@ -71,23 +96,65 @@ vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
opacity->AddPoint(vmin, 0.0);
opacity->AddPoint(vmax, kMaxOpacity);
// SmartVolumeMapper有 GPU 走 GPU ray cast否则自动回退 CPU避免无 GPU 时卡死/失败。
vtkNew<vtkSmartVolumeMapper> mapper;
mapper->SetInputData(img);
// 全程统一全质量(GPU 足够快, 实测 ~7ms/帧):关掉交互降采样, 避免"停手补高清"那一帧突跳停顿。
mapper->SetAutoAdjustSampleDistances(0);
mapper->SetInteractiveAdjustSampleDistances(0);
return assembleVolume(img, color, opacity);
}
vtkNew<vtkVolumeProperty> prop;
prop->SetColor(color);
prop->SetScalarOpacity(opacity);
prop->SetInterpolationTypeToLinear();
prop->ShadeOff();
vtkSmartPointer<vtkVolume> 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<vtkImageData>& outImage)
{
const int nx = vol.nx(), ny = vol.ny(), nz = vol.nz();
auto volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(mapper);
volume->SetProperty(prop);
return volume;
// vmin/vmax 退化兜底,避免传递函数区间为零。
if (vminPhys >= vmaxPhys) vmaxPhys = vminPhys + 1.0;
auto img = vtkSmartPointer<vtkImageData>::New();
img->SetDimensions(nx, ny, nz);
img->SetOrigin(ox, oy, oz);
img->SetSpacing(dx, dy, dz);
vtkNew<vtkShortArray> sc;
sc->SetName("v");
sc->SetNumberOfTuples(static_cast<vtkIdType>(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<vtkIdType>(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<double>(qmin);
const double qmaxD = static_cast<double>(qmax);
// 颜色传递函数:对每个量化级 qv物理值 phys=q.toPhys(qv),用 double 版相同方式取色。
vtkNew<vtkColorTransferFunction> color;
for (int t = 0; t < kTransferSamples; ++t) {
const double qd = qminD + (qmaxD - qminD) * t / (kTransferSamples - 1);
const auto qvLevel = static_cast<std::int16_t>(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<vtkPiecewiseFunction> opacity;
opacity->AddPoint(static_cast<double>(geopro::core::ScalarVolumeI16::kBlank), 0.0);
opacity->AddPoint(qminD, 0.0);
opacity->AddPoint(qmaxD, kMaxOpacity);
return assembleVolume(img, color, opacity);
}
vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,

View File

@ -4,6 +4,7 @@
#include <vtkImageData.h>
#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<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
double vmin, double vmax,
vtkSmartPointer<vtkImageData>& 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<vtkVolume> 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<vtkImageData>& outImage);
} // namespace geopro::render

View File

@ -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_voxelbuildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) + dims
target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp)
# dd_voxel int16buildVoxelI16(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)
# CurtainbuildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) actor + =nx*ny

View File

@ -0,0 +1,40 @@
#include <gtest/gtest.h>
#include <vtkImageData.h>
#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<vtkImageData> 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<short*>(img->GetScalarPointer(1, 1, 1)), 10);
EXPECT_EQ(*static_cast<short*>(img->GetScalarPointer(0, 0, 0)),
core::ScalarVolumeI16::kBlank);
}