简要分析Ogre渲染队列的实现原理(一)

渲染队列在Ogre中是一个重要的概念,在场景中的所有物体都会在绘制前被Ogre放入到一个特定的渲染队列中。渲染队列主要起两个作用:1.确保正确的绘制顺序。比如先绘制天空盒再绘制一般物体,最后绘制界面。2.提高渲染效率。Ogre将具有相同pass的物体放在一起进行绘制,目的是尽可能减少渲染状态的切换。一般用户常用的是在entity中设置渲染队列序号,其实整个渲染队列的工作流程远远比这个复杂,但基本不需要最终用户干预。这篇文章就是要把这部分不用干预的细节分析一下。

渲染队列是构成Ogre渲染流程和控制渲染效率的关键一环,所以应该深入学习和理解这个部分。我认为通过这个过程不仅可以学习到优秀开源库的设计思想,而且了解实现原理可以帮助我们更好的使用它们。另外在整个Ogre的渲染流程中有很多关键类与之有关联,理解渲染队列对进一步深入理解其它功能是大有好处的。

 

为了更好的讲清楚这个内容我打算按以下的顺序进行展开,希望能够做到深入浅出。

1.渲染队列的实现。

这个部分主要分析Ogre中渲染队列各个类之间的关系与实现细节,

2.Ogre的其它部分如何与渲染队列交互。

这个部分主要分析谁在什么时候操作渲染队列。这部分有助于深入理解渲染队列的各个部分是如何协作完成整个工作的。

3.用户可以做什么。

这个部分主要分析渲染队列的留给我们多大的可操作空间。

 

渲染队列的实现

    下图是渲染队列对应的主要类的简单关系图,作为阅读后续内容的一个参考图.

                                                         

 

 

要了解渲染队列首先接触到的类是RenderQueue,从名字上乍一看很容易让人产生误解,其实RenderQueue不是渲染队列而是一个管理器,负责管理一组名为RenderQueueGroup的对象。RenderQueue本身隶属于场景管理器(SceneManager)对象,一个场景管理器拥有一个RenderQueue对象。

 

RenderQueueGroup表示具有固定编号的渲染队列组。很明显这个类也不是最终的渲染队列,而同样是一个管理器,负责管理一组具有优先级的RenderPriorityGroup对象。RenderQueueGroup的固定编号通过Ogre中的RenderQueueGroupID枚举变量定义,如下:

     enum RenderQueueGroupID

    {

        RENDER_QUEUE_BACKGROUND = 0,

        RENDER_QUEUE_SKIES_EARLY = 5,

        RENDER_QUEUE_MAIN = 50,

        RENDER_QUEUE_SKIES_LATE = 95,

        RENDER_QUEUE_OVERLAY = 100,

 };

每一个渲染队列组对象拥有在上述枚举变量中的一个值,用来标识这个渲染队列组的处理优先级。从枚举值上看编号越小的越先绘制,比如编号为5的天空队列组要先于编号为100的界面叠加队列组绘制。

RenderQueue这个类按照渲染队列组的固定编号作为索引保存每一个RenderQueueGroup对象。RenderQueue没有提供创建RenderQueueGroup对象的方法,只要提供编号就可以获得一个队列组对象。内部会根据指定编号是否存在而自动创建对应对象,最终确保每一个编号的渲染队列组只有一个。RenderQueue类的内部使用固定编号作为key的map来保存渲染队列组对象,这样做的好处是可以快速插入和获取渲染队列组对象。由于map在插入时会自动排序,所以在绘制的时候可以按照顺序从map中依次取出渲染队列组对象,而不用单独再处理排序工作了。

    RenderQueueGroup这个管理器本身的实现比较简单,并提供了一个称之为优先级编号的东西。每一个优先级编号对应一个RenderPriorityGroup对象。其实这一层优先级划分是为了能基于上面的分组后提供更加精细的优先级划分。比如在编号为1的天空盒渲染对象组中又可以划分若干个优先级。这个优先级大部分情况都不需要设置,可以采用默认值。

下面我们来看看RenderQueueGroup这个管理器负责管理的RenderPriorityGroup对象。实际上这还不是最终的渲染队列,而又是一个管理器,这个管理器管理着六个固定类型的渲染队列。通过层层结构终于在这里确定了渲染队列的位置。至于渲染对象最终被放入哪个渲染队列是由RenderPriorityGroup对象负责的。分类标准是基于渲染对象所关联的Technique信息确定的。一般根据是否使用透明材质或是否开启阴影等参数决定最终该渲染对象所属的渲染队列,其中具体的算法就不在这里详细讲解了。

RenderPriorityGroup对象中管理的对象是QueuedRenderableCollection类就是真正的渲染队列,负责渲染对象的存储和排序。目前渲染队列支持三种排序方式:升序、降序和按pass排序。排序方式必须在渲染队列为空的时候确定,因为在向渲染队列插入元素的时候依赖排序方式,所以集合不为空的时候是不能修改排序方式的。渲染队列类内部有两个用来保存渲染对象的集合,如下:

            PassGroupRenderableMap mGrouped;

            RenderablePassList mSortedDescending;

第一个集合负责保存按照pass排序的集合,凡是属于使用同一个pass对象绘制的渲染对象被放在这一个组中。

第二个集合负责按照渲染对象与摄像机的距离进行排序的集合。其实升序和降序排列都使用这个集合,可以通过反向访问达到反序的结果。

从源码中可以看出插入渲染对象时,根据当前渲染队列设置的排序方式,将渲染对象分别放入上面描述的集合中。需要注意的是在元素插入的时候只是分组存储而没有进行真正的排序,排序工作触发的时机将在后面分析。渲染队列有sort方法完成的这个排序工作。

 

我们现在回过头来看看整个渲染队列是如何达到确保正确绘制顺序和提高绘制效率的设计目标的。

首先来看绘制顺序。带固定编号的RenderQueueGroup对象是确保绘制顺序的第一步,将背景、物件和界面划分开,确保渲染顺序不会出问题。另外渲染队列QueuedRenderableCollection对象支持按距离升序或降序排列,可以确保具有透明属性的物件按照由远及近的顺序绘制,完成了确保绘制顺序的第二步。

然后我们看渲染绘制效率。渲染队列QueuedRenderableCollection对象支持按照pass分组,在绘制的时候使用相同pass进行绘制的渲染对象会连续被绘制,大幅减少渲染状态的切换。

    至此大体上分析了渲染队列功能的实现原理。系统共分为四层结构实现整个渲染队列的功能,其中前三层都起到管理器的作用,最后一层存储渲染对象。从组织结构来看是比较复杂的,我想只有通过应用的上下文才能更容易理解这样一个复杂的软件结构,所以下一步我们来分析Ogre中其它类是如何与渲染队列交互的。本文章的第二篇将对交互这个部分进行分析。

posted @ 2009-11-19 12:32  徐淼  阅读(3395)  评论(0编辑  收藏  举报