Ogre渲染主循环内部流程简介

发表于2015-12-07
评论0 4k浏览

Ogre在开源图形引擎里面属于比较重的,涉及内容比较复杂。Ogre诞生至今,其版本已发生了很大的变化。Ogre早先诞生于windows平台(D3D)。后来在移动终端设备上,由于OpenGL ES版本的引入,Ogre不断支持了ESRenderSystem。但在起初Ogre在移动终端上的性能不高, 近两年Ogre对于ES 2.0的优化使得性能大幅提升。

 

1Ogre渲染流程

 

Ogre的教程可以搜到很多,一般是介绍Ogre的组件和SDK用法。但对于一个图形引擎,拿到源码最急迫的是想知道其渲染流程是什么样的,说白了就是主循环都干了啥。

 

一个图形引擎它要渲染一幅画不外乎干这么几件事:

  1. 设置物体顶点——Vertex更新
  2. 设置空间变换——Transform更新
  3. 设置Camera和投影视口变换——Viewport更新
  4. 设置纹理数据——Texture更新
  5. 使用需要的GPU Shader,开始渲染,管线执行

上述步骤反复调用,绘制所有物体

  1. 最后,绘制完一帧提交渲染结果。

 

 当然,在这之前需要将顶点、纹理、Shader等资源数据首先加载到显存中。就是渲染流程的主线,无论多复杂的渲染引擎,都得实现上述的这些步骤,其他的一些特效如光照、骨骼等,都是附着在这条主线上的。因此,能在Ogre上也清晰地看到这条主线,Ogre的深入研究将有很大帮助。

 

Ogre到底是怎么做的?

首先是Root::renderOneFrame

 

  SDK的例子一般是利用Root::startRendering来触发该函数。renderOneFrame这个函数就是对整个OGRE进行一帧的更新,包括动画,渲染状态的改变,渲染api的调用等。本文就是剖析这个函数里面都做了什么。renderOneFrame有两个fire函数给用户以渲染前后的一些回调,如_fireFrameStarted就会对所以的frameListener进行处理,这些fire函数可以暂时不理会

 

下一步,进入_updateAllRenderTargets

 

在这个函数中,用当前的RenderSystem对所有创建出来的RenderTarget进行updateRenderTarget也就是渲染的目的地,它分为两种,一种是RenderTexture,表示渲染到纹理一种是RenderWindow,即渲染到窗口

 

RenderSystem::_updateAllRenderTargets

可以看到在RenderSystem中,对创建出来的RenderTarget是用RenderTargetPriorityMap来保存的,用于按照一定的顺序来对RenderTarget进行update,因为在渲染物体到RenderBuffer时,一般会用到之前渲染好的RenderTexture,所以RenderTexture形式的RenderTarget需要在RenderBuffer之前进行更新。

 

  RenderTargetupdate,调用所有挂在这个RenderTarget上的Viewportupdate

   Viewport其实就是定义了RenderTarget上的一块要进行更新的区域,所以一个RenderTarget是可以包含多个Viewport。多个viewport就可以在画面中开多个窗口。 在OgreViewport看成是保存CameraRenderTarget这两者的组合;渲染时把ViewportCamera拍摄到的东西渲染到RenderTarget上。Viewport有一个ZOrder的属性ZOrder越小的,越先被渲染。如果两个Viewport区域重合,Zorder大的最后会覆盖掉Zorder小的内容

 

继续进入Viewport::update

就像前面所说,它所引用的camera来渲染整个场景,而在Camera::_renderScene中,是调用

SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)

真正的场景渲染流程开始了, _renderScene是机器复杂的。下面慢慢道来。

从函数名称还有参数也可以看出来,这个函数的作用就是利用所指定的CameraViewport,来把场景中的内容渲染到Viewport所指定的RenderTarget的某块区域中。根据Camera,可以获取计算出View MatrixProjection Matrix,还可以进行视锥体的剔除与裁剪另外,可以只渲染Camera可见的物体

由于_renderScene的任务比较多,一些特效的计算更新也放入其中,如:骨骼等。这些本文先忽略,主要关注渲染流程。

 

SceneManager::_renderScene中所应看的第一个重要函数是_updateSceneGraph

Scene的更新从根Node开始。在Ogre中,Scene图是用Node节点来进行组织的,Node之间有父子关系,有一个根节点。所有的物体都需要挂接在某个Node上,任何一个Node可以进行位置,缩放,旋转的空间变换;并且会作用到挂接在这些节点上的具体的物体,也就是说,Node保存了全局的World Transform。对于任何一个物体来说,操作Node的空间变换,物体也进行响应的空间变换。

 

此外,Node节点还保存了AABB(包装盒),这个包装盒是一个包含Node上的所有物体的一个立体空间它的主要是用于视锥裁减的,如果Camera看不见某个节点的AABB,那么说明Camera看不见Node所挂接的所有物体,在渲染时可以忽略掉这个Node我个人觉得这块儿的计算量较大,是主循环主要耗时的地方。经过_updateSceneGraph,场景中的每个节点都经过了更新,包括位置,缩放,旋转,还有节点的包围盒AABB

 

   继续回到SceneManager::_renderScene,接下来重要的setViewport

它会调用具体的RenderSystemsetViewport的操作,设置Viewport中所包含RenderTarget为当前所要渲染的目标,Viewport中的区域为当前所要渲染的目标区域。

 

接下来一个重要的概念RenderQueue。可以简单把它想成是一个容器,里面的元素就是Renderable,每个Renderable可以看成是每次绘制时需要渲染的物体,可以是一个模型,也可以是模型的一部分。在RenderQueue中,它会按材质Material来分组这些Renderable,还会对Renderable进行一定规则的排序。

 

在每一次调用SceneManager::_renderScene时,都会调用SceneManager::prepareRenderQueue来清理RenderQueue,然后再调用SceneManager::__findVisibleObjects来把当前摄像机所能看见的物体都加入到RenderQueue中。

      SceneManager::__findVisibleObjects是一个递归过程,它从场景的根节点开始,先检查Camera是否能看见节点的包围盒,如果看不见,忽略它。如果能看见,再检测挂在这个节点上的所有MovableObject,如果MovableObject是可见的,就会调用它的_updateRenderQueue方法,把这个MovableObject相关的Renderable送入RenderQueue中去

      MovableObject用于表示场景中真实的离散的物体他有很多子类,Entity。一个MovableObject要先显示,必须先挂接在某个场景节点上,通过场景节点来设置它的位置 

      一般来说,检测完所有Node节点之后,场景中当前Camera可以看得到无物体MovableObject,他们的Renderable将被添加到RenderQueue中去

这块儿的计算量是除特效计算外,渲染流程中CPU计算量最的地方个人觉得是Ogre需要进一步优化的地方

      

到此, 我们就有了场景中要渲染的所有数据信息,下面_renderScene就开始进行实际的渲染。

 

       

      

这里解释一下_renderVisibleObjects()会对RenderQueue中的每个Renderable进行渲染,最终在SceneManager::renderSingleObject中取出每个Renderable所保存的顶点世界矩阵等信息来进行渲染。      

 

看看整个渲染过程,是不是很像自己写一个OpenGL的渲染过程。而且我个人发现,Ogre还保留着以前PC上固定管线控制上的痕迹,比较喜欢用CPU来计算哪些可以看到哪些看不到。这方面随着硬件的发展,GPU可以介入进来,提高CPU这边的性能。

 

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引