5.  视口和裁剪

这一部分我们讨论几何管道的最后一段:裁剪。分为以下几个部分:

  Direct3D通过一个COM对象来执行裁剪,并且提供接口对对象进行操作。IDirect3DViewport3接口是视口对象的最新的接口。

 

5.1 什么是视口?

  从概念上来将,一个视口就是一个二维的矩形,一个三维场景投影在这个举行中。(在Direct3D中,这个矩形存在于一个作为渲染目标的DirectDraw表面上。)投影变换将顶点变换到视口使用的坐标系统中。在Direct3D中,可以用视口来声明下面列出的特性:

  裁剪空间是用来定义要显示到渲染目标表面上的部分场景的区域。如果选择的视口太小,使得场景中的一部分不可见,那么就应该裁剪掉不可见的部分,以免系统再渲染这一部分。

 

5.2  裁剪空间

下面我们来讨论裁剪空间的作用和效果,以及它的用法:

 

5.2.1 关于裁剪体

  我们在视口坐标系统内定以一个裁剪体。在Direc3D中定义裁剪体最好的方法是使用D3DVIEWPORT2结构,它在IDirect3DViewport3中声明。(另一个方法是使用D3DVIEWPORT结构。D3DVIEWPORT2结构比D3DVIEWPORT有更好的裁剪体的定义。)

  D3DVIEWPORT2结构的dvClipXdvClipYdvClipWidthdvClipHeight成员声明了一个区域,在这个区域内的顶点是可见的。这个区域相对于由D3DVIEWPORT2dwXdwYdwWidthdwHeight成员声明的目标矩形。

  Direct3D使用D3DVIEWPORT2结构中设定的值构造一个矩阵,在执行裁剪测试前,这个矩阵被用于所有的顶点。矩阵如下:

pic56.gif (3446 bytes)

  上述矩阵中,所有的变量都直接来自于D3DVIEWPORT2结构:CxCydvClipXdvClipY成员中的值,CwChdvClipWidthdvClipHeight中的值,ZminZmaxdvMinZdvMaxZ成员的值。矩阵根据裁剪空间的属性对顶点进行缩放,并将它们安置在空间原点的周围。

  裁剪矩阵的值使用下面的公式来进行测试,测试失败的顶点将被裁剪掉:

pic57.gif (1436 bytes)

  公式中,XcYcZc、和Wc表是应用裁剪矩阵之后的顶点的坐标。

  大多数情况下,我们定义的裁剪空间在x-y-方向位于-1.01.0之间,z-方向在0.01.0之间,并且不对顶点进行缩放。这样的空间设置的D3DVIEWPORT2结构如下所示:

dvClipX = -1.0;
dvClipY = 1.0;
dvClipWidth = 2.0;
dvClipHeight = 2.0;
dvMinZ = 0.0;
dvMaxZ = 1.0;

  dvClipWidthdvClipHeight都不为0是很重要的。同时,dvMinZ也不能等于dvMaxZ裁剪体与目标矩形相对应,如下图所示:

pic58.gif (6954 bytes)

  D3DVIEWPORT2结构的dwXdwYdwWidthdwHeight成员的值是相对于渲染目标表面的左上角的坐标值。

 

5.2.2 考虑不同的顶点类型

  如果你使用D3DVERTEXD3DLVERTEX顶点——也就是说,如果Direct3D正在执行变换——那么你可以这样来设置这个结构的最后六个成员:

dvClipX = -1.0f;
dvClipY = 1.0f;
dvClipWidth = 2.0f;
dvClipHeight = 2.0f;
dvMinZ = 0.0f;
dvMaxZ = 1.0f;

  注意:向上例那样设置视口的值不能说明视口的高宽比例。尽管很少使用视口参数来缩放视口的高宽比,但是投影矩阵对于这一任务来说的确是一个很精确、很灵活、很清晰的平台。使用投影矩阵来设置视口高宽比的详细内容见“什么是投影变换?”。

  如果你使用D3DTLVERTEX顶点——也就是说,如果程序正在处理变换和光线——,那么你也可以设置裁剪空间。如果数据的x-y-坐标已经与像素相匹配,就可以向下面这样来设置D3DVIEWPORT2的最后六个成员:

dvClipX = 0;
dvClipY = 0;
dvClipWidth = dwWidth;
dvClipHeight = dwHeight;
dvMinZ = 0.0f;
dvMaxZ = 1.0f;

  程序可以使用由D3DVIEWPORT2dwXdwYdwWidthdwHeight成员定义的矩形来进行裁剪。在使用D3DTLVERTEX顶点时,D3DVIEWPORT2中的剪切成员(以dvClip开头)会被忽视,但是系统仍然认为它们有效,所以程序还必须为它们提供合理的值。如果你不需要这一裁剪,可以在调用IDirect3DDevice3::DrawPrimitiveIDirect3DDevice3::DrawIndexedPrimitive时声明D3DDP_DONOTCLIP

 

5.3 视口缩放

  D3DVIEWPORT2结构的dwXdwYdwWidthdwHeight成员使用的尺寸用来定义渲染目标表面上的视口的位置和大小。这些值是基于屏幕坐标的,原点在左上角。

  Direct3D使用视口的位置与大小来对顶点进行缩放,使一个被渲染的场景在目标表面上能由一个适当的位置。在内部,Direct3D将这些值插入一个矩阵,这个矩阵应用于每一个顶点。矩阵如下:

pic59.gif (2925 bytes)

  这个矩阵根据视口的大小简单的对顶点进行缩放,并将它们平移到目标表面上合适的位置。(这个矩阵将y-坐标“翻转”,使原点在左上角,y-坐标向下增长。)使用了这个矩阵之后,顶点仍然是齐次的(homogeneous)——也就是说,它们仍然是[x,y,z,w]顶点——并且在进行光栅之前必须将它们转换成非齐次坐标(non-homogeneous coordinate)。这一操作要通过简单的分割(division)来实现,见“光栅”。

 

5.4  使用视口

下面我们详细讨论创建、运行和删除视口。

 

5.4.1 准备使用视口

  在Direct3D中设置一个视口,需要一下步骤:

  1. 创建视口对象
  2.   创建视口,使Direct3D分配内部数据结构来包含视口的属性。详细内容见“创建视口”。

  3. 将视口加入设备
  4.   在视口和设备间建立联系;设备将视口加入一个内部的视口列表中。可以对这个设备调用IDirect3DDevice3::NextViewport方法来打开当前的视口。详细内容见“将视口加入设备”。

  5. 设置视口属性
      将视口加入设备之后,要设置视口的属性。视口的属性控制
    Direct3D如何对对象进行裁剪,决定要被渲染到目标表面的场景的高宽比。

  对视口的操作结束之后,可以从设备的视口列表中将它删除。详细内容见“删除视口”。

 

5.4.2 创建视口

  调用IDirect3D3::CreateViewport方法创建视口对象。调用中需要一个有效的LPDIRECT3D3指针,如下例中的lpD3D2

HRESULT hr;
hr = lpD3D3->CreateViewport(&lpD3DViewport3, NULL);
if(FAILED(hr))
 return hr;
else
{
// Add the viewport to a device.
}

  创建了一个视口后,要将它加入设备。

 

5.4.3 将视口加入设备

  调用IDirect3DDevice3::AddViewport方法将视口加入设备的视口列表。


HRESULT hr;
// lpD3DDevice3 is a valid pointer to a Direct3D
// device object.
hr = lpD3DDevice3->AddViewport(lpD3DViewport3);

if(FAILED(hr))
 return hr;
else
{
 // Set the viewport properties.
}

  将视口加入设备之后,要设置视口裁剪空间。

 

5.4.4 设置视口裁剪空间

  要设置视口裁剪空间,要初始化和设置它的裁剪值。通常,视口被设置为在整个表面进行渲染,并且要校正高宽比。可以使用下面的方法来设置D3DVIEWPORT2的成员:

memset(&viewData, 0, sizeof(D3DVIEWPORT2));
viewData.dwSize = sizeof(D3DVIEWPORT2);
viewData.dwX = 0;
viewData.dwY = 0;
viewData.dwWidth = width;
viewData.dwHeight = height;
viewData.dvClipX = -1.0f;
viewData.dvClipY = 1.0;
viewData.dvClipWidth = 2.0f;
viewData.dvClipHeight = 2.0f;
viewData.dvMinZ = 0.0f;
viewData.dvMaxZ = 1.0f;

  设置完后,调用IDirect3DViewport3::SetViewport2方法将D3DVIEWPORT2结构应用于视口对象。


HRESULT hr;
hr = lpD3DViewport3->SetViewport2(&viewData);
if(FAILED(hr))
 return hr;

  如果调用成功,就可以使用视口了。如果需要改变视口的值,可以简单的更新D3DVIEWPORT2结构的值,并再次调用SetViewport2

  IDirect3DViewport3有两种方法来声明一个视口裁剪空间,见“裁剪空间”。

 

5.4.5 删除视口

  对于一个给定设备,如果不再需要视口了,首先要删除与它相关的灯光和材质,然后调用IDirect3DDevice3::DeleteViewport将它从设备的视口列表中删除。DeleteViewportIDirect3DViewport3接口的地址作为为一个参数。(如果你正在使用执行缓冲,可以调用IDirect3DDevice::DeleteViewport方法,它将IDirect3Dviewport接口的地址作为参数。)

  删除一个视口,仅仅是将它从设备的视口表中移走;它不会释放视口对象本身的资源。要彻底将视口清除掉,可以调用IUnknown::Release方法。

 

5.4.6 清空视口

  清空视口操作不仅将渲染目标表面上的视口矩形的内容清除,同时(如果声明的话)也将深度和模板缓冲表面的内容清除。通常,再渲染一个新框架之前要清空视口,以确保图形和其他数据已经准备好接受新的渲染对象而不是旧的。

  IDirect3DViewport3接口提供了IDirect3DViewport3::ClearIDirect3DViewport3::Clear2方法来进行不同方法的清空视口操作。这两种方法都接受一个或多个要清空的矩形。有时,当进行渲染的场所包括了贯穿整个视口矩形的运动时,比如在一个第一人称视角的三维游戏中,我们可能想要在每一帧都清空整个视口。这时,可以将 dwCount参数设为1,将lpRects参数设置为一个覆盖整个视口区域的单独矩形的地址。

  注:DirectX 5.0允许将背景材质(background materials)与纹理联合使用,将视口清空为一个纹理,而不是一个简单的颜色。这一特性很少使用,也不是特别有效。DirectX 6.0新加的接口不接受纹理句柄,也就不能将视口清空为一个纹理。现在,程序必须人工绘制背景。因此,很少需要清空渲染目标表面上的视口了。只要程序清空深度缓冲,渲染表面上所有的像素都会被冲掉改写。

  在某些情况下,可能只要对渲染目标表面和深度缓冲的一部分进行进行渲染。清空函数也允许只通过一次调用来清空表面上的多个区域。要完成这样的任务,就要将dwCount参数设置为想要清空的矩形的编号,并且将lpRects参数声明为矩形队列中第一个矩形的地址。

  Clear函数有不同的特性和行为。如果将dwFlags参数声明为D3DCLEAR_TARGET标志,IDirect3DViewport3::Clear方法可以将视口清空为一个背景材质的颜色;如果包括D3DCLEAR_ZBUFFER标志,它也可以将z-buffer清空为“最深”的颜色值(1.0)。这个方法需要程序调用IDirect3DViewport3::SetBackground来设置一个背景颜色。Clear函数不能清空模板缓冲。

  IDirect3DViewport3::Clear2方法提供了更大的灵活性,也提高了易用性,还支持在深度缓冲中清空模板位(stencil bit)。Clear2接受dwFlags参数中的两个与先前版本同样的标志,但是结果有少许变化,还加入了支持模板的新标志。如果程序包含了D3DCLEAR_TARGET标志,Clear2会使用dwColor参数提供的RGBA颜色(不是材质颜色)来清空视口。如果包含了D3DCLEAR_ZBUFFER标志,它会用dvZ中声明的一个深度值来清空深度缓冲:0.0表示最近距离,1.0表示最远距离。包含了新的D3DCLEAR_STENCIL标志,可以将模板位重新设置为dwStencil参数提供的值:它的范围从02n-1n使模板缓冲的位深。

 

5.4.7 人工变换顶点

  在Direct3D程序中,可以使用三种不同的顶点:

  在渲染之前不对顶点进行变换和光线处理的程序使用D3DVERTEX顶点(或者一个等价的可塑(flexible)顶点格式)。尽管声明了光线参数和变换矩阵,Direct3D仍然要进行运算。

  使用D3DLVERTEX顶点(或等价的可塑(flexible)顶点格式)

  使用D3DTLVERTEX顶点(或等价的可塑(flexible)顶点格式)。这些顶点会跳过几何管道的变换部分,但是如果需要的话可以被系统裁剪。如果程序自己裁剪顶点,那么在调用渲染方法时可以通过声明D3DDP_DONOTCLIP标志来得到最好的性能。如果你要使用Direct3D来进行裁剪,则要省略D3DDP_DONOTCLIP标志。注意:如果需要Direct3D来裁剪经过变换和光线处理的顶点,那么系统将它们先变换到摄像机空间进行裁剪,然后在变换回屏幕空间,这样会增加系统开销。

  Direct3D有两种方法将简单的顶点类型变换为复杂的顶点类型:TransformVertices方法——一种老方法;和顶点缓冲——DirectX 6.0中提出的新方法。后一种方法最为有效。

  1. 使用TransformVertices方法
  2.   使用IDirect3DViewport3::TransformVertices方法可以将D3DVERTEXD3DLVERTEX顶点类型变换到D3DTLVERTEX类型描述的屏幕坐标系中。

      TransformVertices使用当前的矩阵(调用IDirect3DDevice3::SetTransform方法设置的矩阵)来执行变换。如果你只想变换而不必渲染一组顶点时,可以使用TransformVertices方法。例如,你可能提供两个顶点,它们定义了一个边界和的两个相反的角。如果这两个顶点都被裁剪掉了,那么你可能放弃这个模型的更进一步处理。

      TransformVertices不执行光线操作。如果使用它来处理D3DVERTEX顶点,那么得到的D3DTLVERTEX顶点的散射和镜面分量将不正确。同样的,如果使用D3DLVERTEX顶点,那么每一个顶点的散射和镜面分量在TransformVertices方法处理之后将不会发生变化。

      尽管自己管理变换要比调用IDirect3DViewport3::TransformVertices方法快,但是执行你自己的变换引擎也会耗费时间,所以使用TransformVertices方法是相当快的。如果你想要得到比使用D3DVERTEXD3DLVERTEX顶点时更多的对几何管道的控制,而又不想开发自己的变换引擎,那么使用TransformVertices方法是一个好的折衷方案。

      注:程序经常使用TransformVertices来执行可见性检测,这是因为它的调用能够返回相关的 D3DTRANSFORMDATA结构中的裁剪信息。但是调用IDirect3DDevice3::ComputeSphereVisibility方法能够得到性能最好的可见性检测。

  3. 使用顶点缓冲
  4.   顶点缓冲用来处理成批的顶点和进行快速渲染。它提供了IDirect3DVertexBuffer::ProcessVertices方法来执行顶点变换;它通常比TransformVertices方法要快。ProcessVertices只接受没有经过变换的顶点,并且可以对顶点随意地进行光线和裁剪处理。光线处理在调用ProcessVertices方法时进行,而裁剪在渲染时才进行。

      处理完顶点之后,就可以使用渲染方法来渲染顶点了,也可以通过锁定顶点缓存来直接访问顶点。

 

6.  光栅

  经过Direct3D几何管道之后,顶点经过变换、裁剪和缩放,已接近准备好送往光栅而绘制到屏幕上。但此时,顶点仍然是齐次的(homogeneous),并且光栅希望以x-y-z-坐标的形式得到顶点,同时还要求RHW——齐次w的倒数(reciprocal-of-homogeneous-w)。Direct3Dx-y-z-坐标与w-坐标分离,使齐次顶点转换为非齐次顶点,并利用w-坐标的倒数来得到RHW值:

Xs=X/W
Ys
=Y/W
Zs
=Z/W
RHW
=1/W

  得到的结果被传送给光栅。光栅用x-y-坐标作为屏幕的坐标,用z-坐标作为深度信息。RHW值可以有多种用途:可以用来计算雾化效果,执行透视校正纹理映射,用于w-buffering

 

上一页 | 目录 | 下一页