参考之前记录的标量颜色映射,很多时候为了搞清楚标量在空间或者平面上的分布趋势,通常会通过提取等值面或者等值线的方式将这些数据可视化,这里会记录怎么使用VTK实现这样的功能。
工欲善其事必先利其器,实现这个功能的核心是vtkContourFIlter类,它提供了等值线(等值面)提取算法,只需要准备好数据,然后设置好轮廓参数,就可以实现了。关于过滤器的使用可以参考之前关于VTK渲染流水线的博客。
vtkContourFilter
这个过滤器的功能是从众多的标量值中找出标量值相等的位置,把这些位置连成线或者生成面,当然这个过程中会使用已有的标量值进行插值。它可以将任何数据集作为输入并输出等值面或者等值线(这里可以参考在标量颜色映射中对vtkDataSet类的介绍),输出的数据的形式却决于输入数据的维度,如果是3D单元数据那么输出的就是等值面,2D单元输出等值线,1D单元则输出一些点。
要使用这个过滤器必须为过滤器设置轮廓值(等值面的值),可以使用SetValue()来指定每一个轮廓值,或者使用GenerateValues()来生成一些均匀分布的轮廓值。
生成轮廓值需要耗费大量的时间,可以使用vtkScalarTree来加速这个操作,标量树用于快速定位包好等值表面的单元,尤其是对需要提取多个轮廓的时候效果极佳,如果要使用标量树需要调用UseScalarTreeOn()开启。
等值线提取
将之前标量颜色映射的示例拿过来,增加一个vtkContourFIlter。
//为三角形准备三个顶点 vtkNew<vtkPoints> points; points->InsertNextPoint(1, 0, 0); points->InsertNextPoint(0, 0, 0); points->InsertNextPoint(0, 1, 0); //生成点的标量数据 vtkNew<vtkIntArray> scalar; scalar->InsertNextTuple1(30); scalar->InsertNextTuple1(100); scalar->InsertNextTuple1(50); //使用顶点构建一个三角形 点使用ID传入 vtkNew<vtkTriangle> triangle; triangle->GetPointIds()->SetId(0, 0); triangle->GetPointIds()->SetId(1, 1); triangle->GetPointIds()->SetId(2, 2); //将三角形传入单元集 vtkNew<vtkCellArray> cellArray; cellArray->InsertNextCell(triangle); //为多边形数据添加单元集和点集 vtkNew<vtkPolyData> polydata; polydata->SetPolys(cellArray); polydata->SetPoints(points); //为点数据添加标量 polydata->GetPointData()->SetScalars(scalar); //添加一个过滤器 vtkNew<vtkContourFilter> contourFilter; contourFilter->SetInputData(polydata); //设置等值线的值 contourFilter->SetValue(0, 50); contourFilter->SetComputeScalars(true); contourFilter->Update(); vtkNew<vtkPolyDataMapper> mapper; mapper->SetInputData(polydata); mapper->SetScalarRange(scalar->GetRange()); mapper->Update(); vtkNew<vtkPolyDataMapper> contourMapper; contourMapper->SetInputData(contourFilter->GetOutput()); contourMapper->ScalarVisibilityOff(); //为映射器添加一个颜色映射表 //不添加颜色映射表会得到一个默认的颜色表 //如果需要自定义颜色表需要调用SetTableValue()设置 vtkNew<vtkLookupTable> lookupTable; lookupTable->SetTableRange(scalar->GetRange()); lookupTable->Build(); mapper->SetLookupTable(lookupTable); contourMapper->SetLookupTable(lookupTable); vtkNew<vtkScalarBarActor> barActor; barActor->SetLookupTable(lookupTable); barActor->SetNumberOfLabels(6); vtkNew<vtkActor> actor; actor->SetMapper(mapper); actor->GetProperty()->SetColor(255, 0, 0); vtkNew<vtkActor> contourActor; contourActor->SetMapper(contourMapper); contourActor->GetProperty()->SetColor(0,0,0); vtkNew<vtkRenderer> renderer; vtkNew<vtkRenderWindow> window; window->AddRenderer(renderer); vtkNew<vtkRenderWindowInteractor> interactor; interactor->SetRenderWindow(window); renderer->AddActor(actor); renderer->AddActor(contourActor); renderer->AddActor(barActor); window->Render(); interactor->Start();
等值面轮廓提取
提取等值面与提取等值线的过程如出一辙,首先得提供一个三维点数据与标量数据。
我在网上找到了这么一个函数,据说这个F=0的时候,xyz的解会形成一个小心心。于是我生成了一个40*40*40的三维网格数据,然后每个网格的标量值使用这个函数进行赋值,最后再求F=0时的等值面。
vtkNew<vtkStructuredGrid> structuredGrid; vtkNew<vtkPoints> points; vtkNew<vtkFloatArray> scalars; unsigned int gridSize = 40; //构造一个点云,使用函数为点云构造标量值 for (unsigned int i = 0; i < gridSize; i++) { for (unsigned int j = 0; j < gridSize; j++) { for (unsigned int k = 0; k < gridSize; k++) { points->InsertNextPoint(i, j, k); //实现函数 (x^2 + (9/4) y^2 + z^2 - 1)^3 - x^2*z^3 - (9/80) y^2*z^3 =F, {x, -2, 2}, {y, -2, 2}, {z, -2, 2} double x = (i - (gridSize / 2.0)) / 10.0; double y = (j - (gridSize / 2.0)) / 10.0; double z = (k - (gridSize / 2.0)) / 10.0; scalars->InsertNextTuple1( pow(x*x + (9.0 / 4.0)*y*y + z*z- 1.0,3) - x*x*pow(z,3) - (9.0 / 80.0)*y*y * pow(z,3) ); } } } //设置网格大小 structuredGrid->SetDimensions(gridSize, gridSize, gridSize); //附加数据 structuredGrid->SetPoints(points); structuredGrid->GetPointData()->SetScalars(scalars); //设置映射器 vtkNew<vtkDataSetMapper> gridMapper; gridMapper->SetInputData(structuredGrid.Get()); gridMapper->ScalarVisibilityOff(); vtkNew<vtkActor> gridActor; gridActor->SetMapper(gridMapper.Get()); gridActor->GetProperty()->SetOpacity(0.3); //设置等值线过滤器 vtkNew<vtkContourFilter> contourFilter; contourFilter->SetInputData(structuredGrid.Get()); //为标量为0时取一个等值面 contourFilter->SetValue(0, 0); contourFilter->Update(); //创建颜色表 这里只输入颜色值的数量,然后自动生成表 vtkNew<vtkLookupTable> lut; lut->SetTableRange(scalars->GetRange()); lut->Build(); //为生成的等值面添加法向 vtkNew<vtkPolyDataNormals> normals; normals->SetInputConnection(contourFilter->GetOutputPort()); normals->SetFeatureAngle(45); normals->Update(); vtkNew<vtkDataSetMapper> contourMaper; contourMaper->SetInputData(normals->GetOutput()); contourMaper->SetLookupTable(lut); contourMaper->SetScalarRange(scalars->GetRange()); vtkNew<vtkActor> contourActor; contourActor->SetMapper(contourMaper); vtkNew<vtkRenderer> renderer; vtkNew<vtkRenderWindow> renderWindow; renderWindow->AddRenderer(renderer); vtkNew<vtkScalarBarActor> barActor; barActor->SetLookupTable(lut); vtkNew<vtkRenderWindowInteractor> renderWindowInteractor; renderWindowInteractor->SetRenderWindow(renderWindow); renderer->AddActor(contourActor); renderer->AddActor(barActor); renderer->AddActor(gridActor); renderWindow->Render(); renderWindowInteractor->Start();
哇!真的有小心心欸。
赶紧送给对象,什么?你没有对象?那感觉new一个吧。
小结
以上,即是记录实现等值线面的提取的全过程,过程十分简单,不过有些细节需要十分注意。
- 映射器一定要设置好标量范围,否则无法正常完成标量值的颜色映射
- 三维等值面提取时一定要重新计算多边形的法向
第二点需要特别注意,我在写示例时就遇到了这个问题,轮廓提取之后的多边形是没有法向信息的,这就会导致它不能正常的反射光照,即生成的等值面颜色不对,或者直接和背景黑色融为一体,所以一定要重新计算法向!!
文章评论