研究ww的人都知道,ww在加载海量的三维模型方面,性能不行,稳定性很差。要想ww能够稳定快速的加载海量的三维模型,必须学习dx技术 及研究ww 原始设计,修改ww底层,优化其性能,使其满足我们在三维业务方面的需要。
下面从不同的方面,从ww原先的设计与dx技术的角度讲述ww程序的性能优化。
一、ww三维模型加载设计
1.1 ww通过序列化xml文件来预加载三维模型对象,并把这些对象存储是树形菜单中(注:程序启动时加载Config\Earth下
的模型配置文件 ),利用一个whlie循环不但刷行三维球,在每次刷新的时候判断,树形菜单中的对象是否被选中,是否
在可是范围,如果在可是范围,开始加载该模型。这些多是单线程的。 其实这样设置会带来很大的问题。
1) 如果模型量比较大,树形菜单中的对象比较多,那判断这些模型是否在可视范围就比较耗时,就导致,模型已在可视范
围还的等待一段时间才显示。
2)模型加载速度比较慢 在加载的过程很卡。因为加载模型跟刷新三维球在同一个线程,三维模型没加载完 该线程一直处在
堵住等待执行中。
3)加载模型渲染时 加载网格跟贴图加载 是相当的费时。
想解决上面的问题,首先要设计一个加载模型的算法,类似谷歌地图,openlayer 等加载瓦片的算法来加载模型。其次单
独 开线程来刷行三维球,保证三维球在任何时候能流畅操作,再者就是要对三维模型数据处理,加速模型的加载 很少资
源的消耗。下面具体介绍dx方面对模型加载方面的优化 。这也是比较关键的(参考学步图圆的一片文章)。
二、dx技术方面优化性能
1: 光源
1.1:尽量减少光源数量,使用环境光来提高亮度。
1.2:方向光源比点光源和聚光灯更高效,因为光的方向是固定的。
1.3:使用光照范围参数来剔除不受光照影响的物体。
1.4:镜面高光几乎使光照计算量加倍,因此只在需要时使用:
将SPECULARENABLE设为FALSE,
将材质的specular power 设为0,
将材质的specular color 设为0。
2:纹理
2.1:尽量减小纹理(贴图)尺寸,这样可以增加纹理被缓存的可能性。
过大的贴图可能造成贴图cache过载, 从而导致贴图cache命中降低.过大的贴图会导致显存过载,
这时候贴图是从系统内存中取的。
2.2:纹理不要超过纹理内存大小,否则你的高速缓存机制,会被强迫每帧上传大量的纹理数据。
2.3:尽量使用正方形纹理。最好长宽都是2的n次方,最快的纹理是256×256,将4张128×128的纹理拼接
成256×256使用。
2.4:尽量减少纹理的切换,将使用同一纹理的对象集中绘制。
2.5:上传纹理到设备上会消耗带宽并引起和顶点数据的带宽冲突。
2.6:动态纹理。首先必须要检查DYNAMICTEXTURES来判断硬件是否支持。
动态纹理不能放在MANAGED pool中。动态纹理总是能锁定,甚至是在D3DPOOL_DEFAULT中。
D3DLOCK_DISCARD是合法的。
注意:只有真正需要修改的贴图才使用Dynamic,并且使用 DISCRAD 和 WRITEONLY 来lock.
2.7.只要可能就用16位色的贴图, 如环境贴图或者shadow map.它们用32位色的贴图实在是浪费。
2.8:.考虑使用DXT 贴图压缩。
2.9: 如果可能,使用简单的贴图过滤或者mip map, 除非必要否则尽量不要使用三线过滤和各项异性过滤.
light map 和环境贴图基本上都不需要使用它们。
3: 缓存
3.1:动态缓存(D3DUSAGE_DYNAMIC)使用DISCARD参数来lock更新, 使用NOOVERWRITE来添加.
尽量不要使用不带参数的lock调用(0)。
当需要在每帧里锁定顶点或索引缓存是,应该使用动态缓存。对动态缓存使用D3DLOCK_DISCARD锁
定能减少延迟。
D3DLOCK_NOOVERWRITE锁定可以用于在缓存空闲处添加新的数据而不修改已经写入的数据。
3.2:尽量使用顶点和索引缓存而不是完全由程序分配内存,这样可以更有效利用顶点高速缓存。
因为至少顶点(索引)缓存的锁定机制能够避免冗余的拷贝操作。在一些驱动中,顶点(索引)
缓存会被放到更优化的内存中(可能是在显存或者AGP内存中)被硬件读取。
3.3:最小化顶点缓存的切换
3.4:如果可能尽量多的使用static vertex buffer(静态顶点缓存)代替dynamic vertex buffer。
3.5:对静态对象,对每种FVF使用一个大的静态顶点缓存来保存多个对象的顶点数据,而不是每个对象使用
一个顶点缓存。其目的也是减少顶点缓存的切换
3.6:尽量使顶点格式大小是32字节的倍数.可以考虑使用压缩过的顶点格式然后用vertex shader去解. 或
者留下冗余的部分,使顶点大小刚好使32字节的倍数。
特别是如果程序需要随机访问AGP内存中的顶点缓存,顶点格式的大小最好是32bytes的倍数。否
则,选择合适的最小的格式。32bytes 也就是8个float数据或2个vector4。
3.7:尽量减少无用的顶点数据, 比如贴图坐标, 如果有Object使用2组有的使用1组, 那么不要将他们放在
一个vertex buffer中, 这样可以减少传输的数据量。
3.8:顶点在顶点缓冲中的顺序尽量符合绘制的顺序, 可以考虑使用三角条带(strips) 代替 三角列表和三
角扇。这样,能能更有效利用顶点高速缓存(cache),在排列条带时因考虑尽快重用顶点。
3.9:一个Vertex buffer 的大小在2M-4M之间最好。
4: Shader
4.1: 尽量使用shader来代替Fixed Pipeline。
4.2: 尽量使用shader来实现来取代Multipass渲染效果。
4.3: 如果可能, 尽量使用vertex shader来代替pixel shader.将计算从逐象素变成逐顶点。
4.4: 使用Effect时,应该根据Effect,然后根据Technique来安排渲染顺序,也就是使用相同Effect和
Technique的物体应该集中绘制。这样可以减少状态切换开销。
4.5: 避免Vertex shader指令数量太多或者分支过多, 尽量减少vertex shader的长度和复杂程度. 尽量使
用swizzling代替mov。
4.6: 可以考虑使用vertex shader来计算静态VB中的数据.比如SkinMesh的顶点可以放到vectex shader中计
算, 这样就可以避免每一帧都从AGP内存中向显存传送数据. 这样也可以使用静态VB了。
4.7: 在shader中判断Z值可以避免绘制不可见的象素, 但是 nvidia 建议简单的shader不要这么做.
4.8: 按照shader和贴图分组后再渲染.先按照shaders分组再按贴图。
4.9: 将计算结果和输出的shader指令合并:
//Rather than doing a multiply and add, and then output the data with two instructions
mad r2, r1, v0, c0
mov oD0, r2
//Combine both in a single instruction, because this eliminates an additional register copy
mad oD0, r1, v0, c0
5: 绘制
5.1:批处理数据量(Batch)
将使用相同渲染状态和贴图的图元集中在一起绘制,这样能尽量减少顶点缓存和状态的切换。并
且将状态切换操作集中成一组设置。
原因:
1:dx对大量图元数据处理有优化。
一次函数调用处理的多边形越多,效率越高。故尽量一次画尽可能多的多边形。
一个比较好的规则就是平均每次函数调用处理1000(显卡性能决定)个图元。在这个数量之下
你很难优化性能,在此之上你则会减少返回次数以及与其他优化考虑的潜在冲突。
2:尽量减少渲染状态切换。并且将需要进行的状态切换组合在一起设置。
改变绘制状态会成为代价昂贵的操作,特别是在改变纹理时。因此要尽可能的减少每帧的状
态改变操作,同样也要尽量减少顶点缓存或者索引缓存的切换。状态包括RenderState,
SamplerState,TextureStageState等。
注意:和DX8一样,改变顶点缓存不再和从前版本一样耗时,但是减少顶点缓存切换仍然是
良好的操作。
5.2:并行操作(Concurrency)
如果能尽可能的把绘制操作和其他操作并行执行,那么性能就能得到显著提高。
这个目标可能和减少绘制状态改变相重度。
你需要平衡批处理数据量以减少绘制状态改变,并且在早期就把数据发送到驱动中以获得并行性。
使用多顶点缓存轮流操作能够促进并行性。
5.3:Clear操作
只在必须的时候Clear。IDirect3DDevice9::Clear函数通常需要花费较多的时间,因此要尽量少调
用,而且只清空的确需要清空的缓存。
对于Color Stencil Z buffer尽量在一次Clear调用中清除,例如:深度缓存和模版缓存本来就是一
块缓冲,尽量一起clear。
5.4:尽量按照front–back(从前至后)的顺序来绘制(渲染)场景中的对象。这样,可以尽可能早地精选出
不需要绘制的对象和象素。
5.5:尽量减少lock的次数, 有些东西并不一定非要每一帧都更新VB, 比如人物动画一般每秒钟更新30次VB基本
上就够了。
5.6:坚决不要在渲染循环中调用创建资源。
5.7:如果是因为需要绘制的顶点数据太多了可以考虑使用LOD, 但是现在的显卡的绘制能力都很强劲, 所以需
要权衡一下LOD是否能够带来相应的好处, 如果过分的强化LOD很可能将瓶颈转移到CPU这边。
5.8:使用多个streamsource, 比如SkinMesh渲染, 可以把顶点坐标和法线这些每一帧都要修改的数据放在一个
动态VB中, 其它不需要修改的(如贴图坐标)放到一个静态VB中, 这样就减少了数据传输量。
7: 其它:
7.1:避免过多的顶点计算,比如过多的光源, 过于复杂的光照计算(复杂的光照模型), 纹理自动生成的开
启也会增加顶点的计算量.
如果贴图坐标变换矩阵不是单位矩阵, 也会造成顶点计算量的增加, 所以如果纹理变换已经结
束, 记得要将纹理变换矩阵设为单位矩阵同时调整贴图坐标。
7.2:连接World-View Matrix,将ViewMatrix设为Identity减少矩阵乘法运算。
7.3:可能的话尽量使用alpha test代替alpha blending。
7.4:尽量使用16位的索引缓冲,避免32位的. 一方面浪费带宽, 一方面也不是所有的显卡都支持32位的索
引缓冲。
7.5:减小RenderTarget 贴图的大小, 如shadow map环境贴图. 可能根本不需要那么大效果就很好。
7.6:如果不需要stencil buffer就尽量使用16位的Z buffer。
7.7:可以根据所需要据消耗的系统资源来逐步减少特效。
7.8:关注渲染的总三角面数,最好先使用最低精度的模型,在保证性能的前提下逐步使用更高精度的模
型。
7.9:如果图象质量方面的计算(pixel shader)范围很大, 并且很复杂, 可以考虑试试全屏反走样。说不定
更快。
7.10:坚决避免使用Draw**UP一族的函数来绘制多边形。
7.11:太多的帧缓冲读写可以考虑关闭Z-Writes如有些多pass的渲染中的后续pass或者粒子系统等半透明几
何物体(如果可以)。
7.12: 定位瓶颈
一般情况,定位渲染通道瓶颈的方法就是改变渲染通道每个步骤的工作量, 如果吞吐量也改变
了,那个步骤就是瓶颈。找到了瓶颈就要想办 法消除瓶颈, 可以减少该步骤的工作量, 增加其他步骤
的工作量。
一般光栅化之前的瓶颈称作”transform bound”, 三角形设置处理后的瓶颈称作”fill bound”.
定位瓶颈的办法:
1.改变帧缓冲或者渲染目标(Render Target)的颜色深度(16 到 32 位), 如果帧速改变了, 那么瓶颈应该在帧
缓冲(RenderTarget)的填充率上。
2.否则试试改变贴图大小和贴图过滤设置, 如果帧速变了,那么瓶颈应该是在贴图这里。
3.否则改变分辨率.如果帧速改变了, 那么改变一下pixel shader的指令数量,如果帧速变了, 那么瓶颈应该就
是pixel shader. 否则瓶颈就在光栅化过程中。
4.否则, 改变顶点格式的大小, 如果帧速改变了, 那么瓶颈应该在显卡带宽上。
5.如果以上都不是, 那么瓶颈就在CPU这一边。