八.深度缓冲

  下面我们来讨论有关深度缓冲的问题:

  Direct3D立即模式教程中提供了有关使用深度缓冲的更多的内容。

 

1. 什么是深度缓冲?

  一个深度缓冲,统称被称为z-bufferw-buffer,是一个DirectDraw表面,它保存着Direct3D使用的深度信息。当Direct3D渲染一个三维场景时,它可将深度缓冲作为一个工作区来决定光栅的多边形中的像素如何吸收掉其它的像素。Direct3D使用一个离屏(off-screenDirectDraw表面作为写最终颜色值的目标。与这个渲染目标表面相关的深度缓冲表面被用来存储深度信息,它通知Direct3D,场景中的可见像素有多深。

  当一个3-D场景经过光栅,并且深度缓冲有效时,渲染表面上的每一个点都会被检查。深度缓冲中的值被作为一个点的z坐标或是等同的w坐标。使用z值的深度缓冲被称为“z-buffer”,使用w值的称为“w-buffer”。每种深度缓冲都有各自的优缺点。

  在检测开始时,深度缓冲中的深度值被设置为场景中的最大可能值。渲染表面的颜色值被设置为背景颜色或者那一点的背景纹理的颜色值。场景中的每一个多边形都要进行检查,看它是否和渲染表面上的当前坐标(x,y)相交叉。如果交叉,那么就检查当前点的深度值(z-buffer中的z,或w-buffer中的w),看它是否小于已经存储在深度缓冲中的深度值。如果多边形的深度值较小,那么就将它存储深度缓冲中,并将多边形的颜色值写到渲染表面的当前点上。如果多边形的深度值较大,那么接着检查下一个多边形。整个过程如下图所示:

注:我们可以改变Direct3D所用的比较关系,来决定将哪些值放在深度缓冲中以及紧接着的渲染目标表面中。要这样做,只要改变D3DRENDERSTATE_ZFUNC渲染状态的值就可以了。但是,这种方法我们一般并不采用。

  几乎所有的3-D加速卡都支持z-buffering,这样就使得z-buffers成为现在最常用的深度缓冲类型。但是,z-buffers也有它本身的缺陷。由于它所使用的数学方法,使得一个z-buffer中产生的z值在它允许的范围内(通常为0.01.0之间,包括它们)并不是均匀分布的。特别是靠近剪切面与远离剪切面处的比例,更是影响了z值的均匀分布。如果远平面的距离与近平面的距离的比为100,那么深度缓冲范围的90%就只消耗在了场景深度范围。一般的娱乐程序或视觉仿真程序要求远近平面的距离比在100010000之间。比值为1000时,98%的范围消耗在深度范围开始的2%上,并且随着比值的增大,分布将变得更糟。这样可能会使较远距离的物体上的隐藏表面产生失真,特别是当使用16-bit深度缓冲时。

  w深度缓冲比z-buffer能更均匀地在远近剪切面之间进行分配。使用它的最大好处就是远近剪切面的距离比不再是关键因素。这样就允许程序使用更大的距离范围,同时仍能保持深度缓冲与观察位置间的精确联系。w-buffer也不是完美的,有时也会使近处物体的隐藏表面产生失真。w-buffer的另一个缺点是不能得到硬件的广泛支持。

  DirectDraw HEL可以创建用于Direct3D或其它3-D渲染软件的执行缓冲。HEL支持16-bit深度缓冲。3-D加速卡的DirectDraw设备驱动器允许在显存中创建深度缓冲,要使用DDSCAPS_ZBUFFER标志。你可以通过调用IDirect3D3::EnumZBufferFormats方法来查询硬件支持的深度缓冲的位深度(bit-depth)。

  创建一个z-buffer表面时,程序应该保留指向这个z-buffer的指针,直到关闭Direct3D系统时为止。在释放渲染表面之前,应该先释放z-buffer表面。

  在渲染过程中,z-buffering会有一定的系统开销。使用z-buffer时,我们可以采用一些优化措施,详情见“Z-Buffer的性能”部分。

注:一个深度值的实际解释要视不同的3-D渲染器而定。

 

2. 使用深度缓冲

  下面我们来详细讨论深度缓冲的用法:

 

2.1 查询是否支持深度缓冲

  和其它一些特性一样,我们不能假定所使用的驱动器能够支持深度缓存运算;而应该查询驱动器的能力。几乎所有的驱动器都支持z深度缓存运算。如果试图使一个不支持的配置有效的话,驱动器并不会出现故障,而是返回到另一个深度缓存方法,有时也可能使深度缓存运算无效,这样就会使场景产生深度上的失真。

  在创建一个Direct3D设备之前,可以通过向DirectDraw查询所使用的显示设备来检查对深度缓冲的总体支持能力。如果DirectDraw对象报告支持深度缓存运算,那么从这个DirectDraw对象创建的任何硬件设备都将支持z-buffering(但是,我们仍然无法知道驱动器是否支持w-buffering)。

查询深度缓冲的总体支持能力

  1. 对显示设备调用DirectDraw对象的IDirectDraw4::GetCaps方法,将初始化过的DDCAPS结构作为参数。调用之后,DDCAPS结构就包含了有关DirectDraw的硬件和仿真能力的信息。
  2. 检查作为第一个参数的结构的ddsCaps成员。如果这个成员——一个DDSCAPS2结构——包含DDSCAPS_ZBUFFER标志,那么就表示驱动器支持使用z-buffer的深度缓存运算。

知道了驱动器支持z-buffer之后,就可以来检查w-buffer的支持能力了。所有的软件光栅都支持z-buffer,但是某些参考光栅也支持w-buffer,虽然它几乎不适用于real-world程序来使用。无论我们使用什么类型的设备我们都应该在使用w-based深度缓存运算之前,对w-buffer的支持能力进行检查。

确定是否支持w-buffer

  1. 创建设备之后(HAL或仿真设备),调用IDirect3DDevice3::GetCaps方法,并将初始化过的D3DDEVICEDESC结构传递给它的两个参数。
  2. 调用之后,dpcTriCapsdpcLineCaps成员(D3DPRIMCAPS结构)包含了有关设备对图元渲染的支持能力的信息。
  3. 如果这些结构的dwRasterCaps成员包含了D3DPRASTERCAPS_WBUFFER标志,那么对这种图元类型驱动器就支持基于w的深度缓存运算。

 

2.2 创建深度缓冲

  通常,我们在创建一个Direct3D设备对象之前的程序的启动阶段来创建深度缓冲。我们用下面的步骤来创建一个深度缓冲,并将它配属到渲染目标表面:

  1. 调用IDirect3D3::EnumZBufferFormats方法来决定深度缓冲的像素格式。
  2. 准备一个DDSURFACEDESC2结构,来描述一个与渲染目标表面大小相配的DirectDraw表面,包含DDSCAPS_ZBUFFER能力标志,并使用一种支持的深度缓冲像素格式(由步骤1得到)。
  3. 在显存或系统内存中创建表面,这要视程序使用的渲染设备的类型而定。(见“注”部分)
  4. IDirectDrawSurface4::AddAttachedSurface方法将深度缓冲配属于渲染表面。

  创建了深度缓冲并将它配属与渲染目标表面之后,调用IDirect3D3::CreateDevice方法来创建一个使用渲染目标表面和深度缓冲的渲染设备。

  当程序使用硬件驱动器时(HAL设备),在显存中创建深度缓冲——使用DDSCAPS_VIDEOMEMORY标志;使用软件仿真驱动器时(MMXRGB设备),在系统内存中创建深度缓冲——使用DDSCAPS_SYSTEMMEMORY标志。如果在适当的内存中创建深度缓冲失败了,那么就会导致CreateDevice方法的失败。

注:有些常用的硬件设备要求渲染目标与深度缓冲表面使用相同的位深度。丢与这些硬件,如果使用16-bit的渲染目标表面,那么相应的深度缓冲也要是16-bit的。对于32-bit渲染表面,深度缓冲必须是32-bit的;8-bit渲染表面要使用模板缓冲(如果需要的话)。

如果程序没有满足硬件的这些要求,那么使用非适应(non-compliant)表面来创建一个渲染设备的尝试将是失败的。你可以使用IDirectDraw4::GetDeviceIdentifier来跟踪硬件的这些限制。

 

2.3 启动深度缓冲

  创建了一个深度缓冲之后,只要调用IDirect3DDevice3::SetRenderState方法就可以启动深度缓冲。通过设置D3DRENDERSTATE_ZENABLE渲染状态来使之有效。使用D3DZB_TRUE(或TRUE)使z-buffering有效,使用D3DZB_USEW使w-buffering有效,或者使用D3DZB_FALSE(或FALSE)使深度缓冲无效。.

    注:要使用w-buffering,程序必须设置一个适当的投影矩阵,即使它不使用Direct3D变换管道,并且必须要使透视修正纹理映射有效。要时透视修正纹理映射有效,要将D3DRENDERSTATE_TEXTUREPERSPECTIVE渲染状态设置为TRUE。对于DirectX 6.0来说,这个值是默认的。

 

2.4 清空深度缓冲

  每次渲染一个新框架时,都应该将深度缓冲清空。可以使用IDirect3DViewport3::ClearIDirect3DViewport3::Clear2方法来进行这一操作。Clear方法在清空时使用“最深的”值,Clear2方法则允许你来设定一个任意的深度值。

  也可以使用DirectDraw来清空一个深度缓冲,调用深度缓冲表面的IDirectDrawSurface4::Blt方法来进行。DDBLT_DEPTHFILL标志会显示一个清空的深度缓冲正在使用blit。当这个标志声明了时,被传递给IDirectDrawSurface4::Blt方法的DDBLTFX结构应该已经经过初始化,并且它的dwFillDepth成员应该被设定为需要的深度。

  如果一个3-D硬件的DirectDraw设备驱动器被设计为能够对深度缓冲进行硬件清空操作,那么它会报告DDCAPS_BLTDEPTHFILL标志,并处理DDBLT_DEPTHFILL blit操作。一个深度填充的blit操作目的表面必须是一个深度缓冲表面。

 

2.5 改变深度缓冲写访问

  缺省情况下,Direct3D系统允许对深度缓冲进行写入操作。大多数的程序都会允许对深度缓冲进行些操作,但是有时也需要禁止写深度缓冲。

  调用IDirect3DDevice3::SetRenderState方法,并将dwRenderStateType参数设定为D3DRENDERSTATE_ZWRITEENABLE,将 dwRenderState参数设定为0,这样就可以使写深度缓冲操作无效。.

 

2.6 改变深度缓冲比较函数

  缺省情况下,在对一个渲染表面执行深度测试时,如果相应的每个点的深度值(zw)比深度缓冲中的值小,那么Direct3D系统会更新渲染目标表面。调用IDirect3DDevice3::SetRenderState方法,并将dwRenderStateType参数设定为D3DRENDERSTATE_ZFUNC,这样,我们就可以改变系统执行比较操作的方式。dwRenderState参数应该被设定为D3DCMPFUNC枚举类型中的一个值。

 

2.7 使用Z-偏移量

  在一个三维场景中,我们可以对共面的多边形使用z-偏移量来使它们不再共面。这项技术通常用于在场景中正确的显示阴影。例如,一堵墙上的阴影与这堵墙的深度值是相同的,如果我们先渲染了墙再来渲染阴影,那么阴影就有可能看不到,或者会出现一些深度上的失真。这时,我们可以颠倒渲染的顺序希望能使得到的效果也产生相应的颠倒,但是深度上的失真仍然无法避免。

  在渲染共面的多边形时,我们可以给z值增加一些偏移量,这样就能正确的显示共面的多边形了。要给多边形增加一个z-偏移量,我们可以在渲染前调用IDirect3DDevice3::SetRenderState方法,并将dwRenderStateType参数设定为D3DRENDERSTATE_ZBIAS,将dwRenderState参数设定为016之间的一个值(包括016)。如果我们将z-偏移量设的较高,那么与其它共面的多边形一同渲染时,就有可能使它更加明显。

 

九.模板缓冲

下面我们来讨论模板缓冲的用法以及相关内容。

 

1. 什么是模板缓冲?

  模板缓冲用来控制是否向渲染目标表面一个像素一个像素的绘制场景。在模板缓冲最基本的一级上,它能够使程序掩盖住被渲染图象的一部分,使它不能显示。我们可以使用模板缓冲来得到一些特殊的效果,如淡入淡出(dissolve)、贴纸(decaling)和勾画轮廓(outlining)等。详细内容件“模板缓冲技术”。

  模板缓冲的信息嵌入在z-buffer数据中。程序可以调用IDirect3D3::EnumZBufferFormats方法测试用户的硬件,看它是否支持模板缓冲。要得到有关z-buffer数据的详细格式的信息,可以调用IDirectDrawSurface4::GetPixelFormat方法,它将一个DDPIXELFORMAT结构传递给你的程序。相关的信息在dwZBufferBitDepthdwStencilBitDepthdwZBitMaskdwStencilBitMask成员中。

 

2. 模板缓冲如何工作

  Direct3D在模板缓冲上执行一个基于pixel-by-pixel的测试。对于目标表面上的每一个像素,它使用模板缓冲中相应的值——模板参考值(stencil reference value)——和模板掩模值(stencil mask value)来执行测试。如果测试通过,Direct3D就会执行一个动作。测试使用下面的步骤来进行:

  1. 将模板参考值与模板掩模进行逐位AND运算。
  2. 将当前像素的模板缓冲置于模板掩模进行逐位AND运算。
  3. 用比较函数比较前两步得到的结果。

如果写成伪代码形式,步骤如下:

(StencilRef & StencilMask) CompFunc (StencilBufferValue & StencilMask)

  上式中,StencilBufferValue是当前像素的模板缓冲的内容;伪代码使用&符号来表示逐位AND操作;StencilMask表示模板掩模的值;StencilRef表示模板参考值;CompFunc是比较函数。

  如果模板测试通过了,那么当前像素就会被写到目标表面,如果没有通过,则会将当前像素忽略掉。默认的比较行为是无论每个逐位操作得到什么结果都执行写像素操作*D3DCMP_ALWAYS标志)。我们可以改变D3DRENDERSTATE_STENCILFUNC渲染状态来改变这一行为,通过传递D3DCMPFUNC枚举类型的一个成员来声明我们所需的比较函数。

 

3. 定制模板缓冲

  程序可以对模板缓冲操作进行定制。可以设置比较函数,设置模板掩模和模板参考值。也可以控制在模板测试通过或失败时Direct3D所采取的措施。详细内容见“模板缓冲状态”。

 

十.顶点缓冲

  这一部分我们介绍在使用和理解顶点缓冲时需要了解的一些内容:

 

1. 什么是顶点缓冲?

  顶点缓冲,由IDirect3DVertexBuffer接口表示,实际就是用来包含顶点数据的简单的内存缓冲器。它可以包含任何顶点类型——transformed或者untransformed以及lit或者unlit。我们也可以在顶点缓冲中执行顶点操作,如变换、光线处理或产生裁剪标志等。(变换操作总要执行。)

  顶点缓冲对于要重复利用几何变换的分段顶点来说是比较理想的。我们可以只创建一个顶点缓冲,并且变换、光线处理和顶点裁剪都包含在其中,然后不再需要进行变换就可以对模型进行多次的渲染,甚至在交叉渲染状态(interleaved render state)发生变化时也可以。这在使用多个纹理渲染模型时是非常有用的:只需要进行一次几何变换,然后它的一部分就可以根据需要进行渲染,随着所需纹理的变化进行交叉存取。顶点处理之后的渲染状态改变会影响接下来的顶点处理。详细内容见“处理顶点”。

  我们可以对顶点缓冲的几何处理进行优化,使顶点操作与渲染达到最佳的性能。件“对顶点缓冲进行细节优化”。

    注:顶点缓冲使用DirectDrawSurface对象来进行内存管理服务。这样,顶点缓冲的访问就与DirectDrawSurface对象的访问比较相似了。实际上,IDirect3DVertexBuffer::Lock方法接受与IDirectDrawSurface4::Lock方法一样的标志。详细内容见“访问顶点缓冲内存”。

     

2. 顶点缓冲的描述

  我们用一个顶点缓冲的能力来对它进行描述:是否它只能存在于系统内存中,是否它只能使用写操作,它包含的顶点类型和数量,以及在创建时是否进行了优化。所有的这些特性都包含在一个D3DVERTEXBUFFERDESC结构中。

  对顶点缓冲的描述会告诉系统如何来创建一个顶点缓冲,并告诉程序一个已经存在的顶点缓冲是如何创建的(以及是否经过了优化)。在创建一个顶点缓冲时应该声明一个完整的描述,还要为系统提供一个空的描述结构体,使系统能将一个先前创建的顶点缓冲的描述填充在其中。有关的详细内容见“创建顶点缓冲”与“获得顶点缓冲描述”。

  dwSize成员是DirectX中最常用的,它用来确定结构的方案,并要在结构使用之前被设定为结构的大小。dwCaps结构成员包含了一般的能力标志。D3DVBCAPS_SYSTEMMEMORY标志表示系统应该在系统内存中创建(或者已经创建了)顶点缓冲。如果程序使用软件渲染设备,那么就要创建一个显式模式的(explicit)系统内存顶点缓冲;否则,最好通过省略标志来让系统决定最佳的创建位置。关于显式系统内存顶点缓冲,请看“顶点缓冲与设备类型”部分。

  dwCaps中的D3DVBCAPS_WRITEONLY标志的存在就表示顶点缓冲内存只能进行写操作。这样就可以使驱动器能够自由的对顶点数据进行最好的内存定位,使处理和渲染的速度得以提高。使用这个标志时,从顶点缓冲中读数据会使内存访问速度变得很慢。如果不使用D3DVBCAPS_WRITEONLY标志,驱动器可能为了读操作而将数据放在效率不是很好的地方,从而损失了一定的处理和渲染速度。如果没有声明标志,也就是假定程序会在顶点缓冲中执行读和写数据操作。

注:在创建顶点缓冲过程中,不使用D3DVBCAPS_OPTIMIZED标志。在对顶点缓冲进行优化时,系统才使用这一标志。

  最后两个D3DVERTEXBUFFERDESC结构成员用来描述其它的特性。dwFVF成员包含了一个可塑(flexible)顶点格式标志的组合,用来确认顶点的类型。顶点缓冲的容量用它所包含的所有顶点的数量来表示,在dwNumVertices成员中给出。

 

3. 顶点缓冲与设备类型

  一个Direct3D软件渲染设备只能使用显式(explicit)系统内存顶点缓冲。要在系统内存中创建顶点缓冲,就要在提供给IDirect3D3::CreateVertexBuffer方法的描述结构体中包含D3DVBDESC_SYSTEMMEMORY标志。

  如果程序使用硬件加速设备,最好忽略掉D3DVBDESC_SYSTEMMEMORY标志。这样就可以使系统根据性能的需要在内存中来安排顶点缓冲的位置,但是设备在使用顶点缓冲时又可以不用考虑它的位置。

 

4. 使用顶点缓冲

  下面我们讨论如何使用顶点缓冲:

 

4.1 创建一个顶点缓冲

  下图展示了创建一个顶点缓冲所需的一些步骤:

pic92.gif (5678 bytes)

  调用IDirect3D3::CreateVertexBuffer方法创建一个顶点缓冲对象,它有四个参数。第一个参数是一个D3DVERTEXBUFFERDESC结构的地址,同它来描述顶点的格式、缓冲器的大小和其它一些特性。通常,系统会为顶点缓冲安排最佳的内存位置(安排在显存或系统内存中)。但是,软件渲染设备只能使用显式系统内存顶点缓冲。

  第二个参数是一个变量的地址,如果函数调用成功的话,这个变量是一个指向顶点缓冲对象的新的IDirect3DVertexBuffer接口的指针。第三个参数用来决定顶点缓冲是否有能力包含裁剪信息——以裁剪标志的形式。将它设置为0表示创建一个“有裁剪能力”的顶点缓冲;如果使用D3DDP_DONOTCLIP标志,就表示不包含裁剪标志。如果程序指明了包含transformed顶点(D3DFVF_XYZRHW标志包含在描述结构体的dwFVF成员中),那么就只能使用D3DDP_DONOTCLIP标志。如果指明了将包含untransformed顶点(D3DFVF_XYZ标志),那么CreateVertexBuffer方法将会忽略D3DDP_DONOTCLIP标志。裁剪标志需要占用额外的内存,这就使得有裁剪能力的顶点缓冲要比不包含裁剪标志的顶点缓冲大一些。由于这些资源要在创建顶点缓冲时进行分配,因此必需提早请求一个有裁剪能力的顶点缓冲。

注:创建一个包含裁剪标志的顶点缓冲并不意味着一定要在顶点处理或进行渲染时来产生裁剪标志。每一个顶点缓冲渲染方法都可以使用D3DDP_DONOTCLIP标志在渲染时将裁剪操作绕过,并且IDirect3DVertexBuffer::ProcessVertices方法还接受D3DVOP_CLIP标志,它可以在渲染顶点时被忽略,从而阻止系统产生裁剪标志。

如果一个顶点缓冲不支持裁剪标志,那么我们也就没有办法再为它产生裁剪标志了。尝试使用IDirect3DVertexBuffer::ProcessVertices方法来为它产生裁剪标志会在调试时出现错误,会得到一个D3DERR_INVALIDVERTEXFORMAT错误标志。当从一个不包含裁剪标志的transformed顶点缓冲来渲染时,渲染方法会忽略所有的裁剪请求。

  CreateVertexBuffer的最后一个参数用来提供给将来的COM聚合特性使用。目前,它是不支持聚合的,因此这个参数应该被设定为NULL

  下面是创建一个顶点缓冲的例子:


/*
* For the purposes of this example, the g_lpD3D variable is the
* address of an IDirect3D3 interface exposed by a Direct3D
* object, and the fIsAHardwareDevice variable is a BOOL variable
* that is assumed to be set during application initialization.
*/

D3DVERTEXBUFFERDESC vbdesc;
ZeroMemory(&vbdesc, sizeof(D3DVERTEXBUFFERDESC));
vbdesc.dwSize = sizeof(D3DVERTEXBUFFERDESC);
vbdesc.dwCaps = 0L;
vbdesc.dwFVF = D3DFVF_VERTEX;
vbdesc.dwNumVertices = NUM_FLAG_VERTICES;

// If this isn't a hardware device, make sure the
// vertex buffer uses system memory.
if( !fIsAHardwareDevice )
 vbdesc.dwCaps |= D3DVBCAPS_SYSTEMMEMORY;

// Create a clipping-capable vertex buffer.
if(FAILED(g_lpD3D->CreateVertexBuffer(&vbdesc,&g_pvbVertexBuffer, 0L,NULL)))
 return E_FAIL;

 

4.2 访问顶点缓冲内存

  顶点缓冲对象允许程序直接访问放置顶点数据的内存空间。调用IDirect3DVertexBuffer::Lock方法可以得到一个指向顶点缓冲内存的指针,然后,可以根据需要访问内存,将新数据添入缓冲器,或者读出其中包含的数据。

  IDirect3DVertexBuffer::Lock方法有3个参数。第一个参数是dwFlags,它通知系统内存应该如何被锁定,还被用来提示程序将如何访问缓冲器中的数据。(可以提示只读或只写访问,这样就允许驱动器将内存锁定,从而为所需的访问类型提供最佳的性能。这些提示并不是必需的,但却能在一定程度上提高内存访问的性能。)由于顶点缓冲使用DirectDrawSurface对象来包含顶点数据,因此IDirect3DVertexBuffer::Lock方法接受的标志就与IDirectDrawSurface4::Lock方法接受的标志是一样的,并且有相同的结果。

  第二参数是lplpData,它是一个LPVOID变量的地址,如果调用成功的话,变量就会包含一个指向顶点缓冲内存的指针。最后一个参数是lpdwSize,它也是一个变量的地址,这个变量包含了缓冲器的大小(字节形式)。如果程序不需要有关缓冲大小的信息的话,可以将lpdwSize设置为NULL

性能注释:如果程序只从顶点缓冲内存中读数据,可以使用DDLOCK_READONLY标志。使用这个标志可以使Direct3D优化它的内部程序以提高效率,也就是只对内存进行只读访问。我们也可以对使用DDLOCK_READONLY标志的锁定的内存进行写操作,但是这样会得到难以预料的结果。另外,试图从一个使用D3DVBCAPS_WRITEONLY标志创建的顶点缓冲中读出数据会非常的慢,即使你将缓冲器锁定为只读访问。

  顶点缓冲内存本身只是一个很简单的顶点数组,它们以可塑(flexible)顶点格式来进行声明。如果程序使用遗留的顶点结构体D3DVERTEXD3DLVERTEXD3DTLVERTEX,那么跨距(stride)就是结构体的大小(字节形式)。如果使用不同于遗留格式的顶点格式,那么就要使用你所定义的顶点格式结构体的跨距。可以通过检查顶点缓冲描述中的可塑(flexible)顶点格式标志来计算每个顶点的跨距。下表显示了每种顶点成分的大小:

顶点格式标志

大小

D3DFVF_DIFFUSE

sizeof(DWORD)

D3DFVF_NORMAL

sizeof(float) × 3

D3DFVF_SPECULAR

sizeof(DWORD)

D3DFVF_TEXn

sizeof(float) × 2 × n

D3DFVF_XYZ

sizeof(float) × 3

D3DFVF_XYZRHW

sizeof(float) × 4

  顶点格式中纹理坐标的序号用D3DFVF_TEXn标志来表示(n08)。由于每个纹理坐标设置都占用两个浮点变量的空间,因此将一个纹理坐标设置所占的大小乘以纹理坐标设置的序号,就得到了这一纹理坐标设置所占用的内存的大小。

  根据具体的顶点的需要,我们将内存指针加上或者减去顶点跨距的总和。

 

4.3 处理顶点

  处理顶点缓冲中的顶点时,可以使用设备的当前变换矩阵,也可以随意的使用其它顶点操作,如光线处理、产生裁剪标志以及修正范围(updating extent)等等。IDirect3DVertexBuffer接口提供了IDirect3DVertexBuffer::ProcessVertices方法来处理顶点。

  当我们要将一个源顶点缓冲中的顶点处理到一个目的顶点缓冲中时,我们应该调用目的缓冲器的ProcessVertices方法,而不是源顶点缓冲该方法。这个方法有7个参数,它们分别用来描述要执行的操作、源缓冲器的IDirect3DVertexBuffer接口的位置、执行顶点操作的渲染设备、以及方法所指向的顶点的位置及数量。调用之后,目的缓冲器中就包含了处理过的顶点,而源缓冲器中的内容没有变化。

  准备处理顶顶点时,先设置第一个参数dwVertexOp,指明想要执行的顶点操作。必需包含D3DVOP_TRANSFORM标志,否则方法将会调用失败,但是剩下的操作都是可选的。在处理时,可以包含其它可选标志,光线处理、产生裁剪标志、修正范围(update extent)等等,的任意的组合。

  第二和第三个参数,dwDestIndexdwCount,反映了目的缓冲器中的索引以及要进行处理的顶点的总数。第四个参数,lpSrcBuffer,应该被设置为包含源顶点的顶点缓冲对象的IDirect3DVertexBuffer接口的地址。dwSrcIndex声明了方法开始处理顶点时所用的索引。(源顶点的总数隐含在dwCount参数中。)lpD3DDevice参数应该被设定为进行顶点处理的渲染设备的IDirect3DDevice3接口的地址。最后一个参数是为了将来使用而保留的,应该被设定为0

  在创建顶点缓冲时要注意使用一致的顶点格式。至少源顶点缓冲应该包含untransformed顶点(使用缓冲器描述中的D3DFVF_XYZ 标志),目的缓冲器应该包含transformed顶点(使用D3DFVF_XYZRHW标志)。另外,光线处理或裁剪处理都应有适当的作用域。例如,当顶点格式不包含顶点法向量时,就不需要进行光线处理。同样的,如果一个目的缓冲器没有裁剪能力,我们也不能要求对它产生裁剪标志。任何不兼容的操作都会在调试时出现错误。

  当源或目的缓冲器被锁定时,不能进行顶点处理。

 

4.4 优化顶点缓冲

  对顶点缓冲进行优化可以使系统调整缓冲器的内容,使得在处理或渲染顶点时能有更好的性能。顶点如何来进行优化与具体的平台有关,并有不同的变化。因此,我们不能锁定或者另外访问一个优化了的顶点缓冲的内容。

  调用IDirect3DVertexBuffer::Optimize方法来进行顶点缓冲的优化。IDirect3DVertexBuffer::Optimize方法有两个参数,但是只有一个是可用的。lpD3DDevice参数应该被设定为进行顶点优化的设备的地址,另一个参数应该设定为0。锁定的顶点缓冲不能进行优化。

  通过顶点优化可以提高程序的性能。但是,经过优化的顶点缓冲只能用来进行静态几何处理(static geometry),这是因为,一旦缓冲器进行了优化,我们就不能锁定它来改变优化的内容了。缓冲器经过优化之后,只能使用IDirect3DVertexBuffer::ProcessVertices方法和顶点缓冲渲染方法。

 

4.5   从顶点缓冲开始渲染

  我们在“渲染”部分中曾经讨论过,IDirect3DDevice3接口中包含了从顶点缓冲渲染图元的方法。这些方法与接口中的其它渲染方法的工作方式是一样的,但是使用的参数是不同的。

 

4.5.1 关于顶点缓冲渲染

  一般有两种情况我们要使用顶点缓冲进行渲染。从最基本的层次来讲,这两种情况根据顶点缓冲中顶点的类型来进行分类,但是间接上,程序使用的过程也决定了何时使用顶点和渲染操作。下图中展示了从一个untransformed顶点缓冲来进行渲染的过程。

pic93.gif (8106 bytes)

  如上图所示,IDirect3DDevice3::DrawPrimitiveVBIDirect3DDevice3::DrawIndexedPrimitiveVB方法有能力从一个non-transformed顶点缓冲来进行渲染。这样,每次调用一个渲染方法时,系统就执行顶点和渲染操作。对于DirectX 6.0,使用这种方法并不比使用传统的DrawPrimitive渲染方法能提高多少性能,但是,它却更适于某些情况。如果可能的话,我们可以通过重新利用transformed顶点数据来进行性能优化,如下图所示:

pic94.gif (15782 bytes)

  在这种情况下,程序创建了两个顶点缓冲:一个用于untransformed几何处理,另一个用于transformed几何处理。当调用IDirect3DVertexBuffer::ProcessVertices方法时,第二个顶点缓冲接收到transformed顶点数据。ProcessVertices方法从源缓冲器中读出顶点,对它们执行所需的顶点操作,并将结果放置在目的缓冲器中。你可以对transformed顶点调用与untransformed顶点同样的渲染方法。但是,与untransformed顶点不同的是,Direct3D自动检验顶点缓冲中的顶点是transformed,然后传递给光栅。通过减少不必要的变换使性能消耗达到最小。

有关优化的详细内容见“优化顶点缓冲”。

 

4.5.2 调用顶点缓冲渲染方法

  IDirect3DDevice3::DrawPrimitiveVB方法与IDirect3DDevice3::DrawPrimitive方法相对应。DrawPrimitiveVB方法假定顶点以它在顶点缓冲中的连续的顺序。

  DrawPrimitiveVB方法有5个参数。使用D3DPRIMITIVETYPE枚举类型的成员来设置d3dptPrimitiveType参数,它用来指明正在被渲染的图元的类型。第二个参数lpd3dVertexBuffer用来声明包含顶点的顶点缓冲的地址,然后设置dwStartVertexdwNumVertices参数的值,它们分别表示要进行渲染的第一个顶点和顶点的总数。我们并不一定要渲染缓冲器中所有的顶点,但是必须使用与图元类型(在D3DPRIMITIVETYPE中)相符的顶点类型。最后一个参数是dwFlags,它用来决定渲染行为。这里使用的标志与其它渲染方法使用的标志是一样的。我们可以通过设置标志来打开光线和裁剪处理,或者在渲染时更新视口的内容。

  IDirect3DDevice3::DrawIndexedPrimitiveVB方法通过索引使用顶点缓冲中的顶点来绘制图元。它的第一、第二个参数与DrawPrimitiveVB方法的参数相同。第三个参数lpwIndices,是一个有顺序的WORD索引数组,函数用它来访问要被渲染的顶点。第四个参数dwIndexCount,是数组包含的索引值的总数。dwFlags参数与DrawPrimitiveVB方法中相应的参数相同。

  如果缓冲器中的顶点格式不包含顶点法向量的话,那我们也就不能要求顶点画缓冲器渲染方法来执行光线操作。同样的,当我们从一个由D3DDP_DONOTCLIP标志创建的transformed顶点缓冲进行渲染时,我们也不能要求渲染方法对顶点进行裁剪。(对于untransformed顶点缓冲,允许进行裁剪操作。)如果我们要求了无效的操作,不会在调试编译阶段出现失败,但是操作将不会得到执行。

  一个软件渲染设备只能从以D3DVBCAPS_SYSTEMMEMORY标志创建的顶点缓冲来进行渲染。详细内容,请参看“顶点缓冲与设备类型”。

 

4.6 获得顶点缓冲描述

  在运行时,程序往往需要得到已经存在的顶点缓冲的信息。这时,我们可以使用IDirect3DVertexBuffer::GetVertexBufferDesc方法。使用顶点缓冲的IDirect3DVertexBuffer接口来调用这个方法可以得到缓冲器的描述。

  GetVertexBufferDesc只有一个参数:一个正确初始化的D3DVERTEXBUFFERDESC结构的地址。要初始化这个结构体,要将dwSize设置为结构体的大小(字节形式),将其他成员都设置为0。函数返回后,结构体中就包含了有关顶点缓冲的能力、缓冲器中顶点的格式以及顶点总数等信息。

注:在D3DVERTEXBUFFERDESC结构的dwFlags成员中设置D3DVBCAPS_OPTIMIZED能力标志可以优化一个顶点缓冲。如果设置了这个能力标志,缓冲器就不能被锁定了,并且它的内容将只对渲染方法和IDirect3DVertexBuffer::ProcessVertices方法有效。

 

上一页 | 目录 | 下一页