3d优化总结和一些新思路

3D引擎的优化实现,是个比较大的课题.本文重点针对过去的实践以及关键系统的新思路抛砖引玉做一些总结.



.3D引擎中的多线程加速


引擎中的多线程加速,主要有两个方向:密集计算的并行化和渲染层多线程化.


1.密集计算并行化

 实际游戏中,对于每帧都做的密集计算,比如骨骼动画/粒子更新等,可以缓存后并行加速.

 以OGRE的骨骼动画为例,SceneManager::_findVisibleObjects在渲染流程中,主要是可见性裁剪,更新,再加入到渲染队列中.

骨骼动画更新这一步,又包含根据时间/权重的骨骼关键帧插值更新,骨骼结构更新,应用骨骼矩阵于顶点这三步.最后一步如果是硬件骨骼的话,会放在GPU中计算.不过不管怎么样,这个计算都是相对密集的.并且对外部依赖相对较少.

在开始更新前,可以把信息缓存起来.然后在后续对渲染队列排序处理前,对缓存的各对象,利用parallel_for并行处理之.

可以做成框架,处理所有类似的情况.这里的并行处理,可以选择openmp/tbb或自实现.不过需要注意的一点是:对于并行加速,并不总是实用.有两个原则.

   1.如果并行加速后,一定要额外加锁才能正确处理数据.最好别做并行.

   2.如果并行的任务太轻量级,反而会拖慢速度.


             2.渲染层多线程化

游戏这里所说的渲染层,是指所有直接跟dx/opengl打交道的部分.SetTexture/DrawIndexedPrimitive类似这样的调用。只对这部分做处理,可以尽量减少多线程对引擎稳定性的影响.

采用双缓冲队列来接受调用的缓存.一个队列负责缓存当前帧的调用,当调用Present真正显示时,通知另外一个线程开始对缓存的调用进行真正的处理.

再切换队列,用于下一帧的调用缓存.

这样处理后,类似VertexBuffer::Lock这样要操作显存的地方,就需要额外处理了.比较好的做法是.在内存中创建同等大小的buff,lock时返回buff指针.

Unlock时生成更新显存的操作.

为了更方便的集成多线程渲染层,可以通过hook dxAPI形式进行。最终达到只需要链接dll,甚至外部注入dll的方式集成这一部分,不需要引擎改任何代码.

根据OGRE的单元测试结果,普遍都有性能的提升,某些情况下甚至有50%提升.


.3D数学运算SSE2加速


SSE2intelP4之后引入的新指令,特殊适合多媒体,游戏的应用场合.现阶段来说,也具有很高的普及度.

一般sse2汇编处理运算时,除了算法本身要根据指令的特色进行改进外,在优化时也有些注意事项.


1.一般来说,同样的功能会有好几种指令的实现方式,比如把寄存器内容写回内存,可以用movaps也可以采用movlps和movhps组合的方式.而后者具有更好的速度.多尝试,多了解各种指令,可以让最终的代码具有更好的效率.

2.对代码进行汇编改写后,根据CPU流水线的执行特色,可以对指令进行适当的排序,提高CPU的指令并行度.比如在两个乘法指令之间穿插加法指令等.

其他细节可以看另外一个专门写汇编加速的文章.

最终测试的效果,比典型的矩阵乘法运算,OGRE自带快2倍多,比网上能找到的汇编实现快20%


. 2D UI3D引擎中的应用

对于客户端程序而言,UI是个亘古不变的开发课题.大部分对界面要求比较高的应用程序/2D游戏都是采用的自绘UI.3D中的UI大概有两种主流的实现方式:类似CEGUI的开源解决方案以及由FALSH托管绘制的Scaleform UI实现.

考察3D引擎中的UI实际的需求,基本上也是相当于UI逻辑+2D引擎.我们可以考虑把2D 这套UI无缝迁移到3D中来这样可以大大的缩短开发周期,一劳永逸的解决UI问题.

总的思路大概是以下两步:

    1.把整个场景渲染到一张纹理上(ID3DXRenderToSurface::BeginScene)

    2.通过创建离屏表面,获取渲染纹理的RGB数据.以此为Canvas,绘制UI.

这部分都是在CPU中做,所有的绘制都是数据的copy.采用SSE可以很好的优化他们的效率


.无限大世界的室外裁剪算法


3D 的裁剪算法很多,比如以前经典的室内BSP裁剪,常用的视锥裁剪,水平线裁剪等.也有基于遮挡物的裁剪,比如这文章里介绍的

这里介绍一种专门适用于高度场地形的裁剪算法,通过预生成一些初略的可见性信息,以加速实时的可见判断.

考虑这样一种典型的情况,一个山丘,在对应的两头分别站立玩家和NPC,虽然玩家看不见NPC,NPC也一样会被加入到渲染队列中.而实际上,这样的可见性是可以预计算出来的.

如果我们把整个地形划分成很多小格子,以每个格子为相机所在,类似于BSP的的预处理,采集足够的位置和方向后,我们是可以判断出格子之间的可见性的.算法流程大概如下:

1.遍历每个格子,每次渲染前把相机设置成格子内的不同位置和方位

2.确定相机后,以格子的底边,格子索引为颜色,渲染一定高度的长方体.为了加速预处理,可以尽量一次性渲染多个肯定不会形成遮挡关系的长方形.

3.获取渲染后的屏幕颜色数据,根据颜色就可以得出格子是否可见.

4.为了让这样的预处理具有更好的适应性,在采样相机和绘制长方体时,都采用不同的高度区间。比如相机高度在0-2m,2-3,3-5之间采样,长方体分别绘制2m,3m,5m的高度,尽量多的适应玩家的操作和物件高度.

在得到这样一份格子可见性后,对于上面提高的典型情况,我们就可以根据NPC和玩家所占据的格子,以及相机/NPC高度判断可见性了.

可能会产生的一些问题:

1.内存占用.假设格子两两可见性用1bit来表示,1m为格子的大小,以典型的WOW地形,533*533而言,在玩家所加载的九宫格内,居中的格子可见性关系数目为n*(n-1)/2,相邻九宫格内n*n*8.实时计算需要占用的内存不超过2.5M.如果多重采用的话,这个数据再乘对应的倍数.总体而言,不会成为内存的大头.

2.算法的局限性.如果玩家在游戏中,可以随便飞,算法的适用程度将大为降低.

3.室内外衔接portal处的裁剪应用.可以根据视线跟portal的截面,获得对应的格子,来判断可见性.


五.无限大世界的地形加载

无限大视野的资源加载,一般采用九宫格来做.如果玩家频繁在九宫格的边界处游走,对应的资源加卸载会影响性能.

考虑到地形资源大部分都是摆在上面的物件.可以对这部分加载的特殊处理,以优化性能。


根据高度场地形的实现原理,每个Page(九宫格中的某一格)会被继续细分成更小的格子(cell),以便渲染和做静态LOD.

我们以一个Page有16*16cell为例来说,首先地表物件都根据一定规则(比如其中心点所在cell)依附在cell上,以玩家当前cell为中心,7*7cell内的物件加载,9*9cell外的物件卸载.Page那层的九宫格加卸载就只需要管理纯地形数据.这样既减少了实时的内存占用,渲染批次,又能解决资源加载问题.


cell层加卸载算法,具体的流程如下:

1.玩家初次进入地图时,加载7*7cell,并标记已加载.
2.玩家跨格子后,对于新7*7cell内物件加载并标记,老的9*9cell卸载(如果本身没加载直接过滤).
3.玩家离开地图,针对所有9*9cell范围内已加载的卸载


六.动态小物件的批次合并

游戏中,为了增加表现力,经常会有些动态小物件,比如脚印和脚底的假阴影等.东西虽小,但是实际游戏中,由于玩家/NPC增多,会导致批次增加而显著的影响效率.这些小物件通常具有同样的材质Triangle很少,可以考虑采用建立一个大的缓存合并成很少的批次。

底层合并的算法,以合并只有一个Tri,带索引(0, 1, 2)为例说明:

   加入第一个物件时,index_buff0, 1, 2,vertex_buffv1, v2, v3

   后续陆续加入2,3个物件后, IB0, 1,2,3,4,5,6,7,8

   VBv1, v2, v3,v1, v2, v3,v1, v2, v3

   需要删除时,保持VB不动,只对IB操作.

   删除第一个物件后,IB3,4,5,6,7,8,删除第二个物件后,6,7,8

从算法流程看,为了准确的操作IB达到删除的目的,需要对每个物件记录它在IB中的位 置,当某个物件删除时,由于数据前移,则要把记录的位置相应的做改变(减去物件index_count).当物件更新时,我们也需要能准确找到对应的数据就行修改,就需要记录物件在VB的位置.

配合引擎渲染的流程,可以利用已有的裁剪算法,尽量少的修改实现合并.

在物件真正加入渲染队列,类似OGRG_updateRenderQueue函数内,设置物件的可见性.这里只需要设置可见,不可见通过排除法得到.

在对渲染队列排序前,统一判断所有物件当前帧和上一帧的可见性是否一致,如果不同,则做添加/删除处理.否则就不再需要操作缓存.再保存当前帧可见性,并还原当前帧为不可见,以便下一帧做同样的处理.

如果物件本身有些特殊的效果,比如脚印在消失前会有个渐入渐出的过程.通过设置材质效果,让顶点色的alpha值,作为整个物件的alpha,以得到同样的效果.

这样的合并有个缺点.对于透明物件,由于合并的顶点可能跨度很大,无法给一个视野距离来做透明排序,会导致显示异常.典型的比如玩家的头顶文字,当玩家入水后,头顶文字依然看起来像在水面上.可以判断这种情况下,不走合并流程.默认把头顶文字合并的视野距离设的比较小,尽量减少异常显示情况的发生. 

建立统一的合并架构后,把它用于脚印/假阴影/头顶文字/远的地形(他们采用统一的材质),可以很好的减少批次,提高性能.极限情况下,能提升30%.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值