Ogre 渲染队列(二)

摘要:
清除渲染队列Ogre将在渲染每个帧之前清除渲染队列。熟悉Ogre渲染过程的人可以很容易地在SceneManager中看到它:_ repareRenderQueue()方法在renderScene()方法中调用。将场景管理器传递到此函数意味着每个节点对象都负责填充渲染队列。Ogre总是尽可能地追求可扩展性,因此为用户提供定制的渲染顺序也不例外。以下部分描述了Ogre用户可以做什么。此方法通常只能用于配置渲染队列,并且在渲染队列的操作过程中不能干扰内部函数,因此称为第一级控制。

转自:http://www.cnblogs.com/cppguru/archive/2009/11/25/1610626.html

简要说Ogre渲染主流程分三步使用渲染队列:清空、构造和访问,这个过程在每一帧的绘制过程中重复执行。

渲染队列的清空

Ogre在每一帧渲染前都会先清空渲染队列。熟悉Ogre渲染流程的很容易看到在SceneManager::_renderScene()这个方法中调用prepareRenderQueue()方法。这个方法的实现很简单,就是清空现在的渲染队列并且初始化渲染队列的一些配置参数。渲染队列的清空函数是RenderQueue::clear(),该函数继续调用内部其它对象的清理函数。

渲染队列的构造
  渲染队列构造的基本原理是从场景管理器中计算出当前帧可见的所有对象,并依次将这些可见的对象加到渲染队列中。根据前面介绍的内容,渲染队列根据传入的对象和自身的配置按照一定规则将对象保存起来。
  在调用SceneManager::prepareRenderQueue()方法之后,Ogre会继续调用场景管理器的_findVisibleObjects() 方法。从函数名称我们可以看出这个函数是用来寻找可见对象的。下面是它实现的核心代码:
 
getRootSceneNode()->_findVisibleObjects(cam,getRenderQueue(),  visibleBounds, true,   mDisplayNodes,onlyShadowCasters);

   从代码中很容易看出它取出场景管理器中的根节点,并调用根节点的_findVisibleObjects()方法完成寻找可见对象和渲染队列填充功能。这个函数是个递归函数,从根节点出发递归调用所有子节点的这个函数。我们最关心的是这个函数的第二个参数,它代表当前场景管理器使用的渲染队列。将场景管理器传进这个函数意味着由每个node对象负责渲染队列的填充。当这个函数返回时我们就可以拿到填充好的渲染队列了。为了能深入了解整个过程我们继续向下分析,在节点的_findVisibleObjects方法中我们可以看到它获取了绑定到node上的所有的MovableObject对象,并调用这些MovableObject对象的_updateRenderQueue方法。Ogre中有很多继承自MovableObject的对象,这里我们从最熟悉的Entity对象来继续我们的分析。Entity是一种ovableObject对象,所以如果该节点上绑定的对象是Entity对象,那么节点上调用_updateRenderQueue方法其实就是调用Entity对象的_updateRenderQueue方法。
 
我们知道Entity对象中包含SubEntity对象,而只有SubEntity对象才是可以显示的对象,所以在Entity_updateRenderQueue方法中获得所有可以显示的SubEntity对象,并将其放入作为参数传入的渲染队列中,代码如下:
 
SubEntityList::iterator i, iend;
 
iend =displayEntity->mSubEntityList.end();
 
for (i = displayEntity->mSubEntityList.begin();i != iend; ++i)
 
{
 
if((*i)->isVisible())
 
{
 
if(mRenderQueueIDSet)
 
queue->addRenderable(*i,mRenderQueueID);
 
else
 
queue->addRenderable(*i);
 
}
 
}
 
  在这里我们终于看到了对渲染队列的添加操作,调用RenderQueueaddRenderable函数。addRenderable函数有几个重载的版本,根据需要可以选用合适的函数。
至此我们了解了一个渲染对象是如何被更新至渲染队列的大体过程。当然这里提到的函数本身功能是很复杂的,这里我只将重要的部分做了介绍,其余的内容不在讨论的范围。

渲染队列的访问
 
  在构造了当前这帧所有可见对象的渲染队列后,剩下一步就是如何从渲染队列中取出我们要绘制的渲染对象,我们回到SceneManager::_renderScene() 方法。之前我们讲到调用SceneManager::_findVisibleObjects()方法来填充渲染队列,在这个方法之后我们看到了_renderVisibleObjects()方法,它调用了renderVisibleObjectsDefaultSequence方法。源代码中这里还有一个按照自定义的顺序进行渲染的方法,这个部分我们后面再进行详细分析。Ogre总是尽可能追求扩展性,所以这里也不例外为用户提供了自定义的渲染顺序。我们先从简单的默认渲染顺序入手吧。
 
  由于开启阴影的处理比较复杂,所以这里我们只讨论默认不带阴影的渲染过程。SceneManager::renderVisibleObjectsDefaultSequence函数实现很简单,按照优先级由小到大从RenderQueue中取出RenderQueueGroup进行处理。前面讲过RenderQueueGroup内部又进行了一次优先级排序,所以从RenderQueueGroup中按照优先级取出RenderPriorityGroup对象依次进行处理。我们这里讨论不包含阴影的处理,所以只需要处理RenderPriorityGroup中的三个渲染队列就可以了(前面有介绍,RenderPriorityGroup对象包含六个不同功能的渲染队列)。包括一般对象、不排序透明对象和排序透明对象三个序列。
在渲染对象插入队列的时候并没有进行排序,现在到了进行排序的时机了。每个渲染队列分别调用sort方法完成排序工作,排序之后获得了正确有效的绘制顺序,并使用访问者模式进行最后的渲染调用,最终所有绘制都会调用SceneManager::renderSingleObject函数完成一个对象的渲染工作。这里使用访问者模式增大了理解代码的难度,我想暂时先不管这部分,以后有空专门写个文章来讲一下吧。
  着一块背后的思想挺简单的,但实现的时候相互交织太深,很难一下看清原貌。所以上一段很难写清楚,将就看看吧。

下面一部分讲讲使用Ogre的用户能够做些什么。
总体上来说Ogre在这个功能上开放了三个层次的控制权,
1. RenderQueue
对象配置。
2. RenderQueue
对象监听者。
3.
自定义渲染顺序。

RenderQueue对象配置
 
  一个场景管理器只拥有一个RenderQueue对象,而且该对象在渲染过程中不会发生变化,所以这个对象拥有若干可以配置的参数。配置的时机当然是在渲染之前。从RenderQueue对象的方法上可以看出一连串set开头的函数负责这些参数的设置。利用这种方式只能总体上对渲染队列进行配置,在渲染队列运作过程中无法干预内部的功能,所以称之为第一个层次的控制。
 
想要配置这个对象非常简单,调用SceneManager::getRenderQueue方法可以获得RenderQueue对象的指针,通过对象指针访问相关的方法。

RenderQueue对象监听者
 
  监听者这个概念在Ogre的体系中非常常见,同样在渲染队列这里也提供了设置监听者的接口。接监者接口方法定义如下:

virtual bool renderableQueued(Renderable* rend, uint8groupID,  ushort priority, Technique** ppTech, RenderQueue* pQueue) =0;

用户可以自己实现上述监听者接口的方法。这个方法在渲染对象被放入渲染队列前被调用。通过这个方法可以修改绘制这个对象的Technique对象,而且可以通过函数返回false阻止Ogre将这个对象放入渲染队列。使用监听者可以通过调用RenderQueue::setRenderableListener方法完成。

自定义渲染顺序
 
  自定义渲染顺序当然是最灵活的控制方式,同时也是比较复杂的一种方式。这个功能主要包括两个主要的类RenderQueueInvocationSequenceRenderQueueInvocation。场景管理器在渲染时会判定用户是否设置了RenderQueueInvocationSequence对象,如果有就按照这个对象定义的方式启动渲染,反之就按照之前介绍的默认方式渲染。RenderQueueInvocationSequence对象是与viewport相关的,用户可以在每一个viewport上关联一个自定义渲染对象,用来控制每个viewport的渲染过程。
 
RenderQueueInvocationSequence对象是很单纯的容器类,用于保存RenderQueueInvocation对象的实例,而RenderQueueInvocation对象负责调用RenderQueueGroup的渲染过程。
 
  这个结构的控制原理其实也挺简单的。利用RenderQueueInvocation对象做了一次渲染顺序的映射,这个对象在RenderQueueInvocationSequence中按顺序存放,按顺序访问,同时这个对象与RenderQueueGroup做关联,完成渲染顺序的变更。
  比如有两个RenderQueueInvocation对象,第一个关联第五十号RenderQueueGroup对象,第二个关联第一号RenderQueueGroup对象对象。而RenderQueueInvocation是按顺序存储和访问的,所以五十号先于一号RenderQueueGroup对象绘制,这就改变了绘制的顺序。 需要明确的是这套机制只是重新定义了RenderQueueGroup的渲染顺序,如果想真正完全控制整个渲染顺序必须自己从RenderQueueInvocation类继承,根据需要重载这个类的虚函数。
 
结语
  从总体上来看渲染队列还是比较复杂的,这里的介绍也只能是走个流水过程,很多细节还需要仔细推敲,真要想讲清楚每个部分都需要单独拿出来写成一篇文章。对我来说写这篇内容的过程又是一次学习,很多原先没有仔细研究的部分又重新看了一番,我始终觉得阅读高质量源代码确实是提升能力的一种有效方法。

免责声明:文章转载自《Ogre 渲染队列(二)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SpringBoot整合redis哨兵主从服务【Apache】Apache ab压力测试工具Window下载和用法详解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ANDROID窗体管理服务实现机制和架构分析

 一、功能     窗体管理是ANDROID框架一个重要部分,主要包含例如以下功能:    (1)Z-ordered的维护   (2)窗体的创建、销毁   (3)窗体的绘制、布局    (4)Token管理,AppToken    (5)活动窗体管理(FocusWindow)    (6)活动应用管理(FocusAPP)    (7)输入法管理  ...

Chrome开发者工具面板 F12 调试大全 记录

面板上包含了Elements面板、Console面板、Sources面板、Network面板、Timeline面板、Profiles面板、Application面板、Security面板、Audits面板这些功能面板。这些按钮的功能点如下: Elements:查找网页源代码HTML中的任一元素,手动修改任一元素的属性和样式且能实时在浏览器里面得到反馈。...

使用异步I/O大大提高应用程序的性能

转自:https://www.ibm.com/developerworks/cn/linux/l-async/ AIO简介 Linux中最常见的输入输出(I/O)模型是同步I/O。在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这是很好的一种解决方案,因为调用应用程序在等待I/O请求完成时不需要使用任何中央处理单元(CPU)。但是在某些情...

VC++创建、调用dll的方法步骤

文章来源:http://www.cnblogs.com/houkai/archive/2013/06/05/3119513.html 代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,ATL、MFC等,它们都以源代码的形式发布。由于这种复用是...

消息处理(MSMQ)基础知识

     MSMQ(MicroSoft Message Queue,微软消息队列)是在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一台机器上,也可以分布于相连的网络空间中的任一位置。它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为Message),然后把它保存至一个系统公用空间的消息队列(Messa...

asyncio之Coroutines,Tasks and Future

asyncio之Coroutines,Tasks and Future Coroutines and Tasks属于High-level APIs,也就是高级层的api。 本节概述用于协程和任务的高级异步api。 Coroutines Coroutines翻译过来意思是协程, 使用async/await语法声明的协程是编写asyncio应用程序的首选方法。...