VTK|Z轴拉伸功能的实现
文章目录
- 为什么不用处理之后的数据而要使用原始数据
- ✅ TL;DR 总结
- 🔍 1. 原始数据 vs. 处理后数据
- ⚠️ 2. 举个例子说明
- 💡 3. 为什么要保存原始数据?
- 🧠 4. 实际工程中的标准做法(尤其图形系统)
- ✅ 小结:你要记住一句话
- 如何实现
- 外部如何调用
- 项目git链接
为什么不用处理之后的数据而要使用原始数据
从概念上和技术细节上解释一下。
✅ TL;DR 总结
因为你对处理后的数据再做“变换”会导致重复叠加、非线性变形和精度丢失,最终结果不可控。而原始数据是唯一可信的“干净基线”。
🔍 1. 原始数据 vs. 处理后数据
数据 | 特性 | 是否适合做 Z 轴拉伸等操作? |
---|---|---|
原始数据 _poly_data | 干净、没有平移、没有 glyph | ✅ 是。变换简单、可重复、稳定 |
处理后的数据(如 transformFilter->GetOutput() ) | 已经平移、着色、glyph,结构和坐标都变了 | ❌ 否。再做拉伸会叠加变形、甚至错误 |
⚠️ 2. 举个例子说明
你在 loadPlyFile()
做了以下操作:
transform->Translate(-center[0], -center[1], -center[2]); // 平移居中
→ ElevationFilter → Glyph → Mapper → Actor
然后你说:“我现在要做 Z 轴拉伸”,你可能会想:
我是不是可以直接拿
vertexGlyphFilter->GetOutput()
然后再拉伸它?
答案是:不能。
如果你这么做:
- 拉伸是基于平移过后的坐标;
- 会让数据“偏离原始参考中心”;
- 你还可能把 Glyph 后的结构搞乱(点已经变成 Vertex 了);
- 再做拉伸时 Z 轴方向不一定是你期望的范围;
- 每次操作都是在“变过的东西上继续变”,误差会叠加,最终模型就“扭曲变形”了。
💡 3. 为什么要保存原始数据?
这是为了让你的数据处理 保持幂等性和可重复性:
- 原始数据是“干净起点”;
- 每次拉伸、旋转、裁剪、着色都可以从头重新开始构建;
- 保证逻辑清晰、结果可预测。
如果不保存原始数据,你就只能“拿已经变形的模型接着变”,逻辑上越来越混乱。
🧠 4. 实际工程中的标准做法(尤其图形系统)
永远保留一份原始模型数据 polyData 原样副本。
流程大致如下:
-
原始数据
_poly_data
→ 永久保存(ply_poly_data_
); -
每次用户操作(Z 拉伸、居中、着色等):
- 新建 transform;
- 用原始 polyData 做 transform → 得到新的中间 polyData;
- 再做着色、Glyph、Mapper;
- 设置到 actor 上;
-
渲染并更新界面。
这种模式在 OpenGL、VTK、三维建模软件(如 CloudCompare、ParaView、MeshLab)中是标准架构。
✅ 小结:你要记住一句话
🧭 永远不要在处理后的结果上“继续做变换”,而是始终从原始数据开始构建流程。
这样你才能保证:
- 模型操作准确;
- 交互逻辑清晰;
- 渲染效果稳定。
如何实现
新增加了一个类对原始数据进行处理
定义了 ModelPipelineBuilder 类,用于封装模型加载和基础处理(如 elevation 和 Z 拉伸等)。
实现代码头文件如下
/*** @file ModelPinelineBuilder.h* @brief 该头文件定义了 ModelPipelineBuilder 类,用于封装模型加载和基础处理(如 elevation 和 Z 拉伸等)。* @details 该类提供了加载 PLY 和 OBJ 格式模型的功能,并且支持对模型进行 Z 轴拉伸、Elevation 着色等处理,还能获取处理后的模型数据和 Actor。* @author 高子奇* @date 2025年5月23日*/
#pragma once#include <vtkSmartPointer.h>
#include <vtkPolyData.h>
#include <vtkActor.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkElevationFilter.h>
#include <vtkOBJReader.h>
#include <vtkPLYReader.h>
#include <QString>/*** @class ModelPipelineBuilder* @brief 封装模型加载和基础处理的类,支持 PLY 和 OBJ 格式模型的加载,并能对模型进行 Z 轴拉伸和 Elevation 着色等处理。*/
class ModelPipelineBuilder
{
public:/*** @enum ModelType* @brief 定义支持的模型类型枚举。*/enum class ModelType{UNKNOWN, ///< 未知模型类型PLY, ///< PLY 格式模型OBJ ///< OBJ 格式模型};/*** @brief 构造函数,初始化类的成员变量。*/ModelPipelineBuilder();/*** @brief 加载指定路径的模型文件。* @param filePath 模型文件的路径。* @return 如果模型加载成功返回 true,否则返回 false。*/bool loadModel(const QString &filePath);/*** @brief 设置模型在 Z 轴上的拉伸比例。* @param scale Z 轴的拉伸比例。*/void setZAxisScale(double scale);/*** @brief 获取处理后的模型对应的 Actor。* @return 处理后的模型的 Actor 智能指针。*/vtkSmartPointer<vtkActor> getActor() const;/*** @brief 获取处理后的多边形数据。* @return 处理后的多边形数据的智能指针。*/vtkSmartPointer<vtkPolyData> getProcessedPolyData() const;/*** @brief 获取当前加载模型的类型。* @return 当前加载模型的类型,为 ModelType 枚举值。*/ModelType getModelType() const;/*** @brief 获取用于对模型进行变换的过滤器。* @return 变换过滤器的智能指针。*/vtkSmartPointer<vtkTransformPolyDataFilter> getTransformFilter() const;/*** @brief 获取用于对模型进行 Elevation 着色的过滤器。* @return Elevation 过滤器的智能指针。*/vtkSmartPointer<vtkElevationFilter> getElevationFilter() const;/*** @brief 获取 OBJ 文件的面数据对应的 Actor。* @return OBJ 文件面数据的 Actor 智能指针。*/vtkSmartPointer<vtkActor> getSurfaceActor() const { return surfaceActor_; }/*** @brief 获取 OBJ 文件的线框数据对应的 Actor。* @return OBJ 文件线框数据的 Actor 智能指针。*/vtkSmartPointer<vtkActor> getWireframeActor() const { return wireframeActor_; }/*** @brief 获取 OBJ 文件的点数据对应的 Actor。* @return OBJ 文件点数据的 Actor 智能指针。*/vtkSmartPointer<vtkActor> getPointsActor() const { return pointsActor_; }private:/*** @brief 更新模型处理流程,包括变换、Elevation 着色等操作。* 当模型数据或 Z 轴拉伸比例发生变化时,调用此方法更新处理结果。*/void updatePipeline();// 清空旧状态void resetState();// 变换(中心对齐 + Z拉伸)void applyTransform();// 着色(按 Z 高度生成 scalar)void applyElevationColoring();void setupOBJPipeline();void setupPLYPipeline();private:ModelType modelType_ = ModelType::UNKNOWN; ///< 当前加载模型的类型,默认为未知类型double zScale_ = 1.0; ///< 模型在 Z 轴上的拉伸比例,默认为 1.0vtkSmartPointer<vtkPolyData> originalPolyData_; ///< 原始的多边形数据,即加载的模型数据vtkSmartPointer<vtkPolyData> processedPolyData_; ///< 处理后的多边形数据vtkSmartPointer<vtkActor> actor_; ///< 处理后的模型对应的 ActorvtkSmartPointer<vtkTransformPolyDataFilter> transformFilter_; ///< 用于对模型进行变换的过滤器vtkSmartPointer<vtkElevationFilter> elevationFilter_; ///< 用于对模型进行 Elevation 着色的过滤器// 加载obj文件的点线面数据vtkSmartPointer<vtkPolyData> processedSurfacePolyData_; ///< 处理后的 OBJ 文件面数据vtkSmartPointer<vtkPolyData> processedPointPolyData_; ///< 处理后的 OBJ 文件点数据vtkSmartPointer<vtkActor> surfaceActor_; ///< OBJ 文件面数据对应的 ActorvtkSmartPointer<vtkActor> wireframeActor_; ///< OBJ 文件线框数据对应的 ActorvtkSmartPointer<vtkActor> pointsActor_; ///< OBJ 文件点数据对应的 Actor
};
源文件如下
#include "ModelPinelineBuilder.h"#include <vtkPLYReader.h>
#include <vtkOBJReader.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkElevationFilter.h>
#include <vtkVertexGlyphFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkLookupTable.h>
#include <vtkProperty.h>
#include <qDebug>static vtkSmartPointer<vtkLookupTable> createJetLookupTable(double min, double max)
{auto lut = vtkSmartPointer<vtkLookupTable>::New();lut->SetTableRange(min, max);lut->SetHueRange(0.66667, 0.0); // Jet 色带:蓝到红lut->Build();return lut;
}ModelPipelineBuilder::ModelPipelineBuilder()
{actor_ = vtkSmartPointer<vtkActor>::New();
}bool ModelPipelineBuilder::loadModel(const QString &filePath)
{std::string ext = filePath.section('.', -1).toLower().toStdString();if (ext == "ply"){auto reader = vtkSmartPointer<vtkPLYReader>::New();reader->SetFileName(filePath.toStdString().c_str());reader->Update();originalPolyData_ = reader->GetOutput();modelType_ = ModelType::PLY;}else if (ext == "obj"){auto reader = vtkSmartPointer<vtkOBJReader>::New();reader->SetFileName(filePath.toStdString().c_str());reader->Update();originalPolyData_ = reader->GetOutput();modelType_ = ModelType::OBJ;}else{modelType_ = ModelType::UNKNOWN;return false;}if (!originalPolyData_ || originalPolyData_->GetNumberOfPoints() == 0)return false;setZAxisScale(1.0); // 默认拉伸为 1.0return true;
}void ModelPipelineBuilder::setZAxisScale(double scale)
{zScale_ = scale;updatePipeline();
}vtkSmartPointer<vtkActor> ModelPipelineBuilder::getActor() const
{return actor_;
}vtkSmartPointer<vtkPolyData> ModelPipelineBuilder::getProcessedPolyData() const
{if (modelType_ == ModelType::OBJ){return processedSurfacePolyData_;}else{return processedPolyData_;}
}ModelPipelineBuilder::ModelType ModelPipelineBuilder::getModelType() const
{return modelType_;
}void ModelPipelineBuilder::updatePipeline()
{// 清空旧状态resetState();// 变换(中心对齐 + Z拉伸)applyTransform();// 着色(按 Z 高度生成 scalar)applyElevationColoring();// 按模型类型构建渲染管线if (modelType_ == ModelType::OBJ)setupOBJPipeline();else if (modelType_ == ModelType::PLY)setupPLYPipeline();
}void ModelPipelineBuilder::resetState()
{transformFilter_ = nullptr;elevationFilter_ = nullptr;processedSurfacePolyData_ = nullptr;processedPointPolyData_ = nullptr;
}void ModelPipelineBuilder::applyTransform()
{double bounds[6];originalPolyData_->GetBounds(bounds);double center[3] = {(bounds[0] + bounds[1]) * 0.5,(bounds[2] + bounds[3]) * 0.5,(bounds[4] + bounds[5]) * 0.5};auto transform = vtkSmartPointer<vtkTransform>::New();transform->Translate(-center[0], -center[1], -center[2]);transform->Scale(1.0, 1.0, zScale_);transformFilter_ = vtkSmartPointer<vtkTransformPolyDataFilter>::New();transformFilter_->SetInputData(originalPolyData_);transformFilter_->SetTransform(transform);transformFilter_->Update();
}void ModelPipelineBuilder::applyElevationColoring()
{// 使用变换后的数据计算 elevationdouble bounds[6];transformFilter_->GetOutput()->GetBounds(bounds);double lowZ = bounds[4];double highZ = bounds[5];elevationFilter_ = vtkSmartPointer<vtkElevationFilter>::New();elevationFilter_->SetInputConnection(transformFilter_->GetOutputPort());elevationFilter_->SetLowPoint(0, 0, lowZ);elevationFilter_->SetHighPoint(0, 0, highZ);elevationFilter_->Update();
}void ModelPipelineBuilder::setupOBJPipeline()
{auto basePolyData = vtkPolyData::SafeDownCast(elevationFilter_->GetOutput());if (!basePolyData)return;double *scalarRange = basePolyData->GetScalarRange();auto lut = createJetLookupTable(scalarRange[0], scalarRange[1]);processedSurfacePolyData_ = basePolyData;// ---------- 面 ----------if (!surfaceActor_)surfaceActor_ = vtkSmartPointer<vtkActor>::New();auto surfaceMapper = vtkSmartPointer<vtkPolyDataMapper>::New();surfaceMapper->SetInputData(basePolyData);surfaceMapper->SetScalarRange(scalarRange);surfaceMapper->SetLookupTable(lut);surfaceMapper->SetColorModeToMapScalars();surfaceMapper->ScalarVisibilityOn();surfaceActor_->SetMapper(surfaceMapper);surfaceActor_->GetProperty()->SetOpacity(1.0);surfaceActor_->GetProperty()->SetRepresentationToSurface();surfaceActor_->GetProperty()->LightingOff(); // 纯色不受光照// ---------- 线 ----------if (!wireframeActor_)wireframeActor_ = vtkSmartPointer<vtkActor>::New();auto wireframeMapper = vtkSmartPointer<vtkPolyDataMapper>::New();wireframeMapper->SetInputData(basePolyData);wireframeMapper->SetScalarRange(scalarRange);wireframeMapper->SetLookupTable(lut);wireframeMapper->SetColorModeToMapScalars();wireframeMapper->ScalarVisibilityOn();wireframeActor_->SetMapper(wireframeMapper);wireframeActor_->GetProperty()->SetRepresentationToWireframe();wireframeActor_->GetProperty()->SetColor(0.2, 0.2, 0.2);wireframeActor_->GetProperty()->SetLineWidth(1.0);wireframeActor_->GetProperty()->LightingOff();// ---------- 点 ----------auto glyphFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New();glyphFilter->SetInputData(basePolyData);glyphFilter->Update();auto outputPolyData = glyphFilter->GetOutput();if (!outputPolyData || outputPolyData->GetNumberOfPoints() == 0)return;processedPointPolyData_ = vtkSmartPointer<vtkPolyData>::New();processedPointPolyData_->ShallowCopy(outputPolyData);if (!pointsActor_)pointsActor_ = vtkSmartPointer<vtkActor>::New();auto pointsMapper = vtkSmartPointer<vtkPolyDataMapper>::New();pointsMapper->SetInputData(processedPointPolyData_);pointsMapper->SetScalarRange(scalarRange);pointsMapper->SetLookupTable(lut);pointsMapper->SetColorModeToMapScalars();pointsMapper->ScalarVisibilityOn();pointsActor_->SetMapper(pointsMapper);pointsActor_->GetProperty()->SetRepresentationToPoints();pointsActor_->GetProperty()->SetPointSize(1.0);pointsActor_->GetProperty()->LightingOff();// 设置主 actor(用于 bbox、clipper)actor_ = surfaceActor_;
}void ModelPipelineBuilder::setupPLYPipeline()
{auto glyphFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New();glyphFilter->SetInputConnection(elevationFilter_->GetOutputPort());glyphFilter->Update();auto polyData = glyphFilter->GetOutput();if (!polyData || polyData->GetNumberOfPoints() == 0)return;processedPolyData_ = vtkSmartPointer<vtkPolyData>::New();processedPolyData_->ShallowCopy(polyData);auto scalarRange = processedPolyData_->GetScalarRange();auto lut = createJetLookupTable(scalarRange[0], scalarRange[1]);auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();mapper->SetInputData(processedPolyData_);mapper->SetScalarRange(scalarRange);mapper->SetLookupTable(lut);mapper->SetColorModeToMapScalars();mapper->ScalarVisibilityOn();actor_->SetMapper(mapper);actor_->GetProperty()->SetRepresentationToPoints();actor_->GetProperty()->SetPointSize(1.0);actor_->GetProperty()->LightingOff(); // 确保无光照影响
}vtkSmartPointer<vtkTransformPolyDataFilter> ModelPipelineBuilder::getTransformFilter() const
{return transformFilter_;
}vtkSmartPointer<vtkElevationFilter> ModelPipelineBuilder::getElevationFilter() const
{return elevationFilter_;
}
外部如何调用
可以根据需求灵活修改相关接口,需要数据从该类里获取
在头文件定义
#include "ModelPinelineBuilder.h"
std::unique_ptr<ModelPipelineBuilder> model_pinpeline_builder_; // 模型构建器;
在源文件调用
model_pinpeline_builder_ = std::make_unique<ModelPipelineBuilder>();void ThreeDimensionalDisplayPage::loadModelByExtension(const QString &filePath)
{if (!QFileInfo::exists(filePath)){qDebug() << "File does not exist: " << filePath;return;}if (!model_pinpeline_builder_->loadModel(filePath)){qDebug() << "Failed to load model: " << filePath;return;}renderer_->RemoveAllViewProps();renderer_->SetBackground(0.5, 0.5, 0.5); // 可选:统一背景色if (model_pinpeline_builder_->getModelType() == ModelPipelineBuilder::ModelType::PLY){ply_point_actor_ = model_pinpeline_builder_->getActor();renderer_->AddActor(ply_point_actor_);// 设置切面控制器meshSliceController_->SetOriginalActor(ply_point_actor_);meshSliceController_->UpdatePolyData(model_pinpeline_builder_->getProcessedPolyData());// BoxClipper 设置boxClipper_->SetInputDataAndReplaceOriginal(model_pinpeline_builder_->getProcessedPolyData(),ply_point_actor_);}else if (model_pinpeline_builder_->getModelType() == ModelPipelineBuilder::ModelType::OBJ){// 添加 OBJ 专属的线框、点、面 actorsurfaceActor_ = model_pinpeline_builder_->getSurfaceActor();wireframeActor_ = model_pinpeline_builder_->getWireframeActor();pointsActor_ = model_pinpeline_builder_->getPointsActor();if (surfaceActor_){surfaceActor_->SetVisibility(is_surface_visible_);renderer_->AddActor(surfaceActor_);}if (wireframeActor_){wireframeActor_->SetVisibility(is_wireframe_visible_);renderer_->AddActor(wireframeActor_);}if (pointsActor_){pointsActor_->SetVisibility(is_points_visible_);renderer_->AddActor(pointsActor_);}meshSliceController_->SetOriginalActor(surfaceActor_);meshSliceController_->UpdatePolyData(model_pinpeline_builder_->getProcessedPolyData());// BoxClipper 设置boxClipper_->SetInputDataAndReplaceOriginal(model_pinpeline_builder_->getProcessedPolyData(),surfaceActor_);}// 添加 BoundingBoxaddBoundingBox(model_pinpeline_builder_->getProcessedPolyData());boxClipper_enabled_ = false;// 重设相机、刷新渲染器renderer_->ResetCamera();renderWindow_->Render();// 比例尺处理if (scaleBarController_){scaleBarController_->ReAddToRenderer();scaleBarController_->UpdateScaleBar(); // 主动触发更新比例尺显示}// 重新添加测量控件的 2D actorif (measurementController_){measurementController_->ReAddActorsToRenderer();}m_pScene->update(); // 最后刷新界面
}
项目git链接
gitee:https://gitee.com/strange-tree-qian/vtktest
github:https://github.com/qishuqian666/project-vtk-test