当前位置: 首页 > news >正文

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 原样副本。

流程大致如下:

  1. 原始数据 _poly_data → 永久保存(ply_poly_data_);

  2. 每次用户操作(Z 拉伸、居中、着色等):

    • 新建 transform;
    • 用原始 polyData 做 transform → 得到新的中间 polyData;
    • 再做着色、Glyph、Mapper;
    • 设置到 actor 上;
  3. 渲染并更新界面。

这种模式在 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

相关文章:

  • maven项目编译时复制xml到classes目录方案
  • 2025-05-28 Python-List-二分法
  • 第一节 51单片机概述
  • 班级管理系统
  • vue 如何对 div 标签 设置assets内本地背景图片
  • 【网络安全】——Modbus协议详解:工业通信的“通用语言”
  • Spring AI 1.0 GA 深度解析:构建企业级AI应用的全栈实践指南
  • Linux 常用命令 -md5sum【计算和校验文件的MD5哈希值】
  • Xamarin入门笔记(Xamarin已经被MAUI取代)
  • 模型量化知识
  • 【数据库系列】bulk_save_objects 与 bulk_insert_mappings 对比
  • 利用openwrt路由器和随身WIFI搭建CPE
  • 使用 Unsloth 快速微调 LLMs 实用指南
  • 【机器学习基础】机器学习入门核心算法:隐马尔可夫模型 (HMM)
  • # Python 语音助手本地的ollama实现
  • Byte(字节)和 k(通常指 kilobit 或 kilobyte)是两种不同的单位,它们的区别和联系
  • 网络协议DHCP
  • Centos7升级openssl
  • Flutter3.22适配运行鸿蒙系统问题记录
  • 数据结构- 10种常见树:二叉树、平衡二叉树、完全二叉树
  • 做软装设计能用到的网站有哪些/seo内部优化包括哪些内容
  • 淘宝网站都是怎么做的吗/百度竞价排名是什么方式
  • 做网站图片怎么找/廊坊百度快照优化排名
  • wordpress用户发文/搜索引擎排名优化方案
  • 超级折扣2WordPress/网站为什么要seo?
  • web作业制作网站源代码/百度站长平台有哪些功能