我们在创建实时三维图形程序时,都要考虑到性能优化的问题。下面这一部分内容向我们提供了一些提高性能时要注意的原则:
1. 数据库与Culling (Databases and Culling) 2. 图元批处理(Batching Primitives) 3. 光线技巧(Lighting Tips) 4. 纹理大小(Texture Size) 5. 三角形标志(Triangle Flags)
6. 运行时的裁剪测试(Clip Tests on Execution) 7. 一般性能技巧(General Performance Tips)
8. Ramp性能注意事项(Ramp Performance Notes) 9. Z- buffer性能
1. 数据库与Culling
在Direct3D中,为世界中的对象建立一个可靠的数据库往往可以得到很好的性能,这一点要比提高光栅或硬件的性能更加重要。
我们应该尽量减少可能管理的多边形的数量。在开始时,要建立低聚合模型(low-poly models),然后在不降低性能的前提下,在以后的处理过程中可以添加必要的多边形。要尽量将多边形的总数控制在2500个左右。我们要记住这样一句话“最快速的多边形是不需要绘制的多变形”。
2. 图元批处理
为了在执行过程中得到最佳的性能,我们要尽量对图元成批的进行处理,并尽量保持渲染状态不要变化。举个例子, 如果我们的一个对象有两个纹理,我们要先将使用第一个纹理的三角形组织起来进行渲染,然后进行必要的渲染状态改变,再将使用第二个纹理的三角形组织起来进 行渲染。我们可以通过HAL来调用对渲染状态批处理和图元批处理的最简单的硬件支持。如果我们将指令有效的组合在一起,那么在执行过程中对HAL的调用就会减少,性能也就会相应的得以提高。
3. 光线使用技巧
由于光线处理需要较大的计算量,因此如果我们更加仔细的处理程序中的光线话,往往能得到较大的性能提高。下面提到的一些技巧都源自于这句话“最快速的代码就是不需要调用的代码”。
尽量少的使用灯光。如果我们需要全面提高光线的等级,那么尽量使用环境光而不要增加新的光源。
方向光要比点光源和聚光灯节省计算量。对于方向光,只需要调整光源的方向,不需要基于每个顶点去计算。 聚光灯要比点光源节省计算量,因为它的光照区域以外的地方不需要进行光线处理。它们是否节省计算量取决于我们使用多少聚光灯来照射场景。 使用范围参数将光线在只需要照亮的区域里。当灯光不在指定的范围里时,所有的灯光类型都要尽早退出。 镜面高光的计算量几乎是一个灯光的两倍,因此尽量只在必须的时候再使用它。不用时,要使用D3DLIGHT2结构中的
D3DLIGHT_NO_SPECULAR标志。在定义材质时,要将镜面能量值设定为0来关闭材质的镜面高光,如果仅仅是将镜面颜色设置为0,0,0是不够的。.
4. 纹理大小
纹理映射的性能在很大程度上依赖于内存的速度。我们有一些方法可以提高程序在处理纹理时的高速缓冲器的性能。
使纹理尽量的小;纹理越小,就越容易保存在主CPU的二级缓存中。 不要对每个图元都改变纹理。尽量成群的处理多边形的纹理映射。 如果可能的话,尽量使用正方形的纹理。纹理的大小为256×256时是最快的。如果程序要使用4个128×128的纹理,那么就应该尽量对它们使用同样的调色板,并将它们放置在一个256×256纹理中。这一技术可以减少纹理调动的数量。当然,如果程序不需要使用那么多的纹理的话,就不要使用256×256的纹理,因为我们已经提到过要使纹理尽可能的小一些。
5. 三角形标志
D3DTRIANGLE结构的wFlags成员包含的一些标志允许程序在建立三角带和三角扇的时候重复利用顶点。有效的使用这些标志可以大大提高某些硬件的速度。
程序可以有两种方式来使用这些标志: D3DTRIFLAG_STARTFLAT(len)
如果当前三角形经过Culling操作,那么驱动器同样可以在带或扇中对由len给出的数量的后续三角形进行Culling操作。 D3DTRIFLAG_ODD and D3DTRIFLAG_EVEN
驱动器只需要从三角形加载一个新的顶点就可以重新使用上一个三角形的中其他的两个顶点。
当程序同时使用D3DTRIFLAG_STARTFLAT标志和D3DTRIFLAG_ODD、D3DTRIFLAG_EVEN标志时,可以获得最好的性能。
由于某些驱动器不会检查D3DTRIFLAG_STARTFLAT标志,因此在使用它时,程序必须格外的小心。如果程序使用了没有检查这一标志的驱动器,可能会将已经渲染过的多边形再渲染一遍。
在使用D3DTRIFLAG_ODD和D3DTRIFLAG_EVEN标志之前,要先使用D3DTRIFLAG_START标志。D3DTRIFLAG_START标志使驱动器重新加载所有三个顶点。如果三角形相邻的话,那么所有紧跟在3DTRIFLAG_START标志之后的三角形都可以使用D3DTRIFLAG_ODD和D3DTRIFLAG_EVEN标志。 本SDK的调试版中,D3DTRIFLAG_ODD和D3DTRIFLAG_EVEN标志都有效。
6. 执行中的裁剪测试
使用执行缓冲的程序可以使用IDirect3DDevice::Execute方法来渲染带有自动裁剪处理或不带此功能的图元。不进行裁剪处理时使用这种方法要比设置裁剪标志时快一些,因为变换或光栅阶段进行的裁剪测试会使进程放慢。如果程序不使用自动裁剪,就要确保所有的渲染数据都在视锥体内。这时,最好的方法就是对模型使用简单的边界体(bounding volume),并首先对它们进行变换。使用第一次变换的结果来决定是否放弃位于视锥之外的渲染数据,是否对视锥内的数据使用IDirect3DDevice::Execute方法的非裁剪版本,或者对部分位于视锥内的数据使用裁剪标志。在立即模式中,我们可以使用D3DSTATUS结构的标志,并且当一个边界量在视锥内时使用D3DOPCODE枚举类型的
D3DOP_BRANCHFORWARD成员将几何体跳过,通过这些,我们就可以完成上述的一系列功能。Direct3D保留模式会自动使用这些特性来提高执行缓冲的处理速度。
7. 一般的性能技巧
我们可以在程序中采用一些一般的方法来提高性能。
仅仅在必须时进行清空操作。 尽量减少状态改变。
仅仅在必须时使用透视修正。 尽量使用小的纹理。
逐步减少占用大量系统资源的一些特殊效果。 经常测试程序性能。
确保程序在硬件加速和软件模拟时都能很好的运行。
8. Ramp性能注意事项
Direct3D程序可以使用RGB软件仿真设备,也可以使用ramp软件仿真设备。下面我们来讨论如何在使用ramp设备时提高性能: 8.1 Ramp纹理
8.2 Copy纹理融合模式 8.3 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性能技巧
在使用单色驱动器时,我们可以使用下面的一些技巧来提高程序的性能: 所有纹理公用一个调色板。
尽量使调色板的颜色数小一些——或更小。 将材质中的ramp大小保持在16或更小。
保持所有材质相同(除了纹理句柄texture handle)——允许纹理声明颜色。例如,将所有材质设为白色并保持它们的镜面能量相同。许多程序都
只需要在场景中使用这两个材质:一个含有镜面能量(power)使物体具有光泽,另一个不含镜面能量用于不光滑的物体。 要使纹理尺寸尽量的小。 将多个小纹理合成为一个256×256的纹理。
使用Gouraud明暗处理模式渲染小三角形,用平面明暗处理模式渲染较大的三角形。 要使用多个调色板时,可以将一个调色板作为主调色板,并使其它的调色板中包含主调色板颜色的一个子集,这样也可以提高性能。
9. Z-Buffer性能
在使用z-buffering和纹理处理时,如果我们按照从前到后的顺序对场景进行渲染,那么我们就可以提高程序的性能。带纹理并经过z-buffer处理的图元会在扫描线的基础上进行预检,如果扫描线被一个渲染过的多边形遮挡住了,那么系统就会放弃掉它。Z-buffering可以用来提高性能,但是它最大的用处还是在一个场景包含了大量overdraw的时候。overdraw就是指一个屏幕上的像素进行写操作的平均次数,它是很难精确计算的,但我们可以得到一个比较接近的估算值。如果overdraw的平均值小于2,那么关闭z-buffering可以得到很好的性能,并要按照从前到后的顺序来进行渲染。
我们也可以对图元进行z值检测来提高程序的性能,也就是对一个给定的相对于z-buffer的图元列表进行检测。如果使用z-可见性检测来渲染一个复杂物体的边界盒,那么我们很容易发现物体是否是完全可见的。如果该物体是隐藏的话,那我们就要避免在开始时渲染该物体。现在假设我们有一个摄像机在一个充满3D物体的房间中,紧挨着它由另一间同样充满3D物体的房间,它们通过一扇门相通。我们渲染第一个房间,然后再使用z值检测来渲染到第二个房间去的通道,如果这时发现通道被第一个房间中的一个物体挡住了,那么我们就不再需要渲染第二个房间中的东西,因为在第一个房间中,我们根本就看不到第二个房间中的任何物体。
对于高速的个人计算机,在系统内存中进行 软件渲染要比在显存中进行渲染要快一些,当然,这样我们就无法使用双缓冲器或是硬件加速清空操作了。如果程序既能在系统内存中进行渲染,又能在显存中进行 渲染,并且能够检测两种渲染途径的速度,那么我们就能够利用当前系统的最佳渲染途径来进行渲染。本SDK的Direct3D例程中包含有这种方法的描述。我们有必要对两种方法都进行一下运行,因为再没有别的方法来进行速度测试了。不同的计算机的速度也是不同的,这要看它们采用的是主内存(main-memory)式的体系结构还是使用了哪种图形适配器。
十四.故障诊断
这一部分中列出了我们在编制Direct3D程序时经常会出现的一些问题以及避免这些问题所要使用的方法。
1. 设备创建
2. 看不到任何东西 3. 调试 Debugging 4. Borland浮点初始化 5. 参数验证 6. 杂项
1. 设备创建
如果程序在设备创建时失败,检查一下错误:
创建DirectDraw表面时要声明DDSCAPS_3DDEVICE。 如果使用调色板设备,必须配属调色板。
如果使用z-buffer,必须经它配属于渲染目标。 确定检查了设备能力,特别是渲染深度。 检查使用的是系统内存还是显存。 确定注册表没有被破坏。
2. 看不见任何东西
如果运行程序后看不到任何东西,检查一下错误:
确定三角形没有退化(degenerate)。
确定索引列表内部是连续的——也就是确定没有这样的入口1, 2, 2(它是逐渐下降的)。
确定三角形没有被Culling掉(cull)。 确定变换是相容的。
检查视口,确定允许三角形可见。 检查执行缓冲的描述。
3. 调试
调试一个Direct3D程序也不是一件简单的事情。除了检查所有的返回值(这一点对于编制Direct3D程序非常重要,它要依赖于不同的硬件执行情况而定)之外,还可以使用下面的一些技术:
切换到调试DLLs.
强制使用软件设备,关闭所有的硬件加速,不管它是否有用。 将表面强制分配到系统内存中。
建立一个可以在窗口下运行程序的选项,这样就可以使用所有的调试器。
上述的第二和第三项可以避免Win16锁定(lock),它会使你的调试器挂起。
另外,可以将下面的入口加入到WIN.INI中: [Direct3D] debug=3
[DirectDraw] debug=3
4. Borland浮点初始化
Borland公司的编译器报告浮点异常时的方式不能适应Direct3D。要解决这个问题,可以包含一个下面这样的_matherr()异常处理:
// Borland floating point initialization #include // 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来指明该方法要渲染的顶点的数量。这个方法只接受0到65,535(0x0000到0xFFFF)之间的值。在Direct3D的调试版本中,如果将65,536传 递过去,该方法会显示错误,并将错误信息记录在错误记录中,然后给程序返回一个错误值。但是,如果我们在使用发行版时犯了相同的错误,那么结果将是无法预 知的,因为出错后的处理方式并没有在该版本中进行定义。出于性能的原因,该方法不会对参数进行验证,因此在参数不正确时,将会出现难以预料的结果。有时调 用会继续运行,有时有可能会导致一个内存错误。如果不正确的调用能够继续在一个特殊的硬件配置和DirectX版本中运行,那么不能保证它在其它的硬件或将来的DirectX版本中也能继续执行。 如果程序在运行Direct3D立即模式的发行版时遇到了无法解释的错误,那么可以在调试版中进行测试来检查我们是在哪里将参数传递错误了。 6. 杂项 下面的技巧可以帮助我们发现其它一些常见的错误: 对纹理检查内存类型(系统内存还是显示内存)。 验证当前硬件是否能进行纹理处理。 确定程序可以重新恢复已经失去的表面。 始终声明D3DLIGHTSTATE_MATERIAL,即使在RGB模式下,因为它在单色模式显示必须的。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- haog.cn 版权所有 赣ICP备2024042798号-2
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务