VTK基础概念(2)

3.3 光照

剧场里有各式各样的灯光,三维渲染场景中也一样,可以有多个光照存在。光照和相机是三维渲染场景必备的因素,如果没有指定(像3.1.1_RenderCylinder例子,我们没有给Renderer指定相机和光照),vtkRenderer会自动地创建默认的光照和相机。VTK里用类vtkLight来表示渲染场景中的光照。与现实中的灯光类似,VTK中的vtkLight实例也可以打开、关闭,设置光照的颜色,照射位置(即焦点),光照所在的位置,强度等等。

vtkLight可以分为位置光照(Positional Light,即聚光灯)和方向光照(Direction Light)。位置光照是光源位置在渲染场景中的某个位置,可以指定光照的衰减值、锥角等;方向光照即光源位置在无穷远,光线可以认为是平行的,比如自然界中的太阳光。光源的位置和焦点的连线定义光线的方向,默认的vtkLight即为方向光照。

vtkLight常的方法有:

SetColor() — 设置光照的颜色,以RGB的形式指定颜色。

SetPosition() — 设置光照位置。

SetFocalPoint() — 设置光照焦点。

SetIntensity() — 设置光照的强度。

SetSwitch() / SwitchOn()/ SwitchOff()— 打开或关闭对应的光照。

我们在讲vtkProp的时候,该类有方法SetVisibility (int) / GetVisibility() /VisibilityOn() /VisibilityOff()等来控制vtkProp对象的可见与不可见的属性。同样,vtkLight里也有类似命名风格的方法:SetSwitch() /GetSwitch()/SwitchOn()/ SwitchOff()。不难发现,在VTK里,某个属性的设置都是采取这一类方法,以vtkLight为例,SwitchOn()跟SetSwitch(1)实现的效果是一样,SwitchOff()则与SetSwitch(0)是一样的,GetSwitch()则是用于获取vtkLight对象关闭或打开这个属性的值。如果某个类有提供SetXXX()方法,一般也会提供GetXXX()方法来获取相应的值,这个我们在前面的内容也已经有一些了解。再比如,vtkLight还提供:SetPositional() / GetPositional()/ PositionalOn()/PositionalOff()一类方法来设置位置光照。

在类vtkLight的头文件vtkLight.h里,找不到类似SetSwitch()/GetSwitch()等方法的原型,但是可以看到以下几行:

vtkSetMacro(Switch,int);

vtkGetMacro(Switch,int);

vtkBooleanMacro(Switch,int);

顾名思义,vtkSetMacro/vtkGetMacro/vtkBooleanMacro都是宏,展开这几个宏以后就是以上函数的原型。为了保证代码的整洁与复用性,VTK在文件vtkSetGet.h里定义了大量了宏,当你找不到某个函数的原型时,可能这些函数就是由宏展开以后所定义的,所以,在阅读VTK源码的时候,不妨多右击一下某个函数或者关键字,然后点击弹出菜单中的“Go to Definition…”,也就一目了然了。

光照vtkLight的使用方法(程序执行结果如图3.5所示):

vtkSmartPointer<vtkLight> myLight= vtkSmartPointer<vtkLight>::New();

myLight->SetColor(0,1,0);

myLight->SetPosition(0,0,1);

myLight->SetFocalPoint(renderer->GetActiveCamera()->GetFocalPoint());

renderer->AddLight(myLight);

 

vtkSmartPointer<vtkLight> myLight2= vtkSmartPointer<vtkLight>::New();

myLight2->SetColor(0,0,1);

myLight2->SetPosition(0,0,-1);

myLight2->SetFocalPoint(renderer->GetActiveCamera()->GetFocalPoint());

renderer->AddLight(myLight2);

上述定义了两个vtkLight对象,一个为绿色光,位置在(0,0,1),焦点对着相机的焦点;另一个为蓝色光,位置在(0,0,-1),焦点也是对着相机的焦点,最后两个光照调用vtkRenderer的方法AddLight()加入到渲染场景中,因为Renderer里可以有多个灯照,所以,VTK提供的接口是AddLight()而不是SetLight()。

 

图3.5例3.1.1_RenderCylinder加入绿色和蓝色光照的效果

3.4 相机

观众的眼睛就好比三维渲染场景中的相机,VTK则是用vtkCamera类来表示三维渲染场景中的相机。vtkCamera负责把三维场景投影到二维平面,如屏幕、图像等。图3.6为相机投影示意图。

从图3.6可以看出与相机投影相关的因素主要有:

l  相机位置:即相机所在的位置,用方法vtkCamera::SetPosition()设置。

l  相机焦点:用方法vtkCamera::SetFocusPoint()设置,默认的焦点位置在世界坐标系的原点。

l  朝上方向:即哪个方向为相机朝上的方向。就好比我们直立看东西,方向为头朝上,看到的东西也是直立的,如果我们倒立看某个东西,这时方向为头朝下,看到的东西当然就是倒立的。相机位置、相机焦点和朝上方向三个因素确定了相机的实际方向,即确定相机的视图。

 

图3.6相机vtkCamera投影示意图

l  投影方向:相机位置到相机焦点的向量方向即为投影方向。

l  投影方法:确定Actor是如何映射到像平面的。vtkCamera定义了两种投影方法,一种是正交投影(OrthographicProjection),也叫平行投影(Parallel Projection),即进入相机的光线与投影方向是平行的。另一种是透视投影(PerspectiveProjection),即所有的光线相交于一点。

l  视角:透视投影时需要指定相机的视角(View Angle),默认的视角大小为30º,可以用方法vtkCamera::SetViewAngle()设置。

l  前后裁剪平面:裁剪平面与投影方向相交,一般与投影方向也是垂直的。裁剪平面主要用于评估Actor与相机距离的远近,只有在前后裁剪平面之间的Actor才是可见的。裁剪平面的位置可以用方法vtkCamera::SetClippingRange()设置。

如果你想获取vtkRenderer里默认的相机,可以用方法vtkRenderer::GetActiveCamera()。

相机vtkCamera的使用方法:

vtkSmartPointer<vtkCamera>myCamera = vtkSmartPointer<vtkCamera>::New();

myCamera->SetClippingRange(0.0475,2.3786); //这些值随便设置的,为了演示用法而已

myCamera->SetFocalPoint(0.0573,-0.2134, -0.0523);

myCamera->SetPosition(0.3245,-0.1139, -0.2932);

myCamera->ComputeViewPlaneNormal();

myCamera->SetViewUp(-0.2234,0.9983, 0.0345);

renderer->SetActiveCamera(myCamera);

上述我们用SetClippingRange();SetFocalPoint();SetPosition()分别设置相机的前后裁剪平面,焦点和位置。ComputeViewPlaneNormal()方法是根据设置的相机位置、焦点等信息,重新计算视平面(View Plane)的法向量。一般该法向量与视平面是垂直的,如果不是垂直的话,Actor等看起来会有一些特殊的效果,如错切。SetViewUp()方法用于设置相机朝上方向。最后用方法vtkRenderer::SetActiveCamera()把相机设置到渲染场景中。

vtkCamera除了提供设置与相机投影因素相关的方法之外,还提供了大量的控制相机运动的方法,如:vtkCamera::Dolly(),vtkCamera::Roll(),vtkCamera::Azimuth(),vtkCamera::Yaw(),vtkCamera::Elevation(),vtkCamera::Pitch(),vtkCamera::Zoom()。这些方法具体表示相机是怎么运动,以及相对哪个位置或者方向运动,请参考图3.7或者关于类vtkCamera的文档说明。

 

图3.7 相机运动方向示意图

图3.7是脚本文件camera.tcl(在VTK的源码目录里:VTKHybridTestingTclcamera.tcl)和camera2.tcl的执行结果。第1章里,我们提到过Tcl脚本文件,当时建议大家安装vtk-5.10.0-win32-x86.exe这个程序,如果你安装了这个程序的话,现在就可以执行图3.7的脚本了。

有两种方法可以运行tcl脚本。第一种是从Windows开始菜单里打开VTK的Tcl脚本程序,出现图3.8所示的界面。

 

图3.8 vtk-5.10.0-win32-x86程序运行界面

接着,在“%”后面输入“source XXXX.tcl”,然后回车,即可执行对应的tcl脚本文件。XXXX.tcl如果输入的是绝对路径时,要注意路径里的斜杠是“/”,而不是“”,如果路径带有空格,需要用英文的双引号把整个路径括起来。

比如:source “D:/ProgramFiles/VTK/camera.tcl” (然后回车,即可运行tcl脚本)

第二种方法是:安装完vtk-5.10.0-win32-x86.exe程序后,把vtk.exe所在的路径加入到Path环境变量,然后打开cmd窗口,在cmd窗口里直接输入:vtk XXXX.tcl,回车,同样可以执行tcl脚本文件。*.tcl文件可以用记事本打开。

3.5 颜色

前面的内容我们提到Actor的属性,颜色是Actor比较重要的属性之一。VTK采用RGB和HSV两种颜色系统来描述颜色。

RGB颜色系统就是由三个颜色分量:红色(R)、绿色(G)和蓝色(B)的组合表示,在VTK里这三个分量的取值都是从0到1,(0, 0, 0)表示黑色,(1, 1, 1)表示白色。vtkProperty::SetColor(r,g, b)采用的就是RGB颜色系统设置颜色属性值。

HSV颜色系统同样也是由三个分量来决定颜色,分别是:色相(Hue),是颜色的基本属性,就是我们平常所说的颜色名称,如红色、黄色等;饱和度(Saturation),是指颜色的纯度,其值越高则越纯;值(Value,也就是强度Intensity或者亮度Bright),值为0通常表示的是黑色,值为1表示的是最亮的颜色。这三个分量的取值范围也是0到1。类vtkLookupTable提供了HSV颜色系统设置的方法。

表3.1列出了常用颜色RGB和HSV值的对照。

颜色

RGB

HSV

黑色Black

0, 0, 0

*, *, 0

白色White

1, 1, 1

*, 0, 1

红色Red

1, 0, 0

0, 1, 1

绿色Green

0, 1, 0

1/3, 1, 1

蓝色Blue

0, 0, 1

2/3, 1, 1

黄色Yellow

1, 1, 0

1/6, 1, 1

蓝绿色Cyan

0, 1, 1

1/2, 1, 1

品红色Magenta

1, 0, 1

5/6, 1, 1

天蓝色Sky blue

1/2, 1/2, 1

2/3, 1/2, 1

表3.1 常用颜色RGB和HSV值的对照

与颜色设置相关的VTK类除了vtkProperty,vtkLookupTable之外,还有vtkColorTransferFunction。vtkLookupTable和vtkColorTransferFunction都派生自vtkScalarsToColors。

3.6 坐标系统及空间变换

3.6.1坐标系统

计算机图形学里常用的坐标系统主要有四种,分别是:Model坐标系统、World坐标系统、View坐标系统和Display坐标系统,以及两种表示坐标点的方式:以屏幕像素值为单位和归一化坐标值(各坐标轴取值都为[-1, 1])。它们之间的关系如图3.9所示。

Model坐标系统是定义模型时所采用的坐标系统,通常是局部的笛卡尔坐标系。例如,我们要定义一个表示球体的Actor,一般的做法是将该球体定义在一个柱坐标系统里。

World坐标系统是放置Actor的三维空间坐标系,Actor其中的一个功能就是负责将模型从Model坐标系统变换到World坐标系统。每一个模型可以定义有自己的Model坐标系统,但World坐标系只有一个,每一个Actor必须通过放缩、旋转、平移等操作将Model坐标系变换到World坐标系。World坐标系同时也是相机和光照所在的坐标系统。

View坐标系统表示的是相机所看见的坐标系统。X、Y、Z轴取值为[-1, 1],X、Y值表示像平面上的位置,Z值表示到相机的距离。相机负责将World坐标系变换到View坐标系。

 

图3.9Model、World、View和Display坐标系统

Display坐标系统跟View坐标系统类似,但是各坐标轴的取值不是[-1, 1],而是使用屏幕像素值。屏幕上显示的不同窗口的大小会影响View坐标系的坐标值[-1, 1]到Display坐标系的映射。可以把不同的渲染场景放在同一个窗口进行显示,例如,在一个窗口里,分为左右两个渲染场景,这左右的渲染场景(vtkRenderer)就是不同的视口(Viewport)。例子3.6.1_Viewport演示了把一个窗口分为四个视口,用vtkRenderer::SetViewport()来设置视口的范围(取值为[0, 1]):

renderer1->SetViewport(0.0,0.0,0.5,0.5);

renderer2->SetViewport(0.5,0.0,1.0,0.5);

renderer3->SetViewport(0.0,0.5,0.5,1.0);

renderer4->SetViewport(0.5,0.5,1.0,1.0);

程序执行结果如图3.10所示。

在VTK里,Model坐标系统用得比较少,其他三种坐标系统经常使用。它们之间的变换则是由类vtkCoordinate进行管理的。根据坐标点单位、取值范围等不同,可以将坐标系统分为:

l  DISPLAY — X、Y轴的坐标取值为渲染窗口的像素值。坐标原点位于渲染窗口的左下角,这个对于VTK里所有的二维坐标系统都是一样的,且VTK里的坐标系统都是采用右手坐标系。

l  NORMALIZEDDISPLAY — X、Y轴坐标取值范围为[0, 1],跟DISPLAY一样,也是定义在渲染窗口里的。

l  VIEWPORT— X、Y的坐标值定义在视口或者渲染器(Renderer)里。

l  NORMALIZEDVIEWPORT — X、Y坐标值定义在视口或渲染器里,取值范围为[0, 1]。

l  VIEW— X、Y、Z坐标值定义在相机所在的坐标系统里,取值范围为[-1, 1],Z值表示深度信息。

l  WORLD — X、Y、Z坐标值定义在世界坐标系统,参考图3.9。

l  USERDEFINED— 用户自定义坐标系统。

vtkCoordinate类提供的设置以上坐标系统的方法是:

SetCoordinateSystemToDisplay ()

SetCoordinateSystemToNormalizedDisplay ()

SetCoordinateSystemToViewport ()

SetCoordinateSystemToNormalizedViewport ()

SetCoordinateSystemToView ()

SetCoordinateSystemToWorld ()

 

图3.10 例3.6.1_Viewport执行结果

3.6.2空间变换

我们在三维空间里定义的三维模型,最后显示的时候都是投影到二维平面,比如在屏幕上显示,生成二维图像等等。三维到二维的投影包括透视投影(PerspectiveProjection)和正交投影(Orthogonal Projection,正交投影也叫平行投影)。

不管是透视投影还是正交投影,变换的基本元素都是三维坐标点,在计算机图形学里,三维坐标点通常用齐次坐标来表示。例如,三维空间里有一个坐标点(x,y,z),用齐次坐标表示为(xh,yh,zh,wh),其中,

     式 3-1

如果wh = 0,则表示无穷远的点。

利用齐次坐标,可以将空间变换用4×4的矩阵来表示。例如,平移、旋转、缩放等仿射变换都可以用矩阵相乘的形式表示:

l  平移。设笛卡尔坐标系下的点(xyz),求经平移向量(txty,tz)变换后的结果(x’,y’,z’)。

首先,先构造平移矩阵TT

    式3-2

得平移结果为:

    式3-3

如果用笛卡尔坐标系表达为:

    式3-4

l  缩放。同理,我们也可以构造出缩放矩阵TS

     式3-5

其中SxSySz分别为X、Y、Z轴的缩放因子。

l  旋转。设绕X、Y、Z轴旋转角度分别为α,β,γ,则旋转矩阵分别为:

    式3-6

VTK里与空间变换相关的类有:vtkTransform2D,vtkTransform,vtkPerspectiveTransform,vtkGeneralTransform,vtkTransformFilter,vtkMatrix4x4等。

3.7 本章小结

本章从一个稍微复杂的VTK程序里出发,深入了解VTK的一些基本概念。

首先我们从vtkCylinderSource学会了一组VTK类:vtkXXXSource。这一组类派生自vtkPolyDataAlgorithm,它们输出的数据类型都是vtkPolyData,都些类都是VTK预定义好的图形模型。

接着,我们把vtkXXXSource的输出作为vtkPolyDataMapper的输入,Mapper的作用是将输入的数据转换为几何图元(点、线、多边形)进行渲染。

然后,我们实例化一个Actor对象,因为我们知道,VTK渲染场景中数据的可视化表达是通过vtkProp的子类负责的。通过Actor的方法SetMapper()可以设置对应的Mapper。

紧接着,我们又实例化了渲染窗口、渲染器、交互器、交互类型等对象,并逐个地加以介绍。于是我们明白了渲染窗口就是我们看到的那个VTK窗口;渲染器Renderer可以通过渲染窗口的方法AddRenderer()加入的渲染窗口里;交互器则是用方法SetRenderWindow()给它指定交互的窗口;交互风格与交互器是用方法SetInteractorStyle()关联在一起;若干个Actor对象可以用方法AddActor()加入到渲染器里渲染。综合起来可以用图3.11来理清它们之间简单而复杂的关系。(可以发现:这些对象之间头尾相接,可以连成一条线结构,这就是VTK里非常重要的概念:VTK管线结构。)

 

图3.11 例3.1.1_RenderCylinder各对象之间的关系

然后我们又看了渲染场景里光照和相机等对象,明白了渲染场景里可以有多个光照,但只能有一个相机;以及了解了设置光照和相机的方法。

介绍vtkActor时,我们通过vtkProperty对象来改变Actor的颜色,于是我们顺便介绍了VTK所用到的颜色模型,分别是RGB颜色模型和HSV颜色模型。

最后我们又讲了似乎跟前面的内容并不搭边的“坐标系统及空间变换”,明白VTK里用到Model、World、View和Display等坐标系统,它们之间可以变换通过类vtkCoordinate来实现。然后介绍了VTK里使用齐次坐标做空间变换的概念,列出了平移、缩放和旋转等变换矩阵。其实这部分内容跟前面介绍的是息息相关的,比如,渲染场景里的Actor通过相机投影到像平面时,就用到了这些内容。vtkRenderer里的方法ViewToWorld()和WorldToView()等,里面也会用到空间变换。

本章的内容是VTK最最基础的东西,可以毫不夸张地说,后续三维方面的例子基本都会用到本章所介绍的概念,所以,掌握本章内容的重要性可想而知。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注