在4.6节里的“Assemblies”一小节里,有以下一段内容:
“注意是如何使用vtkAssembly里的方法AddPart()来建立层次结构的。只要不是自我嵌套,Assembly可以组合成任意层次深度的结构。vtkAssembly是vtkProp3D的子类,但没有与之相关联的Property以及Mapper。因此,vtkAssembly层次结构里的结点必须要包含关于材料的属性(如颜色等)及其他相关的几何信息。一个Actor可以用于多个Assembly(注意以上例子中的coneActor是如何作为一个单独的Actor以及作为Assembly里的一个节点的)。通过渲染器里的方法AddActor()只要加入最顶层的Assembly,而低层次的Assembly不用加入,因为它们会递归加入到渲染器中。”
在世界坐标系中,我们可能需要确定vtkProp3D对象的位置及方向。某些应用程序中,有时希望将数据送入可视化管线进行可视化之前对其做某些空间变换操作。例如,使用平面对数据进行切割(见第98页的“切割”一节)或者裁剪某个对象(见第110页的“数据裁剪”一节)时,在管线里,用于切割的平面必须放置在正确的空间位置,而这不是通过Actor的变换矩阵来完成的。某些对象(特别是程序源对象)需要在空间的指定位置和方向创建,比如,vtkSphereSource类有中心点Center和半径Radius等变量,而vtkPlaneSource则有Origin、Point1和Point2等变量允许用户使用三个点来确定平面的空间位置。然而,大多数类不提供类似的方法来移动数据至适当的位置,在这种情况下,我们必须使用类vtkTransformFilter或者vtkTransformPolyDataFilter将其变换至合适的空间位置中。
vtkTransformFilter以vtkPointSet数据集对象作为输入。抽象类vtkPointSet的数据集子类是以显式的形式来表达点数据,也就是说,使用vtkPoints的实例来存储坐标信息。vtkTransformFilter应用一个变换矩阵到这些点集上,并产生出一个变换后的点集,而数据集结构(即单元拓扑)和属性数据(如标量值、向量值等)则保持不变。vtkTransformPolyDataFilter与vtkTransformFilter工作过程类似,在包含多边形数据的可视化管线中,使用前者则更为方便些。
以下例子使用vtkTransformPolyDataFilter来确定一个3D文本字符串的空间位置(摘自VTK/Examples/DataManipulation/Tcl/marching.tcl,程序如图4-13所示)。关于3D文本字符串的内容可以参考“三维文本标注与vtkFollower”一节。
图4-13在管线里变换数据
#definethe text for the labels vtkVectorText caseLabel caseLabel SetText “Case 12c – 11000101” vtkTransform aLabelTransform aLabelTransform Identity aLabelTransform Translate -.2 0 1.25 aLabelTransform Scale .05 .05 .05 vtkTransformPolyDataFilter labelTransform labelTransform SetTransform aLabelTransform labelTransform SetInputConnection [caseLabel GetOutputPort] vtkPolyDataMapper labelMapper labelMapper SetInputConnection [labelTransform GetOutputPort] vtkActor labelActor labelActor SetMapper labelMapper
需要注意的是vtkTransformPolyDataFilter需要指定一个vtkTransform实例。由前面的章节我们知道,vtkTransform是用于控制空间中Actor的位置及方向,vtkTransform还提供其他的方法,接下来列出该类的一些常用的方法:
以上介绍的最后两个方法告诉我们,变换矩阵的顺序对最终的变换结果有很大的影响。我们建议你多花点时间在这些方法的理解以及矩阵的变换顺序,以求能更加深刻地理解vtkTransform。
对于高级用户来说,可能会使用到扩展的VTK变换层次结构(这部分内容大多数的工作是由David Gobbi完成的)。VTK变换的层次结构(如图19-17所示)提供了一系列的线性与非线性变换。
VTK变换层次结构一个非常棒的特性就是不同类型的变换可以在同一个Filter中使用,以产生不同的结果。例如,vtkTransformPolyDataFilter可以接受vtkAbstractTransform(或其子类)的变换类型作为其输入,包括线性变换、仿射变换(用4×4的矩阵表示)、非线性变换(如vtkThinPlateSplineTransform)。
交互器样式(见第45页的“使用VTK交互器”一节)通常只是控制相机以及提供一些简单的面向键盘和鼠标事件的交互技术。交互器样式在渲染场景中并没有一种表达形式,也就是说,在交互时我们看不见交互器样式到底是什么样子的,用户在使用这些交互器样式时,必须事先知道哪些键盘和鼠标事件是控制哪些操作的。然而,渲染场景中大部分的交互操作都需要直接、简单。比如,如果某条线的端点是可以由用户自由放置的话,那么沿着某条线改变流线的倾斜度,就显得非常容易。
3DWidget就是针对这种功能需求而设计的。与类vtkInteractorStyle类似,3DWidget也是vtkInteractorObserver的子类。换言之,它们会监听由vtkRenderWindowInteractor所调用的事件。我们知道,vtkRenderWindowInteractor可以将基于具体操作系统的事件转换成VTK事件。但是与vtkInteractorStyle不同的是,3DWidget在渲染场景中可以用多种不同的表达形式,图4-14列出了一些VTK可见的3DWidget的样式。
图4-14VTK中可见的一些3D Widget
下面列出一些VTK中可见的比较重要的一些Widget,先简要描述一下这些Widget的特性与功能。需要注意的是,下面所提到的某些概念在本章中尚未详细讲解,读者可以查看第255页里的“交互、Widgets和选择”一章,了解相关的概念以及VTK中所实现的其他的Widget。
虽然每个Widget都提供了不同的功能和API,但是这些3DWidget在创建和使用时都是相似的,其步骤通常为:
1. 初始化Widget;
2. 指定所要监听的vtkRenderWindowInteractor的实例,vtkRenderWindowInteractor会调用Widget要处理的事件。
3. 如果有必要的话,使用命令/观察者模式创建回调函数(见第29页“用户事件、观察者/命令模式”一节),Widget会调用StartInteractionEvent,InteractionEvent和EndInteractionEvent等事件。
4. 大多数Widget还需要放置在渲染场景中,通常需要指定一个vtkProp3D实例,一个数据集或者显式地指定一个包围盒,接着再调用方法PlaceWidget()。
5. 最后,必须激活Widget。缺省情况下,用户按键盘“i”键时,即可以激活Widget并让该Widget在场景中显示出来。
要注意的是,在某个时刻里有可能同时激活多个Widget,这些Widget可以与vtkInteractionStyle实例共同存在而各自的功能不受影响。如果场景中的鼠标事件不是作用在某个Widget上时,就会被vtkInteractorStyle所处理;如果作用于某个Widget时,当然只被该Widget所处理,其他的Widget或者vtkInteractorStyle对这个鼠标事件就是不可见的。(这里的一个例外是类vtkInteractorEventRecorder,该类可以记录用户事件,也可回放事件,这对于录制场景和测试等应用尤其有用)。
以下示例(摘自VTK/Examples/GUI/Tcl/ImplicitPlaneWidget.tct)演示了3DWidget的使用方法。类vtkImplicitPlaneWidget主要用于物体的切割,在该示例中,一个由vtkSphereSource和vtkConeSource所生成的类似狼牙棒(使用符号化的技术生成,见第94页“符号化”一节)的vtkProp3D实例是vtkImplicitPlaneWidget的切割对象,切割平面将其分成两部分,其中一部分用绿色进行着色。可以通过鼠标来控制vtkImplicitPlaneWidget的切割平面的位置和方向,具体就是调整平面的法向量,移动平面的原点等。
图4-15vtkImplicitPlaneWidget示例
vtkSphereSource sphere vtkConeSource cone vtkGlyph3D glyph glyph SetInputConnection [sphere GetOutputPort] glyph SetSourceConnection [cone GetOutputPort] glyph SetVectorModeToUseNormal glyph SetScaleModeToScaleByVector glyph SetScaleFactor 0.25 #The sphere and spikes are appended into a single polydata. #This just makes things simpler to manage. vtkAppendPolyData apd apd AddInputConnection [glyph GetOutputPort] apd AddInputConnection [sphere GetOutputPort] vtkPolyDataMapper maceMapper maceMapper SetInputConnection [apd GetOutputPort] vtkLODActor maceActor maceActor SetMapper maceMapper maceActor VisibilityOn #This portion of the code clips the mace with the vtkPlanes #implicit function. The clipped region is colored green. vtkPlane plane vtkClipPolyData clipper clipper SetInputConnection [apd GetOutputPort] clipper SetClipFunction plane clipper InsideOutOn vtkPolyDataMapper selectMapper selectMapper SetInputConnection [clipper GetOutputPort] vtkLODActor selectActor selectActor SetMapper selectMapper [selectActor GetProperty] SetColor 0 1 0 selectActor VisibilityOff selectActor SetScale 1.01 1.01 1.01 #Create the RenderWindow, Renderer and both Actors # vtkRendererren1 vtkRenderWindow renWin renWin AddRenderer ren1 vtkRenderWindowInteractor iren iren SetRenderWindow renWin #Associate the line widget with the interactor vtkImplicitPlaneWidget planeWidget planeWidget SetInteractor iren planeWidget SetPlaceFactor 1.25 planeWidget SetInput [glyph GetOutput] planeWidget PlaceWidget planeWidget AddObserver InteractionEvent myCallback ren1 AddActor maceActor ren1 AddActor selectActor #Add the actors to the renderer, set the background and size # ren1 SetBackground 1 1 1 renWin SetSize 300 300 ren1 SetBackground 0.1 0.2 0.4 #render the image # iren AddObserver UserEvent {wm deiconify .vtkInteract} renWinRender #Prevent the tk window from showing up then start the event loop. wm withdraw . proc myCallback {} { planeWidget GetPlane plane selectActor VisibilityOn }
从上面的示例可以看到,首先是实例化一个vtkImplicitPlaneWidget对象,然后再放置该Widget。Widget的放置是相对于数据集而言的(Tcl脚本中“[glyph GetOutput]”返回的是vtkPolyData类型,该类型是vtkDataSet的子类)。PlaceFactor参数是调整Widget的相对大小,在这个示例中,Widget大小设置成比输入数据的包围盒大25%。示例中Widget的一个关键设置是添加观察者(Observer)来响应InteractionEvent事件。StartInteractionEvent和EndInteractionEvent分别是鼠标按下和鼠标弹起时所响应的事件,而InteractionEvent则是鼠标移动时响应的事件。在示例中,InteractionEvent是与Tcl的过程(Procedure)myCallback绑定在一起,该过程实现的功能主要是拷贝Widget所切割的平面到一个vtkPlane实例中,切割的过程主要是由一个隐函数完成的(见第213页“隐函数建模”一节)。
3DWidget是VTK里一个功能非常强大的特性,它可以快速用于任何应用程序中以完成复杂的交互。我们建议你看一下VTK源码中自带的与之相关的示例程序来学习这方面的知识,相关的例子在Examples/GUI和Hybrid/Testing/Cxx等目录中。
VTK中有两种方法来开启抗锯齿的功能:基于图元或者是多重采样(perprimitive type or through multisampling),其中多重采样一般能获得更加理想的效果。
抗锯齿的方法都是通过控制vtkRenderWindow的API来完成的。当激活多重采样以及所用的图形卡支持该功能时,基于图元的抗锯齿功能的标志则会被忽略。抗锯齿的过程是在vtkRenderWindow对象创建之后,渲染场景初始化之前完成的。
要注意的是,VTK抗锯齿的结果与实际的OpenGL实现是有所区别的,OpenGL的实现可以是基于软件的,如Mesa,或者是由图形及其驱动来完成。
图4-16针对球体线框模型的抗锯齿技术的效果
有三个标志来控制抗锯齿功能,一个标志分别控制一种图元类型:
初始状态下,这三个标志都是禁用的。下面列出激活点图元(Point Primitive)的抗锯齿功能所需的4个步骤:
1. vtkRenderWindow*w = vtkRenderWindow;:New();
2. w->SetMultiSamples(0);
3. w->SetPointSmoothing(1);
4. w->Render();
下面给出一个完整的示例程序,示例中使用点图元的抗锯齿功能来显示球体面片的顶点。
#include “vtkRenderWindowInteractor.h” #include “vtkRenderWindow.h” #include “vtkRenderer.h” #include “vtkSphereSource.h” #include “vtkPolyDataMapper.h” #include “vtkProperty.h” int main() { vtkRenderWindowInteractor *i =vtkRenderWindowInteractor::New(); vtkRenderWindow *w =vtkRenderWindow::New(); i->SetRenderWindow(w); w->SetMultiSamples(0); //nomultisampling w->SetPointSmoothing(1); //poingantialiasing vtkRenderer *r = vtkRenderer::New(); w->AddRenderer(r); vtkSphereSource *s =vtkSphereSource::New(); vtkPolyDataMapper *m =vtkPolyDataMapper::New(); m->SetInputConnection(s->GetOutputPort()); vtkActor *a = vtkActor::New(); a->SetMapper(m); vtkProperty *p = vtkProperty::New(); p->SetRepresentationToPoints(); //wewant to see points p->SetPointSize(2.0); //big enough tonotice antialiasing p->SetLighting(0); //don’t be disturbby shading r->AddActor(a); i->Start(); s->Delete(); m->Delete(); a->Delete(); r->Delete(); w->Delete(); i->Delete(); }
以下几行代码是针对点图元的抗锯齿而设置的:
w->SetPointSmoothing(1); p->SetRepresentationToPoints(); p->SetPointSize(2.0);
你可以设置成以下的代码,将点图元抗锯齿换成线图元的抗锯齿:
w->SetLineSmoothing(1); p->SetRepresentationToWireframe(); p->SetLineSize(2.0);
同样,可以换成如下代码,将其换成多边形图元的抗锯齿:
w->PolygonSmoothing(1); p->SetRepresentationToSurface();
相对于图元的抗锯齿,多重采样可以获得更好的效果,多重采样默认是开启的,但是这个功能只要当图形卡支持时才起作用。目前,VTK只支持X窗口的多重采样功能。要禁用多重采用,只要设置变量MultiSamples(初始值是8)的值为0即可:
1. vtkRenderWindow*w = vtkRenderWindow::New();
2. w->SetMultiSamples(0);//disable multisampling.
3. w->Render();
回到上一小节的例子中,如果使用X11,只要禁用线的多重采样,就可以看到点、线或多边形的多重采样的效果。
对于可视化系统而言,将几何对象渲染成透明物体是一种非常强大、有用的功能。它可以让我们看到内部的数据,让我们可以将注意力集中在感兴趣的区域上,其中感兴趣区域可以渲染成不透明的物体,而其他的外层的物体则渲染成透明或半透明。
渲染透明物体也不是很繁琐:屏幕上显示的某个像素的颜色是由穿过该像素的所有可见的几何图元贡献的,像素的颜色是所有可见的几何图元的颜色做混合操作的结果。混合操作通常与顺序相关的,因此,要正确渲染透明物体,必须要有正确的深度(Depth sorting)排序。然而,深度排序一般都比较费时。
VTK提供三种方法来渲染透明物体,每种方法都权衡了正确性(质量)与效率(深度排序)。
快速但不准确。忽略之前所提的深度排序问题,这样就没有额外的计算时间占用,但是所渲染的对象则是不准确的。然而,根据应用程序的上下文关系,所渲染的结果也可能足够好。
较慢但基本准确。这种方法由两个Filter组成。首先是使用vtkAppendPolyData将所有的多边形几何数据整合在一起。然后将vtkAppendPolyData的输出做为vtkDepthSortPolyData的输入。由于深度排序是基于每个几何图元的质心而不是每个像素完成的。因此,这种方法不是足够准确但也能给出一个相对较好的结果。参考VTK/Hybrid/Testing/Tcl/depthSort.tcl示例。
非常慢但准确。如果图形显卡支持(只有NVidia的显卡)的话,可以使用“深度剥离”(Depth Peeling)技术。这种方法可以基于每个像素进行排序,但计算速度也是最慢的,同时结果也是最好的。首次渲染时,应该先设置vtkRenderWindow的Alpha位数:
vtkRenderWindow*w = vtkRenderWindow::New(); w->SetAlphaBitPlanes(1); //禁用多重采样功能: w->SetMultiSamples(0);
然后在渲染器中,开启深度剥离:
vtkRenderer*r = vtkRenderer::New(); r->SetUseDepthPeeling(1);<span style="line-height: 1.846153846; font-family: Arial; background-color: rgb(255, 255, 255);"> </span>
设置深度剥离的参数(MaximumNumberOfPeels和OcclusionRatio):
r->SetMaximumNumberOfPeels(100); r->SetOcclusionRatio(0.1);<span style="line-height: 1.846153846; font-family: Arial; background-color: rgb(255, 255, 255);"> </span>
渲染场景:
w->Render();
最后,应该检查一下图形卡是否支持深度剥离的功能:
r->GetLastRenderingUsedDepthPeeling();<span style="line-height: 1.846153846; font-family: Arial; background-color: rgb(255, 255, 255);"> </span>
深度剥离参数。要灵活使用深度剥离参数,需要对深度剥离算法有所了解。深度剥离算法是从前到后逐层剥离透明的几何物体,直到没有可渲染的物体为止。当达到所设置的最大迭代次数(即SetMaximumNumberOfPeels()所设置的参数),或者上一次剥离所修改的像素数目比预先设置的窗口的百分比区域对应的像素数还少的话,终止迭代。窗口的百分比是由SetOcclusionRatio()设置,如果设置为0.0,则表示希望获得精确的结果,设置成0.2比设置成0.1渲染速度要快。
对OpenGL的要求。如果OpenGL能达到以下要求,图形显卡可以支持深度剥离:
实际上,深度剥离只能运行于NVidia GeForce 6系列或更高版本的显卡,或者是Mesa(比如7.4),ATI的显卡不支持。
例子。以下是使用深度剥离技术的例子(可以查看VTK/Rendering/Tesing/Cxx目录里有关深度剥离的例子)。
#include"vtkRenderWindowInteractor.h" #include"vtkRenderWindow.h" #include"vtkRenderer.h" #include"vtkActor.h" #include"vtkImageSinusoidSource.h" #include"vtkImageData.h" #include"vtkImageDataGeometryFilter.h" #include"vtkDataSetSurfaceFilter.h" #include"vtkPolyDataMapper.h" #include"vtkLookupTable.h" #include"vtkCamera.h" int main() { vtkRenderWindowInteractor *iren=vtkRenderWindowInteractor::New(); vtkRenderWindow *renWin =vtkRenderWindow::New(); renWin->SetMultiSamples(0); renWin->SetAlphaBitPlanes(1); iren->SetRenderWindow(renWin); renWin->Delete(); vtkRenderer *renderer = vtkRenderer::New(); renWin->AddRenderer(renderer); renderer->Delete(); renderer->SetUseDepthPeeling(1); renderer->SetMaximumNumberOfPeels(200); renderer->SetOcclusionRatio(0.1); vtkImageSinusoidSource*imageSource=vtkImageSinusoidSource::New(); imageSource->SetWholeExtent(0,9,0,9,0,9); imageSource->SetPeriod(5); imageSource->Update(); vtkImageData*image=imageSource->GetOutput(); double range[2]; image->GetScalarRange(range); vtkDataSetSurfaceFilter*surface=vtkDataSetSurfaceFilter::New(); surface->SetInputConnection(imageSource->GetOutputPort()); imageSource->Delete(); vtkPolyDataMapper *mapper=vtkPolyDataMapper::New(); mapper->SetInputConnection(surface->GetOutputPort()); surface->Delete(); vtkLookupTable *lut=vtkLookupTable::New(); lut->SetTableRange(range); lut->SetAlphaRange(0.5,0.5); lut->SetHueRange(0.2,0.7); lut->SetNumberOfTableValues(256); lut->Build(); mapper->SetScalarVisibility(1); mapper->SetLookupTable(lut); lut->Delete(); vtkActor *actor=vtkActor::New(); renderer->AddActor(actor); actor->Delete(); actor->SetMapper(mapper); mapper->Delete(); renderer->SetBackground(0.1,0.3,0.0); renWin->SetSize(400,400); renWin->Render(); if(renderer->GetLastRenderingUsedDepthPeeling()) { cout<<"depth peeling wasused"<<endl; } else { cout<<"depth peeling was notused (alpha blending instead)"<<endl; } vtkCamera*camera=renderer->GetActiveCamera(); camera->Azimuth(-40.0); camera->Elevation(20.0); renWin->Render(); iren->Start(); }
Painter机制:定制多边形数据的Mapper(Painter mechanism: customizing thepolydata mapper)。在渲染多边形数据时,我们有时需要有效地控制每个渲染步骤。VTK里可以使用Painter机制来实现,这要多亏工厂设计模式。以下的代码实际上创建一个vtkPainterPolyDataMapper对象。
vtkPolyDataMapper* m = vtkPolyDataMapper::New();
我们可以将m向下转型成vtkPainterPolyDataMapper,从而可以访问vtkPainterPolyDataMapper的API:
vtkPainterPolyDataMapper*m2= vtkPainterPolyDataMapper::SafeDownCast(m); vtkPainterPolyDataMapper*m2= vtkPainterPolyDataMapper::SafeDownCast(m);
这个多边形数据的Mapper将渲染过程委托(Delegate)给vtkPainter对象。通过SetPainter()和GetPainter()可以访问这种委托。
vtkPainter只为具体的子类Painter提供抽象的API接口,每个具体的子类负责对各个阶段进行渲染。这种机制允许用户选择或者组合不同的渲染阶段。比如,vtkPolygonsPainter负责多边形数据的绘制;而vtkLightingPainter则负责光照参数的设置。不同Painter的组合可以形成一条Painter链,这条链中的每个Painer可以将渲染的某个执行部分委托给另外一个Painter。
大多数情况下,我们没有必要显式地去设置Painter链,因为vtkDefaultPainter已经为我们设置了一个标准的Painter链了。
编写自定义Painter。要编写自定义的Painter需要编写两个必要的类:一个是抽象基类vtkPainter的子类;另一个是用OpenGL实现具体的类。
我们先看看现在的Painter类:vtkLightingPainter。vtkLightingPainter派生自vtkPainter,这个类基本是空的。真正的实现是在vtkLightingPainter的子类vtkOpenGLLightingPainter中,在这个类中重载了保护成员方法RenderInternal()。
RenderInternal()方法的参数主要是Renderer和Actor。实现RenderInternal()方法时,主要是编写实际的渲染阶段的代码以及调用Painter链中的下一个Painter,即:this->Superclass::RenderInternal()。
原文:http://blog.csdn.net/www_doling_net/article/details/30525781