图形数据的应用非常广泛,最贴近日常生活的应该是3D游戏,其中每个角色的模型场景等都是图形数据。当然,游戏仅仅是图形数据的一个应用点,图形在CAD(计算机辅助设计)、影视、医学、地质、气象数据建模等领域中均有着广泛的应用。vtkPolyData 是 VTK中常用的数据结构之一,可以表示小到一个点、一条线,大到一个模型、一个场景等。
一、vtkPolyData数据生成与显示
1、vtkPolyData数据源
2、 vtkPolyData数据的创建
用户可以显式地定义 vtkPolyData。首先需要定义一个点集合和一个单元集合,点集合定义了 vtkPolyData的几何结构,而单元集合则定义了点的拓扑结构。每个单元由点的索引而非坐标来定义,这样能够减少数据的存储空间。单元的类型可以是点、三角形、矩形、多边形等基本图元组成。注意:只有定义了单元数据才能显示该图形数据。这需要根据实际情况来定义相应的图元。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolygon.h>
#include <vtkPoints.h>
#include <vtkTriangle.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
points->InsertNextPoint(0.0, 0.0, 0.0);
points->InsertNextPoint(1.0, 0.0, 0.0);
points->InsertNextPoint(1.0, 1.0, 0.0);
points->InsertNextPoint(0.0, 1.0, 0.0);
points->InsertNextPoint(2.0, 0.0, 0.0);
vtkSmartPointer<vtkPolygon> polygon = vtkSmartPointer<vtkPolygon>::New();
polygon->GetPointIds()->SetNumberOfIds(4);
polygon->GetPointIds()->SetId(0, 0);
polygon->GetPointIds()->SetId(1, 1);
polygon->GetPointIds()->SetId(2, 2);
polygon->GetPointIds()->SetId(3, 3);
vtkSmartPointer<vtkTriangle> trianle = vtkSmartPointer<vtkTriangle>::New();
trianle->GetPointIds()->SetId(0, 1);
trianle->GetPointIds()->SetId(1, 2);
trianle->GetPointIds()->SetId(2, 4);
vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
cells->InsertNextCell(polygon);
cells->InsertNextCell(trianle);
vtkSmartPointer<vtkPolyData> polygonPolyData = vtkSmartPointer<vtkPolyData>::New();
polygonPolyData->SetPoints(points);
polygonPolyData->SetPolys(cells);
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(polygonPolyData);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(0.5, 0.5, 0.5);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 480);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataNew");
w.show();
return a.exec();
}
运行效果:
3、 vtkPolyData属性数据
属性数据包括点属性和单元属性,可为 vtkPolyData的点数据和单元数据分别指定属性数据。而属性数据可以是标量,如点的曲率;也可以是向量,如点或者单元的法向量;也可以是张量,主要在流场中较为常见。颜色可以直接作为一种标量属性数据,设置到相应的点或者单元数据中,这也是最直接的一种图形着色方式。
设置 vtkPolyData 点集的颜色数据示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolygon.h>
#include <vtkPoints.h>
#include <vtkTriangle.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkUnsignedCharArray.h>
#include <vtkLookupTable.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkPolyDataMapper.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
points->InsertNextPoint(0.0, 0.0, 0.0);
points->InsertNextPoint(1.0, 0.0, 0.0);
points->InsertNextPoint(1.0, 1.0, 0.0);
points->InsertNextPoint(0.0, 1.0, 0.0);
points->InsertNextPoint(2.0, 0.0, 0.0);
vtkSmartPointer<vtkPolygon> polygon = vtkSmartPointer<vtkPolygon>::New();
polygon->GetPointIds()->SetNumberOfIds(4);
polygon->GetPointIds()->SetId(0, 0);
polygon->GetPointIds()->SetId(1, 1);
polygon->GetPointIds()->SetId(2, 2);
polygon->GetPointIds()->SetId(3, 3);
vtkSmartPointer<vtkTriangle> trianle = vtkSmartPointer<vtkTriangle>::New();
trianle->GetPointIds()->SetId(0, 1);
trianle->GetPointIds()->SetId(1, 2);
trianle->GetPointIds()->SetId(2, 4);
vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
cells->InsertNextCell(polygon);
cells->InsertNextCell(trianle);
vtkSmartPointer<vtkPolyData> polygonPolyData = vtkSmartPointer<vtkPolyData>::New();
polygonPolyData->SetPoints(points);
polygonPolyData->SetPolys(cells);
unsigned char red[3] = {255, 0, 0};
unsigned char green[3] = {0, 255, 0};
unsigned char blue[3] = {0, 0, 255};
vtkSmartPointer<vtkUnsignedCharArray> pointColors = vtkSmartPointer<vtkUnsignedCharArray>::New();
pointColors->SetNumberOfComponents(3);
pointColors->InsertNextTypedTuple(red);
pointColors->InsertNextTypedTuple(green);
pointColors->InsertNextTypedTuple(blue);
pointColors->InsertNextTypedTuple(green);
pointColors->InsertNextTypedTuple(red);
polygonPolyData->GetPointData()->SetScalars(pointColors);
vtkSmartPointer<vtkUnsignedCharArray> cellColors = vtkSmartPointer<vtkUnsignedCharArray>::New();
cellColors->SetNumberOfComponents(3);
cellColors->InsertNextTypedTuple(red);
cellColors->InsertNextTypedTuple(green);
polygonPolyData->GetCellData()->SetScalars(cellColors);
vtkSmartPointer<vtkIntArray> pointfield = vtkSmartPointer<vtkIntArray>::New();
pointfield->SetName("Field");
pointfield->SetNumberOfComponents(3);
pointfield->InsertNextTuple3(1,0,0);
pointfield->InsertNextTuple3(2,0,0);
pointfield->InsertNextTuple3(3,0,0);
pointfield->InsertNextTuple3(4,0,0);
pointfield->InsertNextTuple3(5,0,0);
polygonPolyData->GetPointData()->AddArray(pointfield);
vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
lut->SetNumberOfTableValues(10);
lut->Build();
lut->SetTableValue(0 , 0 , 0 , 0, 1);
lut->SetTableValue(1, 0.8900, 0.8100, 0.3400, 1);
lut->SetTableValue(2, 1.0000, 0.3882, 0.2784, 1);
lut->SetTableValue(3, 0.9608, 0.8706, 0.7020, 1);
lut->SetTableValue(4, 0.9020, 0.9020, 0.9804, 1);
lut->SetTableValue(5, 1.0000, 0.4900, 0.2500, 1);
lut->SetTableValue(6, 0.5300, 0.1500, 0.3400, 1);
lut->SetTableValue(7, 0.9804, 0.5020, 0.4471, 1);
lut->SetTableValue(8, 0.7400, 0.9900, 0.7900, 1);
lut->SetTableValue(9, 0.2000, 0.6300, 0.7900, 1);
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(polygonPolyData);
mapper->SetLookupTable(lut);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(1.0,1.0,1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 480);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataColor");
w.show();
return a.exec();
}
运行效果:
点和单元的属性数据是分别存储在vtkPointData和vtkCellData中。这里需要对标量数据和向量数据的区别做一下分析。这与通常的理解不同,这两种数据本质的区别是向量数据具有方向和模值,而标量数据不具有。如果一个数据(可以是单组分,也可以是多组分)经过一个几何变换后保持不变,那么该数据为一个标量,例如颜色数据,虽然一个颜色包含红绿蓝三个组分,但颜色是不会根据几何变换而发生变化的,因此颜色(RGB)数据是一个标量数据。又如点的法向量,该数据同样有三个组分,却是一个向量数据,因为法向量经过几何变换后(如旋转)会发生改变。因此,不能简单地通过组分的个数来区别标量数据和向量数据。在对属性数据赋值时,也要分清是标量数据还是向量数据,不能将两者混淆,例如将颜色数据设置为向量数据,那么在对图形数据进行几何变换后,颜色数据会发生改变。
设置其他属性数据示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPlaneSource.h>
#include <vtkFloatArray.h>
#include <vtkPolyData.h>
#include <vtkLookupTable.h>
#include <vtkCellData.h>
#include <vtkPolyDataMapper.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPlaneSource> gridSource = vtkSmartPointer<vtkPlaneSource>::New();
gridSource->SetXResolution(3);
gridSource->SetYResolution(3);
gridSource->Update();
vtkSmartPointer<vtkPolyData> grid = gridSource->GetOutput();
vtkSmartPointer<vtkFloatArray> cellScalars = vtkSmartPointer<vtkFloatArray>::New();
vtkSmartPointer<vtkFloatArray> cellVectors = vtkSmartPointer<vtkFloatArray>::New();
cellVectors->SetNumberOfComponents(3);
for (int i = 0; i < 9; i++)
{
cellScalars->InsertNextValue(i + 1);
cellVectors->InsertNextTuple3(0.0, 0.0, 1.0);
}
grid->GetCellData()->SetScalars(cellScalars);
grid->GetCellData()->SetVectors(cellVectors);
//颜色映射表
vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
lut->SetNumberOfTableValues(10);
lut->Build();
lut->SetTableValue(0 , 0 , 0 , 0, 1);
lut->SetTableValue(1, 0.8900, 0.8100, 0.3400, 1);
lut->SetTableValue(2, 1.0000, 0.3882, 0.2784, 1);
lut->SetTableValue(3, 0.9608, 0.8706, 0.7020, 1);
lut->SetTableValue(4, 0.9020, 0.9020, 0.9804, 1);
lut->SetTableValue(5, 1.0000, 0.4900, 0.2500, 1);
lut->SetTableValue(6, 0.5300, 0.1500, 0.3400, 1);
lut->SetTableValue(7, 0.9804, 0.5020, 0.4471, 1);
lut->SetTableValue(8, 0.7400, 0.9900, 0.7900, 1);
lut->SetTableValue(9, 0.2000, 0.6300, 0.7900, 1);
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(grid);//使用网格单元属性数据
// //单元属性数据转点属性数据
// vtkSmartPointer<vtkCellDataToPointData> convert = vtkSmartPointer<vtkCellDataToPointData>::New();
// convert->SetInputData(grid);
// convert->SetPassCellData(true);//在输出数据中保存单元属性数据
// convert->Update();
// mapper->SetInputData((vtkPolyData*)convert->GetOutput());
mapper->SetScalarRange(0, 9);
mapper->SetLookupTable(lut);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(1.0,1.0,1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 480);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataColor");
w.show();
return a.exec();
}
运行效果:
由于可以同时为点和单元设置属性,那么怎样用点或单元控制颜色呢?这就需要使用vtkPolyDataMapper 类的方法。
1)SetScalarModeToDefault()。默认设置。该设置下首先使用点标量数据控制颜色。当点标量数据不可用时,如果存在可用的单元数据,则以单元数据为准。
2)SetScalarModeToUsePointData()。该设置下使用点标量数据着色。如果点标量数据不可用,也不会使用其他标量数据着色。
3)SetScalarModeToUseCellData()。该设置下使用单元标量数据着色。如果单元标量数据不可用,也不会使用其他标量数据着色。
4)SetScalarModeToUsePointFieldData()/SetScalarModeToUseCelFieldData()。该设置下点标量数据和单元标量数据都不会用来着色,而是使用点属性数据中的场数据数组。该模式下通常会结合 ColorByArrayComponent()或者 SelectColorArray()设置相应的数据数组。
在某些情况下,需要对点属性数据和单元属性数据进行转换。这需要用到两个类 vtkCellDataToPointData 和 vtkPointDataToCellData。
//单元属性数据转点属性数据
vtkSmartPointer<vtkCellDataToPointData> convert = vtkSmartPointer<vtkCellDataToPointData>::New();
convert->SetInputData(grid);
convert->SetPassCellData(true);//在输出数据中保存单元属性数据
convert->Update();
mapper->SetInputData((vtkPolyData*)convert->GetOutput());
二、基本的图形操作
VTK 中提供了多种图形的基本操作,其中最简单的是点的欧式距离计算,可以使用vtkMath 进行计算,也可以直接计算向量的模。一些图元类提供了许多可以方便使用的静态函数,如 vkLine 提供了点与线间的距离计算、线与线间的距离计算等;vtkTriangle 提供了面积、外接圆、法向量的计算,点与三角形位置关系的判断等;vkPolygen中提供了法向量、重心、面积的计算、点与多边形位置判断、点与多边形距离、多边形与多边形相交判断等;vtkTetra中实现了四面体体积、重心计算等。
另外,还有一个方法是 vtkMassProperties。这个类可以实现三角网格的表面积和体积的计算,但是要求网格必须是封闭的三角形网格数据。对于非三角形网格,需要先将网格转换为三角形网格。vkTriangleFilter 可以实现多边形网格数据向三角形网格数据的转换。该类的使用非常简单,只要将需要转换的vkPolyData 网格数据设置为输入即可。
用 vtkTriangleFilter 实现多边形数据向三角形网格数据的转换示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkCubeSource.h>
#include <vtkTriangleFilter.h>
#include <vtkMassProperties.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkCubeSource> cubeSource = vtkSmartPointer<vtkCubeSource>::New();
cubeSource->Update();
vtkSmartPointer<vtkTriangleFilter> triFilter = vtkSmartPointer<vtkTriangleFilter>::New();
triFilter->SetInputData(cubeSource->GetOutput());
triFilter->Update();
vtkSmartPointer<vtkMassProperties> massProp = vtkSmartPointer<vtkMassProperties>::New();
massProp->SetInputData(triFilter->GetOutput());
float vol = massProp->GetVolume(); //体积
float area= massProp->GetSurfaceArea(); //表面积
float maxArea = massProp->GetMaxCellArea(); //最大单元面积
float minArea = massProp->GetMinCellArea(); //最小单元面积
qDebug()<<"Volume :"<<vol;
qDebug()<<"Surface Area:"<<area;
qDebug()<<"Max Area :"<<maxArea;
qDebug()<<"Min Area :"<<minArea;
vtkSmartPointer<vtkPolyDataMapper> mapper1 = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper1->SetInputData(cubeSource->GetOutput());
vtkSmartPointer<vtkPolyDataMapper> mapper2 = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper2->SetInputData(triFilter->GetOutput());
vtkSmartPointer<vtkActor> actor1 = vtkSmartPointer<vtkActor>::New();
actor1->SetMapper(mapper1);
actor1->GetProperty()->SetColor(0,1,0);
actor1->GetProperty()->SetEdgeColor(1,0,0);
actor1->GetProperty()->SetEdgeVisibility(1);
vtkSmartPointer<vtkActor> actor2 = vtkSmartPointer<vtkActor>::New();
actor2->SetMapper(mapper2);
actor2->GetProperty()->SetColor(0,1,0);
actor2->GetProperty()->SetEdgeColor(1,0,0);
actor2->GetProperty()->SetEdgeVisibility(1);
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkRenderer> renderer1 = vtkSmartPointer<vtkRenderer>::New();
renderer1->AddActor(actor1);
renderer1->SetBackground(1.0,1.0,1.0);
renderer1->ResetCamera();
renderer1->SetViewport(leftViewport);
vtkSmartPointer<vtkRenderer> renderer2 = vtkSmartPointer<vtkRenderer>::New();
renderer2->AddActor(actor2);
renderer2->SetBackground(1.0,1.0,1.0);
renderer2->ResetCamera();
renderer2->SetViewport(rightViewport);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer1);
renderWindow->AddRenderer(renderer2);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataMassProperty");
w.show();
return a.exec();
}
运行效果:
对于三维网格模型来讲,测地距离也是一种重要的距离度量。与欧式距离不同,一个三维模型上两个点的测地距离是指沿着模型表面两者之间的最短距离。测地距离通常采用Diikstra 算法来近似求解,VTK 中的 vtkDiikstraGraphGeodesicPath 类可实现测地距离的求解。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkDijkstraGraphGeodesicPath.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->Update();
vtkSmartPointer<vtkDijkstraGraphGeodesicPath> dijkstra = vtkSmartPointer<vtkDijkstraGraphGeodesicPath>::New();
dijkstra->SetInputData(sphereSource->GetOutput());
dijkstra->SetStartVertex(0);
dijkstra->SetEndVertex(10);
dijkstra->Update();
vtkSmartPointer<vtkPolyDataMapper> pathMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
pathMapper->SetInputData(dijkstra->GetOutput());
vtkSmartPointer<vtkActor> pathActor = vtkSmartPointer<vtkActor>::New();
pathActor->SetMapper(pathMapper);
pathActor->GetProperty()->SetColor(1,0,0);
pathActor->GetProperty()->SetLineWidth(4);
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(sphereSource->GetOutput());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->AddActor(pathActor);
renderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 480);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataGeodesic");
w.show();
return a.exec();
}
运行效果:
包围盒是指能够包围模型的最小立方体,常常用于模型的碰撞检测中。vtkPolyData中定义了函数 GetBounds()来获取包围盒的参数,即三个坐标轴方向上的最大、最小值。仅仅获取这些数据并不直观,有时候还需显示包围盒。vtkOutlineFilter 则提供一个方便的方法来生成包围盒,其输入为一个 vtkPolyData 模型数据,输出为vtkPolyData 数据,只需要将其作为vtkPolyDataMapper 的输入,建立可视化管线即可显示,示例代码如下:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkOutlineFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->SetCenter(0.0, 0.0, 0.0);
sphereSource->SetRadius(5.0);
sphereSource->Update();
vtkPolyData* sphere = sphereSource->GetOutput();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(sphere);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkOutlineFilter> outline = vtkSmartPointer<vtkOutlineFilter>::New();
outline->SetInputData(sphere);
outline->Update();
vtkSmartPointer<vtkPolyDataMapper> outlineMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
outlineMapper->SetInputData(outline->GetOutput());
vtkSmartPointer<vtkActor> outlineActor = vtkSmartPointer<vtkActor>::New();
outlineActor->SetMapper(outlineMapper);
outlineActor->GetProperty()->SetColor(1,0,0);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->AddActor(outlineActor);
renderer->SetBackground(1,1,1);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataBoundingBox");
w.show();
return a.exec();
}
运行效果:
1、法向量计算
三维平面的法向量是指垂直于该平面的三维向量。曲面在某点P处的法向量为垂直于该点切平面的向量。对于一个网格模型,其每一个点和单元都可以计算一个法向量,在三维计算机图形学中法向量一个重要的应用是光照和阴影计算。对于网格模型,模型是由一定数量的面片(单元)来逼近的,面片越多,则模型越精细,反之,则越粗糙。在计算网格模型的法向量时,单元法向量计算比较简单,可以通过组成每个单元的任意两条边的又乘向量并归一化来表示。而对于点的法向量,则是由所有使用该点的单元法向量的平均值来表示。
VTK中计算法向量的Filter为 vtkPolyDataNormals。该类针对单元为三角形或者多边形类型的 vtkPolyData 数据进行计算。由于法向量分为点法向量和单元法向量,可以通过函数SetComputeCell Normals()和 SetComputePointNormals()来设置需要计算的法向量类型。默认情况下计算点法向量,关闭单元法向量计算。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolyDataReader.h>
#include <vtkPolyDataNormals.h>
#include <vtkMaskPoints.h>
#include <vtkArrowSource.h>
#include <vtkGlyph3D.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyDataReader> reader = vtkSmartPointer<vtkPolyDataReader>::New();
reader->SetFileName("D:/data/fran_cut.vtk");
reader->Update();
vtkSmartPointer<vtkPolyDataNormals> normFilter = vtkSmartPointer<vtkPolyDataNormals>::New();
normFilter->SetInputData(reader->GetOutput());
normFilter->SetComputePointNormals(1);//开启点法向量计算
normFilter->SetComputeCellNormals(0);//关闭单元法向量计算
normFilter->SetAutoOrientNormals(1);
normFilter->SetSplitting(0);
normFilter->Update();
vtkSmartPointer<vtkMaskPoints> mask = vtkSmartPointer<vtkMaskPoints>::New();
mask->SetInputData(normFilter->GetOutput());
mask->SetMaximumNumberOfPoints(300);
mask->RandomModeOn();
mask->Update();
vtkSmartPointer<vtkArrowSource> arrow = vtkSmartPointer<vtkArrowSource>::New();
arrow->Update();
vtkSmartPointer<vtkGlyph3D> glyph = vtkSmartPointer<vtkGlyph3D>::New();
glyph->SetInputData(mask->GetOutput());
glyph->SetSourceData(arrow->GetOutput());//每一点用箭头代替
glyph->SetVectorModeToUseNormal();//设置向量显示模式和法向量一致
glyph->SetScaleFactor(0.01);//设置伸缩比例
glyph->Update();
vtkSmartPointer<vtkPolyDataMapper> originMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
originMapper->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkActor> originActor = vtkSmartPointer<vtkActor>::New();
originActor->SetMapper(originMapper);
vtkSmartPointer<vtkPolyDataMapper> normedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
normedMapper->SetInputData(normFilter->GetOutput());
vtkSmartPointer<vtkActor> normedActor = vtkSmartPointer<vtkActor>::New();
normedActor->SetMapper(normedMapper);
vtkSmartPointer<vtkPolyDataMapper> glyphMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
glyphMapper->SetInputData(glyph->GetOutput());
vtkSmartPointer<vtkActor> glyphActor = vtkSmartPointer<vtkActor>::New();
glyphActor->SetMapper(glyphMapper);
glyphActor->GetProperty()->SetColor(1., 0.,0.);
double originalViewport[4] = {0.0, 0.0, 0.33, 1.0};
double normViewport[4] = {0.33, 0.0, 0.66, 1.0};
double glphViewport[4] = {0.66, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkRenderer> originalRenderer = vtkSmartPointer<vtkRenderer>::New();
originalRenderer->SetViewport(originalViewport);
originalRenderer->AddActor(originActor);
originalRenderer->ResetCamera();
originalRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> normedRenderer = vtkSmartPointer<vtkRenderer>::New();
normedRenderer->SetViewport(normViewport);
normedRenderer->AddActor(normedActor);
normedRenderer->ResetCamera();
normedRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> glyphRenderer = vtkSmartPointer<vtkRenderer>::New();
glyphRenderer->SetViewport(glphViewport);
glyphRenderer->AddActor(glyphActor);
glyphRenderer->AddActor(normedActor);
glyphRenderer->ResetCamera();
glyphRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(normedRenderer);
renderWindow->AddRenderer(glyphRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataNormal");
w.show();
return a.exec();
}
运行效果:
在计算法向量时需要注意一个问题,即法向量的方向。因为对于同一个平面来讲,可以有两个方向完全相反的法向量。一般是根据单元的点顺序,采用右手法则来定义一个平面的法向量方向,因此在计算一个模型的法向量时这个方向会与单元的点顺序相关。必须保持单元的点顺序一致,才能得到合理的法向量。SetConsistency()可以设置自动调整模型的单元点顺序。SetAutoOrientNormals()可以设置自动调整法线方向,而不需要用户来通过 SetFlipNormals()实现,因为许多情况下并不确定是否需要进行反向。
另外,vtkPolyDataNormals默认开启对锐边缘(Sharp Edge)的处理。如果检测到存在锐边缘,则会将其分裂,因此模型的数据可能会发生变化。可以通过vtkPolyDataNormals::SetSplitting()函数关闭该功能。
2、符号化Glyphing
模型的法向量数据是向量数据,因此法向量不能像前面讲到的通过颜色映射来显示。但是可以通过符号化(Glyphing)技术将法向量图形化显示。Glyphing是一种基于图形的可视化技术,这些图像可以是简单的基本图形,如具有方向的锥体,也可以是更加复杂的图像。VTK 中使用vtkGlyph3D类可以实现该功能,并且可以支持Glyph 图形的缩放、着色、设置空间姿态等。使用该类时,需要接受两个输入:一个是需要显示的几何数据点集合;另一个是 Glyph 图形数据,为 vtkPolyData数据。
示例代码:
vtkSmartPointer<vtkMaskPoints> mask = vtkSmartPointer<vtkMaskPoints>::New();
mask->SetInputData(normFilter->GetOutput());
mask->SetMaximumNumberOfPoints(300);
mask->RandomModeOn();
mask->Update();
vtkSmartPointer<vtkArrowSource> arrow = vtkSmartPointer<vtkArrowSource>::New();
arrow->Update();
vtkSmartPointer<vtkGlyph3D> glyph = vtkSmartPointer<vtkGlyph3D>::New();
glyph->SetInputData(mask->GetOutput());
glyph->SetSourceData(arrow->GetOutput());//每一点用箭头代替
glyph->SetVectorModeToUseNormal();//设置向量显示模式和法向量一致
glyph->SetScaleFactor(0.01);//设置伸缩比例
glyph->Update();
vtkSmartPointer<vtkPolyDataMapper> glyphMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
glyphMapper->SetInputData(glyph->GetOutput());
vtkSmartPointer<vtkActor> glyphActor = vtkSmartPointer<vtkActor>::New();
glyphActor->SetMapper(glyphMapper);
glyphActor->GetProperty()->SetColor(1., 0.,0.);
3、曲率计算
曲率是曲面弯曲程度的一种度量,是几何体的一种重要的局部特征。如图所示,要计算曲面上给定点M的曲率,考虑经过 M的法线的一个平面与曲面相交,得到一条二维曲线,称之为曲面在 M点的一条法截线,如图所示中的C法;经过 M点法向量的曲面可以任意旋转,即可得到任意多条法截线,每条法截线会对应一个曲率,取具有最大曲率和最小曲率的两条法截线为主法截线,如图所示中的C1和C2,其对应的曲率为看k1和k2,称为主曲率;高斯曲率等于主曲率的乘积,即k1Xk2;平均曲率等于主曲率k1和k2的平均值,即(k1+k2)/2。
VTK中的 vtkCurvatures类实现了四种计算网格模型点曲率的计算方法。该类接受一个vtkPolyData数据,将计算得到的曲率数据作为网格模型的点的属性数据存入返回的vtkPolyData 中。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolyDataReader.h>
#include <vtkCurvatures.h>
#include <vtkLookupTable.h>
#include <vtkScalarBarActor.h>
#include <vtkPointData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyDataReader> reader = vtkSmartPointer<vtkPolyDataReader>::New();
reader->SetFileName("D:/data/fran_cut.vtk");
reader->Update();
vtkSmartPointer<vtkCurvatures> curvaturesFilter = vtkSmartPointer<vtkCurvatures>::New();
curvaturesFilter->SetInputConnection(reader->GetOutputPort());
curvaturesFilter->SetCurvatureTypeToMean(); //平均曲率
curvaturesFilter->Update();
double scalarRange[2];
curvaturesFilter->GetOutput()->GetScalarRange(scalarRange);
vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
lut->SetHueRange(0.0,0.6);
lut->SetAlphaRange(1.0,1.0);
lut->SetValueRange(1.0,1.0);
lut->SetSaturationRange(1.0,1.0);
lut->SetNumberOfTableValues(256);
lut->SetRange(scalarRange);
lut->Build();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(curvaturesFilter->GetOutput());
mapper->SetLookupTable(lut);
mapper->SetScalarRange(scalarRange);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
vtkSmartPointer<vtkScalarBarActor> scalarBar = vtkSmartPointer<vtkScalarBarActor>::New();
scalarBar->SetLookupTable(mapper->GetLookupTable());
scalarBar->SetTitle(curvaturesFilter->GetOutput()->GetPointData()->GetScalars()->GetName());
scalarBar->SetNumberOfLabels(5);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->AddActor2D(scalarBar);
renderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 480);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataCurvature");
w.show();
return a.exec();
}
运行效果:
- VTK中
vtkCurvatures
类实现了4种网格模型曲率计算方法:- SetCurvatureTypeToMaximum():计算最大主曲率;
- SetCurvatureTypeToMinimum():计算最小主曲率;
- SetCurvatureTypeToGaussian():计算高斯曲率;
- SetCurvatureTypeToMean():计算平均曲率。
三、网络平滑
拉普拉斯平滑是一种网格平滑算法,将每个点用其邻域点的中心来代替,通过不断迭代,得到较为光滑的网格。
- vtkSmoothPolyDataFilter类,实现了拉普拉斯平滑算法,用
SetNumberOfIterations()
控制平滑次数,次数越大,平滑越厉害。BoundarySmoothing
控制是否对边界点平滑;FeatureEdgeSmoothing
控制是否对特征边上的点平滑,通过调用SetFeatureAngle()函数设置特征角阈值。vtkSmoothPolyDataFilter
类通过拉普拉斯不断迭代,模型会不断向网格中心收缩。
- vtkWindowedSincPolyDataFilter类,使用窗口函数Sinc实现网格平滑,能够最下程度避免收缩,使用方法与
vtkSmoothPolyDataFilter
类相同。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolyDataReader.h>
#include <vtkSmoothPolyDataFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyDataReader> reader = vtkSmartPointer<vtkPolyDataReader>::New();
reader->SetFileName("D:/data/fran_cut.vtk");
reader->Update();
vtkSmartPointer<vtkSmoothPolyDataFilter> smoothFilter = vtkSmartPointer<vtkSmoothPolyDataFilter>::New();
smoothFilter->SetInputConnection(reader->GetOutputPort());
smoothFilter->SetNumberOfIterations(200);
smoothFilter->Update();
vtkSmartPointer<vtkPolyDataMapper> inputMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
inputMapper->SetInputConnection(reader->GetOutputPort());
vtkSmartPointer<vtkActor> inputActor = vtkSmartPointer<vtkActor>::New();
inputActor->SetMapper(inputMapper);
vtkSmartPointer<vtkPolyDataMapper> smoothedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
smoothedMapper->SetInputConnection(smoothFilter->GetOutputPort());
vtkSmartPointer<vtkActor> smoothedActor = vtkSmartPointer<vtkActor>::New();
smoothedActor->SetMapper(smoothedMapper);
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->AddActor(inputActor);
leftRenderer->SetBackground(0.8, 0.8, 0.8);
leftRenderer->ResetCamera();
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->AddActor(smoothedActor);
rightRenderer->SetBackground(0.8, 0.8, 0.8);
rightRenderer->SetActiveCamera(leftRenderer->GetActiveCamera());
rightRenderer->ResetCamera();
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataLapLasianSmooth");
w.show();
return a.exec();
}
运行效果: