7.  模板缓冲技术

  我们使用模板缓冲来对图象中的像素进行掩模处理,它可以决定是否绘制该像素。详细内容清参看“模板缓冲”部分。

  使用模板缓冲,Direct3D程序能够得到很多特殊的效果。我们将一些常用的效果列在下面:

 

7.1 渐隐、淡入淡出与Swipe

  现在,越来越多的特殊效果出现在电影和视频图象中,这其中就包括了渐隐、淡入淡出以及swipes

  渐隐技术就是将一幅图象逐渐的用另一幅图象替换掉。我们可以使用Direct3D的多纹理融合技术来达到这样的效果,但是我们一般还是选用模板缓冲来完成这一任务。这样,我们就能在使用模板缓冲进行渐隐处理时,仍然可以使用多纹理融合来进行其他效果的处理。

  当程序执行渐隐操作时,要渲染两个不同的图象。这时,使用模板缓冲来控制将哪幅图象的像素绘制到渲染目标表面。我们可以定义一系列模板掩模,并将它们拷贝到模板缓冲中。另一方面,我们也可以为第一帧定义一个基本的模板掩模,然后随着帧的变化,再对基本掩模进行适当的改变。

  在渐隐操作开始时,我们设置的模板函数和模板掩模应该使开始图象的大部分像素能够通过测试;而对于终止图象,它的大部分像素则不应该通过测试。对于连续的帧,模板掩模要不断变化,这样才能使能够通过测试的开始图象的像素不断的减少,而随着帧的变化,越来越多的终止图象的像素将能够通过测试。通过这样的处理,我们就可以使用任意的图象来实现渐隐效果了。

  淡入淡出实际上是渐隐技术的一种特殊情况。淡入技术就是由一幅黑色或白色的图象逐渐变换为一幅场景中的图像。淡出则刚好相反,它是由一个场景图象逐渐变换为黑色或白色。

  Direct3D程序还可以使用另一种类似的技术——swipe。例如,如果我们执行了一个从右至止左的swipe操作,最终的图象就会逐渐从右至左的滑出到开始图象的上面。同执行渐隐操作时一样,我们需要定义一些列的模板掩模,并将这些掩模加载到连续变化的帧的模板缓冲中;或者,我们可以对开始模板掩模进行连续的调制,再将调制后的掩模值应用到图象上。我们用这些模板掩模来控制开始图象和终止图象的像素的填写,从而实现预想的效果。

  一个swipe操作要比一个渐隐操作更加复杂,因为它要按照与swipe相反的顺序来读取像素。也就是说,当一个swipe操作从右到左执行时,那么程序就必须在终止图象中从左到右的读取像素。

 

7.2 贴纸

  Direct3D程序用贴纸技术来将一个特殊图元图象中的像素绘制到渲染目标表面。我们可以对具有共面多边形的图元使用这一技术,这样可以保证它们能够被正确的渲染。

  现在我们举一个例子,我们要把一些轮胎痕迹以及黄色警戒线渲染到一条公路上。这时,这些痕迹就会直接位于公路的表面上,因此,痕迹的z值就与公路的z值完全相同。这样,深度缓冲就不能清楚的将它们两者分离开来,位于后面的图元上的一些像素就会被渲染到前面的图元上。最终的图象就会在帧与帧之间产生微弱的闪光。这种效果,我们称之为“Z Fighting”或“flimmering”。

  要解决这一问题,我们就要使用一个模板来将贴纸出现地方的后面图元的像素掩模掉。然后,关闭z-buffering,将前面图元的图象渲染到目标表面上被掩模掉的区域上。

  我们也可以使用多纹理融合来解决这一问题,但是,这样做就会限制其他的使用多纹理融合才能实现的效果的数量。而使用模板缓冲就可以节省多纹理融合的能力,从而能够实现更多的特殊效果。

 

7.3 合成

  我们可以使用模板缓冲来将2-D3-D图象合成到一个3-D场景中。我们用模板缓冲中的一个掩模来封闭渲染目标表面上的某个区域,然后就可以将2-D信息,例如文本或位图,填写到被封闭的区域。另一方面,程序还可以在渲染目标表面上被模板掩模掉的区域绘制额外的3-D图元,甚至可以绘制一个完整的场景。

  游戏中经常将多个3-D场景合成在一起。例如,驾驶类游戏中通常要显示一个后视镜,镜中显示了驾驶员背后的3-D场景,这时,我们就可以用合成技术来将后视的三维场景合成到后视镜中。

 

7.4 轮廓与剪影

  模板缓冲还能够用来实现一些抽象的效果,如轮廓与剪影。

  如果我们将一个模板掩模应用到一个与图元有相同形状但是尺寸较小一些的图象上,那么最终的图象就会得到这个图元的轮廓。然后,我们可以在模板掩模的区域填充一个具有固定颜色的图象,从而得到一种类似于浮雕的效果。

  如果模板掩模的大小和形状与图元的大小和形状完全一样,那么最后的图象就会在图元存在的地方出现一个“洞”。我们可以将这个“洞”填充上黑色,这样就得到了这个图元的剪影效果。

 

8.  彩色光

  D3DLIGHT2结构的dcvColor成员声明了一个D3DCOLORVALUE结构。这个结构定义的颜色值是RGBA值,范围从010代表黑色。我们不必将光线的颜色值限制在这个范围内,超出这个范围的颜色值往往会产生一些特殊的效果。例如,我们可以将颜色设置为一个很大的值,这样就可以创建在场景中创建一个强光。也可以将颜色设置为一个负值来创建黑色光,它实际上将光线从场景中删除掉了,通常,这种方法用来加强阴影效果或者是其他一些特殊的效果。

  当我们使用单色光模式时,环境光就会是单色的,这样,我们就无法使场景比当前的环境光等级更暗。另外我们还要注意,RGB模式下的彩色光在单色模式中会转变成灰度光;一个RGB模式中的红色光在单色模式中将会是一个暗淡的白色光而已。

 

9.   反走样

  使用反走样技术可以减少图象的锯齿状走样——也就是图象中的非水平和垂直线都呈阶梯状。在三维场景中,这种失真经常会在不同颜色的多边形交界的地方变得非常明显。反走样技术能够有效的将这些边界处的像素进行融合,从而使场景看起来更加自然。

  Direct3D支持两种反走样技术:边缘反走样(edge antialiasing)和全表面反走样(full-surface antialiasing)。具体使用哪种技术要视我们对性能和视觉效果的要求而定。

 

9.1 边缘反走样

  在边缘反走样技术中,我们先渲染一个场景,然后再重新渲染走样了的对象的轮廓。系统重新绘制这些边缘,使它们变得稍微模糊从而减少失真。

 如果一个设备支持边缘反走样,那么D3DPRIMCAPS结构中的D3DPRASTERCAPS_ANTIALIASEDGES能力标志会有所反映(调用IDirect3DDevice3::GetCaps方法)。如果真的支持,要将D3DRENDERSTATE_EDGEANTIALIAS标志设置为TRUE,然后使用IDirect3DDevice3::DrawPrimitiveD3DPT_LINESTRIPD3DPT_LINELIST图元类型中的一个来重新绘制场景中的边缘。边缘反走样对于图元是没有定义的,因此在反走样结束之后,要将D3DRENDERSTATE_EDGEANTIALIAS重新设置为FALSE

  重新绘制场景中的每一个边缘可以减少主要的失真,但是它的计算量是很大的。另外,决定哪些边缘是走样的也是非常困难的。一般来说,位于颜色变化较大或者是材质变化较明显的边缘处的走样问题是比较重要的。对于两个颜色相同的多边形的边缘进行反走样是没有意义的。基于这些原因,我们一般还是选择全场景反走样。

 

9.2 全场景反走样

  全场景反走样是指在一个通道内(不需要第二个通道)进行光栅时,将每个多边形的边缘都进行模糊处理。我们要注意的是,全场景反走样只对三角形和三角形组有效;使用Direct3D是不能对线进行反走样的。

  在某些硬件上,只有当程序渲染从后到前分类的多边形时,才能使用全场景反走样。我们可以用IDirect3DDevice3::方法来检查硬件是否具有这样的特性。调用了GetCaps方法后,检查相关的D3DPRIMCAPS结构的dwRasterCaps成员。如果设备需要从后到前(back to front rendering)的渲染,那么该成员中就会有D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT就会有能力标志。如果设备执行反走样时不考虑多边形的顺序,那么dwRasterCaps成员中就会有D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT标志。当然,两个标志都没有的话也就是表示设备不能执行全场景反走样。

  检查完多边形顺序的支持后,要将D3DRENDERSTATE_ANTIALIAS渲染状态设置为D3DANTIALIAS_SORTDEPENDENTD3DANTIALIAS_SORTINDEPENDENT并绘制场景。

  当程序不再需要全场景反走样时,要将D3DRENDERSTATE_ANTIALIAS设置为D3DANTIALIAS_NONE来使它无效。

 

十二.GUIDs

  Direct3D使用全局统一标识符即GUIDs来确定部分接口。当我们使用QueryInterface方法来决定一个对象是否支持一个接口时,我们可以使用接口的GUIDs来确定我们所感兴趣的接口。

  要成功的使用GUIDs,必须在其他的includedefine描述之前定义INITGUID,也可以直接联接Dxguid.lib库。我们只能在一个源模块中定义INITGUID

  不同的语言,如CC++,也会使GUIDs的使用有所不同。在C语言中,要给GUID传递一个指针(例如&IID_IDirect3D),但是在C++中,需要传递一个引用(如简单的IID_IDirect3D)。

 

十三.性能优化

  我们在创建实时三维图形程序时,都要考虑到性能优化的问题。下面这一部分内容向我们提供了一些提高性能时要注意的原则:

 

1. 数据库与Culling

  在Direct3D中,为世界中的对象建立一个可靠的数据库往往可以得到很好的性能,这一点要比提高光栅或硬件的性能更加重要。

  我们应该尽量减少可能管理的多边形的数量。在开始时,要建立低聚合模型(low-poly models),然后在不降低性能的前提下,在以后的处理过程中可以添加必要的多边形。要尽量将多边形的总数控制在2500个左右。我们要记住这样一句话“最快速的多边形是不需要绘制的多变形”。

 

2. 图元批处理

  为了在执行过程中得到最佳的性能,我们要尽量对图元成批的进行处理,并尽量保持渲染状态不要变化。举个例子,如果我们的一个对象有两个纹理,我们要先将使用第一个纹理的三角形组织起来进行渲染,然后进行必要的渲染状态改变,再将使用第二个纹理的三角形组织起来进行渲染。我们可以通过HAL来调用对渲染状态批处理和图元批处理的最简单的硬件支持。如果我们将指令有效的组合在一起,那么在执行过程中对HAL的调用就会减少,性能也就会相应的得以提高。

 

3. 光线使用技巧

  由于光线处理需要较大的计算量,因此如果我们更加仔细的处理程序中的光线话,往往能得到较大的性能提高。下面提到的一些技巧都源自于这句话“最快速的代码就是不需要调用的代码”。

 

4. 纹理大小

  纹理映射的性能在很大程度上依赖于内存的速度。我们有一些方法可以提高程序在处理纹理时的高速缓冲器的性能。

 

5. 三角形标志

  D3DTRIANGLE结构的wFlags成员包含的一些标志允许程序在建立三角带和三角扇的时候重复利用顶点。有效的使用这些标志可以大大提高某些硬件的速度。

  程序可以有两种方式来使用这些标志:

D3DTRIFLAG_STARTFLAT(len)

  如果当前三角形经过Culling操作,那么驱动器同样可以在带或扇中对由len给出的数量的后续三角形进行Culling操作。

D3DTRIFLAG_ODD and D3DTRIFLAG_EVEN

  驱动器只需要从三角形加载一个新的顶点就可以重新使用上一个三角形的中其他的两个顶点。

  当程序同时使用D3DTRIFLAG_STARTFLAT标志和D3DTRIFLAG_ODDD3DTRIFLAG_EVEN标志时,可以获得最好的性能。

  由于某些驱动器不会检查D3DTRIFLAG_STARTFLAT标志,因此在使用它时,程序必须格外的小心。如果程序使用了没有检查这一标志的驱动器,可能会将已经渲染过的多边形再渲染一遍。

  在使用D3DTRIFLAG_ODDD3DTRIFLAG_EVEN标志之前,要先使用D3DTRIFLAG_START标志。D3DTRIFLAG_START标志使驱动器重新加载所有三个顶点。如果三角形相邻的话,那么所有紧跟在3DTRIFLAG_START标志之后的三角形都可以使用D3DTRIFLAG_ODDD3DTRIFLAG_EVEN标志。

  本SDK的调试版中,D3DTRIFLAG_ODDD3DTRIFLAG_EVEN标志都有效。

 

6. 执行中的裁剪测试

  使用执行缓冲的程序可以使用IDirect3DDevice::Execute方法来渲染带有自动裁剪处理或不带此功能的图元。不进行裁剪处理时使用这种方法要比设置裁剪标志时快一些,因为变换或光栅阶段进行的裁剪测试会使进程放慢。如果程序不使用自动裁剪,就要确保所有的渲染数据都在视锥体内。这时,最好的方法就是对模型使用简单的边界体(bounding volume),并首先对它们进行变换。使用第一次变换的结果来决定是否放弃位于视锥之外的渲染数据,是否对视锥内的数据使用IDirect3DDevice::Execute方法的非裁剪版本,或者对部分位于视锥内的数据使用裁剪标志。在立即模式中,我们可以使用D3DSTATUS结构的标志,并且当一个边界量在视锥内时使用D3DOPCODE枚举类型的D3DOP_BRANCHFORWARD成员将几何体跳过,通过这些,我们就可以完成上述的一系列功能。Direct3D保留模式会自动使用这些特性来提高执行缓冲的处理速度。

 

7. 一般的性能技巧

  我们可以在程序中采用一些一般的方法来提高性能。

 

8. Ramp性能注意事项

  Direct3D程序可以使用RGB软件仿真设备,也可以使用ramp软件仿真设备。下面我们来讨论如何在使用ramp设备时提高性能:

注:ramp软件渲染在DirectX 6.0和以后的版本中将不再予以支持。使用IDirect3D3接口无法创建ramp设备,也不能对已有的ramp设备进行查询。同样,我们可以知道ramp设备不支持任何多纹理融合操作。本文中的ramp设备使用的是IDirect3D2、IDirect3DDevice2或更早的接口。

 

8.1 Ramp纹理

  使用ramp设备的程序应该对使用的纹理颜色进行限制。因为一个单色纹理中的每一种颜色在渲染时都需要自己的查询表。如果程序在场景中使用了很多颜色,那么系统就必须使用很多的查询表。同样,如果可能的话,尽量使纹理能够使用同样的调色板。理想情况当然是使用一个调色板了。

 

8.2 Copy纹理融合模式

  使用ramp设备的程序有时可以使用D3DTEXTUREBLEND枚举类型中的D3DTBLEND_COPY纹理融合模式来提高性能。这种模式为软件光栅进行了优化处理;对于使用HAL的程序,它同D3DTBLEND_DECAL纹理融合模式是等价的。

  Copy模式是光栅最简单的形式,因此它是最快速的。当我们使用copy模式时,纹理上将不会执行光线和明暗处理操作。从纹理来的字节将直接拷贝到屏幕,并用每一个顶点的纹理坐标映射到多边形上。这样,在使用copy模式时,程序的纹理必须在主表面上使用相同的像素格式。当然也要使用同一个调色板。

  程序在使用8-bit的没有光线的单色模型时,如果使用copy模式,性能将会得到提高。而对于使用16-bit颜色的程序,copy模式将没有使用调制纹理(modulated texture)那么快速;对于16-bit颜色,纹理大小是8-bit时的两倍,并且高速缓存的额外负担也会使性能比使用8-bit的有光照纹理时有显著下降。

  Copy模式只执行两种光栅选项,z-buffering和色度键透明(chromakey transparency)。最快的方式是将纹理像素映射到多边形上,不进行透明和z-buffering处理。使用色度键透明会是不可见像素的光栅速度有所增加,因为它只需要执行纹理的读操作;但是可见像素的性能将会有所降低,因为它要进行色度键测试。

  对于8-bit copy模式,启动z-buffering会使性能大幅降低。当z-buffering有效时,程序必须要读一个16-bit值,并且会有条件的对每个像素进行写操作。即使如此,如果平均overdraw超过2并且场景按照从前到后的多边形顺序进行渲染时,z-buffering有效的copy模式还是要比无效时要快。

  如果场景overdraw小于2(这一点很可能发生),那么就不应该在copy模式中使用z-buffering。只有当场景非常复杂时,这一规则才有可能被破坏。举个例子,如果一个场景中有大约1500个多边形要进行渲染,那么排序的系统开销将会很高。这时,启动z-buffering还是值得考虑的。

  当Direct3D需要绘制的只是一个长的三角形指令时,它的速度是最快的。渲染状态的改变对这种情况是一种阻碍;而三角形指令的平均长度越长,那么总的三角形处理能力就会越好。因此,当场景中的所有纹理都来自于一个纹理映射时,程序的性能将会是最好的。尽管这样做会有纹理坐标不能大于1.0限制,但它却完全避免了纹理状态的改变,因此对性能提高很有好处。

  对于一般的简单场景,尽量使用一个纹理,一个材质,并对三角形进行排序(sort)。当场景非常复杂时再使用z-buffering

 

8.3 Ramp性能技巧

  在使用单色驱动器时,我们可以使用下面的一些技巧来提高程序的性能:

  要使用多个调色板时,可以将一个调色板作为主调色板,并使其它的调色板中包含主调色板颜色的一个子集,这样也可以提高性能。

 

9. Z-Buffer性能

  在使用z-buffering和纹理处理时,如果我们按照从前到后的顺序对场景进行渲染,那么我们就可以提高程序的性能。带纹理并经过z-buffer处理的图元会在扫描线的基础上进行预检,如果扫描线被一个渲染过的多边形遮挡住了,那么系统就会放弃掉它。Z-buffering可以用来提高性能,但是它最大的用处还是在一个场景包含了大量overdraw的时候。overdraw就是指一个屏幕上的像素进行写操作的平均次数,它是很难精确计算的,但我们可以得到一个比较接近的估算值。如果overdraw的平均值小于2,那么关闭z-buffering可以得到很好的性能,并要按照从前到后的顺序来进行渲染。

  我们也可以对图元进行z值检测来提高程序的性能,也就是对一个给定的相对于z-buffer的图元列表进行检测。如果使用z-可见性检测来渲染一个复杂物体的边界盒,那么我们很容易发现物体是否是完全可见的。如果该物体是隐藏的话,那我们就要避免在开始时渲染该物体。现在假设我们有一个摄像机在一个充满3D物体的房间中,紧挨着它由另一间同样充满3D物体的房间,它们通过一扇门相通。我们渲染第一个房间,然后再使用z值检测来渲染到第二个房间去的通道,如果这时发现通道被第一个房间中的一个物体挡住了,那么我们就不再需要渲染第二个房间中的东西,因为在第一个房间中,我们根本就看不到第二个房间中的任何物体。

  对于高速的个人计算机,在系统内存中进行软件渲染要比在显存中进行渲染要快一些,当然,这样我们就无法使用双缓冲器或是硬件加速清空操作了。如果程序既能在系统内存中进行渲染,又能在显存中进行渲染,并且能够检测两种渲染途径的速度,那么我们就能够利用当前系统的最佳渲染途径来进行渲染。本SDKDirect3D例程中包含有这种方法的描述。我们有必要对两种方法都进行一下运行,因为再没有别的方法来进行速度测试了。不同的计算机的速度也是不同的,这要看它们采用的是主内存(main-memory)式的体系结构还是使用了哪种图形适配器。

 

十四.故障诊断

  这一部分中列出了我们在编制Direct3D程序时经常会出现的一些问题以及避免这些问题所要使用的方法。

 

1. 设备创建

  如果程序在设备创建时失败,检查一下错误:

 

2. 看不见任何东西

  如果运行程序后看不到任何东西,检查一下错误:

 

3. 调试

  调试一个Direct3D程序也不是一件简单的事情。除了检查所有的返回值(这一点对于编制Direct3D程序非常重要,它要依赖于不同的硬件执行情况而定)之外,还可以使用下面的一些技术:

  上述的第二和第三项可以避免Win16锁定(lock),它会使你的调试器挂起。

  另外,可以将下面的入口加入到WIN.INI中:

[Direct3D]
debug=3

[DirectDraw]
debug=3

 

4. Borland浮点初始化

Borland公司的编译器报告浮点异常时的方式不能适应Direct3D。要解决这个问题,可以包含一个下面这样的_matherr()异常处理:


// Borland floating point initialization
#include <math.h>
#include <float.h>

void initfp(void)
{
 // disable floating point exceptions
 _control87(MCW_EM,MCW_EM);
}

int _matherr(struct _exception *e)
{
 e; // dummy reference to catch the warning
 return 1; // error has been handled
}

 

5. 参数验证

  由于性能的原因,Direct3D立即模式的调试版比零售版的参数验证要多一些。这样,在使用最终发行的零售版之前,就要求程序能够对调试版执行稳定的调试。

  有一些Direct3D立即模式的方法对使用的值进行了限制,而这些限制又往往只在调试版本中才会被检查或执行。但是,我们的程序还是要注意这些限制,否则当运行在Direct3D的发行版中时,会有不可预知的错误发生。例如,IDirect3DDevice3::DrawPrimitive方法有一个参数dwVertexCount来指明该方法要渲染的顶点的数量。这个方法只接受065,5350x00000xFFFF)之间的值。在Direct3D的调试版本中,如果将65,536传递过去,该方法会显示错误,并将错误信息记录在错误记录中,然后给程序返回一个错误值。但是,如果我们在使用发行版时犯了相同的错误,那么结果将是无法预知的,因为出错后的处理方式并没有在该版本中进行定义。出于性能的原因,该方法不会对参数进行验证,因此在参数不正确时,将会出现难以预料的结果。有时调用会继续运行,有时有可能会导致一个内存错误。如果不正确的调用能够继续在一个特殊的硬件配置和DirectX版本中运行,那么不能保证它在其它的硬件或将来的DirectX版本中也能继续执行。

  如果程序在运行Direct3D立即模式的发行版时遇到了无法解释的错误,那么可以在调试版中进行测试来检查我们是在哪里将参数传递错误了。

 

6. 杂项

  下面的技巧可以帮助我们发现其它一些常见的错误:

 

~The End~


西北工业大学 电子工程系 程连冀 刘长松(1999年1月)

HomePage: http://double.163.net

Emaillcsong@126.com

Html Made by ZeroneStudio
July,1999

上一页 | 目录 | 下一页