Clayman's Graphics Corner

DirectX,Shader & Game Engine Programming

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

从Demo到Engine(二) -- Render Queue Sort

仅供个人学习使用,请勿转载,勿用于任何商业用途。

 

      渲染队列排序是引擎渲染步骤中一个非常重要的部分,稍微有经验的图形程序员都知道次序良好的渲染队列对性能有很大提升,但究竟怎么排序,相关的文章和讨论都非常之少。本文所介绍的方法主要来源于Christer Ericson一篇非常实用,也是惟一篇我能找到的关于渲染队列排序的文章。


为什么需要排序

无论是Dx9还是Dx10/11级别的硬件,状态改变都是相对比较费时的操作。而游戏中,大部分对象都有类似的渲染状态(shader,texture,render state….),因此,通过排序把状态相同或者类似的物体放到一起,可以极大减少状态改变的次数,提高性能。

依据什么排序

显然,不同渲染状态和各种渲染状态改变时的代价是排序的主要依据。按照代价大小,主要的渲染状态有shader, texture, render state, constant data, buffer。此外,物体离摄像机的距离也是一个非常重要的因素,对于不透明物体,最好由近及远渲染,透明物体则反之。

如何排序

     一些较老的引擎可能借助scene graph进行排序,<<3D Game Engine Design 2nd>> 3.4部分也介绍了一种基于树的方法来排序。在我看来,2种方法都不太好,就前者来说,scene graph并不是按照渲染状态来组织的,为了加入排序功能,就势必要把原来简单的结构变的复杂;后者构建树的方法不但难以实现,而且效率也不高。

      
既然文章标题是渲染队列,因此假设我们通过culling,已经把所有可见物体都放到一个一维数组里了,我们的排序不会影响scene graph,不会涉及到更复杂的数据结构。接下来事情就很简单了:

依据shader排序:只需要给每个shader一个id,用任意排序算法排序队列。

依据texture排序:初看起来似乎和shader一样,但仔细一想,现代游戏,通常一个对象有一张以上的纹理,应该用哪张或哪几张纹理排序呢?这个问题没有统一的答案,通常来说,如果diffuse map相同,那么normal mapspecular map等纹理也应该相同。但同一物体也有可能有多张diffuse map(比如地形),这就更难办了。我自己的方法是以第一张diffuse mapid进行排序。排序的目的只是尽可能减少状态改变,完美的排序方法几乎是不存在的,因此不一定要非常精确。

依据render state排序:相比纹理,这个就更复杂了,dx9下有数十种render stete,即使是dx10/11,也有很多个state object,况且每个state还有若干可能的值。对这一项进行排序几乎是不可能的任务,我把它称为不可排序元素,不对他进行排序。但这并不意味着不能用其它手段对rende state进行优化,具体方法超出了本文讨论范围。

依据constant date排序:这一项同样是不可排序元素,不予考虑。

依据buffer排序:类似shader排序。

依据距离排序:类似shader排序。

     
上面几种方法其实都不能满足实际需求,我们通常希望对几个元素进行排序,比如对不透明物体,按照shader—texture--camera to object distance的顺序排序(这只是随便举的例子,并不一定是最好的排序策略)。按照上面的思路,需要对队列进行三次排序,你可能可以已经开始担心性能了L. 有没有什么方案可以一次排序就完成呢?当然,最简单的方法就是按照这三个元素,为每个物体生成一个sort id,这样一次排序就可以了! 比如以一个32位的值作为ID,前8位表示shader id,接下来八位表示texture id,后16位则表示distance。好吧,32位值是个不太好的例子,64位值通常能带给我们更多的灵活性。下面是一个更实际的例子:

 

6bit(64)

6bit(64)

13bit(8192)

15bit

……

opaque object

queue id

priority

Shader

distance

others….

 

 

6bit(64)

6bit(64)

20bit

…….

transparent

queue ID

priority

distance

others

 

         前面的讨论只考虑了纯图形方面的需求,而queue idpriority则是来自业务逻辑方面的需求,可以把物体组织为不同queue或者说group,每个group中的物体又能分别指定优先权,为用户控制渲染流程提供了足够灵活性。

    
 再来看一些更细节的问题,以那些元素排序比较合适呢?注意,这里并没有一定的答案,不同场景应该用不同策略。以上面提到的shader—texture--camera to object distance为例,这种方法虽然减少了纹理改变状态,但从early-z的角度来看,对显卡就不太友好。Shader—distance shader—texture---buffer在不同场景下也各有千秋。Distance--buffer也许是最适合渲染pre-z passshadow map的策略。另外,并不是所有的组合都有效,比如camera to object distance—shader与仅用camera to object distance排序的结果可能是一样的。结论是排序时不一定用到的元素越多越好,也不一定要非常精确。此外,我们的排序不依赖于引擎其他系统,也没有复杂的数据结构,完全可以在运行时动态改变排序策略。

      
最后,也是困扰了我很长时间的问题,对于multi-passeffect应该怎么办(注意,前文提到的shader仅仅对应单effectpass的情况),需要npass渲染的物体在渲染队列里是n<mesh, shader>元素,还是一个<mesh, multipass>元素?以什么策略对这样的元素排序?Wolfgang EngelShaderX里提到过现代引擎尽量不要用multipass,我也一直不太喜欢multipass。原因很简单,如果某个effect因为硬件限制,需要分为几个pass渲染,那么最好的方法就是为低级别的硬件重新编写一个简单的shader。但除硬件限制以外,确实还有不少效果是必须用多个pass渲染的,所以这一点需要额外考虑。

 

 

posted on 2010-04-07 03:16  clayman  阅读(3567)  评论(15编辑  收藏  举报