﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-life02-文章分类-转载--OGRE</title><link>http://www.cppblog.com/life02/category/11642.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 31 Aug 2009 09:13:25 GMT</lastBuildDate><pubDate>Mon, 31 Aug 2009 09:13:25 GMT</pubDate><ttl>60</ttl><item><title>OGRE主要渲染流程简介(转载)</title><link>http://www.cppblog.com/life02/articles/94891.html</link><dc:creator>life02</dc:creator><author>life02</author><pubDate>Mon, 31 Aug 2009 08:21:00 GMT</pubDate><guid>http://www.cppblog.com/life02/articles/94891.html</guid><wfw:comment>http://www.cppblog.com/life02/comments/94891.html</wfw:comment><comments>http://www.cppblog.com/life02/articles/94891.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/life02/comments/commentRss/94891.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/life02/services/trackbacks/94891.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt">转载自：<a href="http://www.cppblog.com/richardhe/articles/68841.html">http://www.cppblog.com/richardhe/articles/68841.html</a><br>很早以前就想写一些关于<span>OGRE</span><span>的文章了，一直没机会。</span>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;理解一个渲染引擎，我觉得最重要的是先抓住了它的主架构，它的主线，渲染流程，不然的话，一个引擎几万行，甚至几十万行的代码，光是打开</span><span>solution</span><span>就能吓你一跳了，</span><span>OGRE</span><span>也有十几万行的代码量，我一开始看它的时候也是无从下手，感觉代码太多了，不知道从哪开始看好，这个</span><span>class</span><span>看看，那个</span><span>class</span><span>看看，由于对整个引擎没有一个清晰的认识，看过了也印象不深，所以，最后，还是决定先找出它的主线，了解它的渲染流程，这样才能有机地把各个部分联系起来。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;这篇短文也是对</span><span>OGRE</span><span>的主要渲染流程的一个介绍，可能对一些</span><span>class</span><span>不会太多地去介绍具体的实现细节。我所用的代码都是取自于</span><span>OGRE</span><span>的最新的</span><span>CVS</span><span>版本。</span></p>
<p><span>&nbsp;&nbsp;&nbsp; 读者最好对</span><span>OGRE</span><span>有一定的了解，至少得看懂它的</span><span>example</span><span>，不然可能一些东西理解起来比较困难。对</span><span>D3D</span><span>，</span><span>OPENGL</span><span>有一定了解更好。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;如果你看过</span><span>D3D SDK</span><span>中带的例子，你一定知道一个比较简单的</span><span>3D</span><span>程序要运行起来，至少都会涉及以下的几部分：</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;首先是数据的来源，包括顶点数据，纹理数据等，这些数据可以从文件中读取，也可以在程序运行时生成。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;接下来，我们会建立顶点缓冲区把顶点保存起来，建立</span><span>texture</span><span>对象来表示</span><span>texture</span><span>，对顶点组成的物体设置它在世界坐标系下的坐标，设置摄像机的位置，视点，设置</span><span>viewport</span><span>的位置和大小，然后就可以在渲染循环中开始调用渲染操作了，经过了</span><span>front buffer</span><span>和</span><span>back buffer</span><span>的交换，我们就能在屏幕上看到</span><span>3D</span><span>图形了，伪代码如下：</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setupVertexBuffer</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setWorldTransform</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setCamera</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setProjectionTransform</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setViewport</span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>beginFrame</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setTexture</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drawObject</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endFrame</span>&nbsp;</p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp; 以下就是渲染一个物体的主要步骤，在我看来，这就是</span><span>3D</span><span>程序的主线，同样道理，无论你多复杂的渲染引擎，都得实现上述的这些步骤，其他的一些效果如阴影，光照等，都是附着在这条主线上的，所以，如果你能在你所研究的渲染引擎上也清晰地看到这条主线，可能对你深入地研究它会大有帮助，下面，我们就一起来找到</span><span>OGRE</span><span>中的这条主线。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OGRE</span><span>的渲染循环都是起源于</span><span>Root::renderOneFrame</span><span>，这个函数在</span><span>OGRE</span><span>自带的</span><span>example</span><span>中是不会显式调用的，因为</span><span>example</span><span>都调用了</span><span>Root::startRendering</span><span>，由</span><span>startRendering</span><span>来调用</span><span>renderOneFrame</span><span>，如果你用</span><span>OGRE</span><span>来写真正的游戏，或者编辑器，你可能就需要在的消息主循环中调用</span><span>renderOneFrame</span><span>了，顾名思义，这个函数就是对整个</span><span>OGRE</span><span>进行一帧的更新，包括动画，渲染状态的改变，渲染</span><span>api</span><span>的调用等，在这个函数中，会包括了我们上述伪代码的几乎全部内容，所以是本文的重点所在。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;进入</span><span>renderOneFrame</span><span>，可以看到头尾两个</span><span>fire</span><span>函数，这种函数在</span><span>OGRE</span><span>中经常出现，一般都是</span><span>fire&#8230;start</span><span>和</span><span>fire&#8230;end</span><span>一起出现的，在这些函数中，可能会处理一些用户自定义的操作，如</span><span>_fireFrameStarted</span><span>就会对所以的</span><span>frameListener</span><span>进行处理，这些</span><span>fire</span><span>函数可以暂时不用理会，继续看</span><span>_updateAllRenderTargets</span><span>，在这个函数中，会委派当前所用的</span><span>renderer</span><span>对所有创建出来的</span><span>render target</span><span>进行</span><span>update</span><span>，</span><span>render target</span><span>也就是渲染的目的地，一般会有两种，一种是</span><span>render texture</span><span>，一种是</span><span>render buffer</span><span>，接着进入</span><span>RenderSystem::_updateAllRenderTargets</span><span>，可以看到在</span><span>render system</span><span>中，对创建出来的</span><span>render target</span><span>是用</span><span>RenderTargetPriorityMap</span><span>来保存的，以便按照一定的顺序来对</span><span>render target</span><span>进行</span><span>update</span><span>，因为在渲染物体到</span><span>render buffer</span><span>时，一般会用到之前渲染好的</span><span>render texture</span><span>，所以</span><span>render texture</span><span>形式的</span><span>render target</span><span>需要在</span><span>render buffer</span><span>之前进行更新。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;进入</span><span>render target</span><span>的</span><span>update</span><span>，可以看到，它仍然把</span><span>update</span><span>操作继续传递下去，调用所有挂在这个</span><span>render target</span><span>上的</span><span>viewport</span><span>的</span><span>update</span><span>。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Viewport</span><span>其实就是定义了</span><span>render target</span><span>上的一块要进行更新的区域，所以一个</span><span>render target</span><span>是可以挂多个</span><span>viewport</span><span>的，以实现多人对战时分屏，或者是画中画等效果，可以把</span><span>OGRE</span><span>中的</span><span>viewport</span><span>看成是保存</span><span>camera</span><span>和</span><span>rendertarget</span><span>这两者的组合，把</span><span>viewport</span><span>中所定义的</span><span>camera</span><span>所看到的场景内容渲染到</span><span>viewport</span><span>所定义的</span><span>render target</span><span>的区域里。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Viewport</span><span>还有一个重要信息是</span><span>ZOrder</span><span>，可以看到</span><span>RenderTarget</span><span>中的</span><span>ViewportList</span><span>带有一个比较函数，所以在</span><span>RenderTarget::update</span><span>中，</span><span>ZOrder</span><span>越小的，越先被渲染，所以，如果两个</span><span>viewport</span><span>所定义的区域互相重叠了，而且</span><span>ZOrder</span><span>又不一样，最终的效果就是</span><span>ZOrder</span><span>小的</span><span>viewport</span><span>的内容会被</span><span>ZOrder</span><span>大的</span><span>viewport</span><span>的内容所覆盖。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;继续进入</span><span>Viewport::update</span><span>，就像前面所说，它调用它所引用的</span><span>camera</span><span>来渲染整个场景，而在</span><span>Camera::_renderScene</span><span>中，是调用</span><span>SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)</span><span>。</span><span>SceneManager::_renderScene</span><span>里就是具体的渲染流程了。从函数名称还有参数也可以看出来，这个函数的作用就是利用所指定的</span><span>camera</span><span>和</span><span>viewport</span><span>，来把场景中的内容渲染到</span><span>viewport</span><span>所指定的</span><span>render target</span><span>的某块区域中。根据</span><span>camera</span><span>，我们可以定出</span><span>view matrix</span><span>，</span><span>projection matrix</span><span>，还可以进行视锥剔除，只渲染看得见的物体。注意，我们这里只看标准的</span><span>SceneManager</span><span>的方法，不看</span><span>BspSceneManager</span><span>派生类的方法，而且，我们会抛开跟主线无关的内容，如对</span><span>shadow</span><span>的</span><span>setup</span><span>，骨骼动画的播放，</span><span>shader</span><span>参数的传递等，因为我们只注重渲染的主流程。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在</span><span>SceneManager::_renderScene</span><span>中所应看的第一个重要函数是</span><span>_updateSceneGraph</span><span>，</span><span>OGRE</span><span>对场景的组织是通过节点树来组织的，一个节点，你可以看成是空间中的某些变换的组合，如位置，缩放，旋转等，这些变换，会作用到挂接在这些节点上的具体的物体的信息，也就是说，节点保存了</span><span>world transform</span><span>，对具体的物体，如一个人，在空间中的定位，都是通过操作节点来完成的。同时节点还保存了一个世界坐标的</span><span>AABB</span><span>，这个</span><span>AABB</span><span>能容纳所有它所挂接的物体的大小，主要是用于视锥裁减的，如果当前摄像机看不见某个节点的</span><span>AABB</span><span>，那么说明摄像机看不见节点所挂接的所有物体，所以在渲染时可以对这个节点视而不见。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_updateSceneGraph</span><span>的内部处理比较繁琐，我们只需知道，经过了</span><span>_updateSceneGraph</span><span>，场景节点树中的每个节点都经过了更新，包括位置，缩放，和方位，还有节点的包围盒。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;继续回到</span><span>SceneManager::_renderScene</span><span>，接下来要看的是</span><span>setViewport</span><span>，它会调用具体的</span><span>renderer</span><span>的</span><span>setviewport</span><span>的操作，设置</span><span>viewport</span><span>中所挂接的</span><span>render target</span><span>为当前所要渲染的目标，</span><span>viewport</span><span>中的区域为当前所要渲染的目标中的区域。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;接下来要碰到</span><span>OGRE</span><span>渲染流程中的一个重要的概念，</span><span>Render Queue</span><span>。这个东西实在内容比较多，还是以后有机会单独提出来说吧，你可以简单把它想成是一个容器，里面的元素就是</span><span>renderable</span><span>，每个</span><span>renderable</span><span>可以看成是每次调用</span><span>drawprimitive</span><span>函数所渲染的物体，可以是一个模型，也可以是模型的一部分。在</span><span>RenderQueue</span><span>中，它会按材质来分组这些</span><span>renderable</span><span>，还会对</span><span>renderable</span><span>进行排序。</span></p>
<p><span>&nbsp;&nbsp;&nbsp; &nbsp;在每一次调用</span><span>SceneManager::_renderScene</span><span>时，都会调用</span><span>SceneManager::prepareRenderQueue</span><span>来清理</span><span>RenderQueue</span><span>，然后再调用</span><span>SceneManager::__findVisibleObjects</span><span>来把当前摄像机所能看见的物体都加入到</span><span>RenderQueue</span><span>中。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SceneManager::__findVisibleObjects</span><span>是一个递归的处理过程，它从场景的根节点开始，先检查摄像机是否能看见这个节点的包围盒（包围盒在</span><span>_updateSceneGraph</span><span>时已经计算好了），如果看不见，那么这个节点，还有它的子节点都不用管了。如果能看见，再检测挂在这个节点上的所有</span><span>MovableObject</span><span>，如果当前所检测的</span><span>MovableObject</span><span>是可见的，就会调用它的</span><span>_updateRenderQueue</span><span>方法，一般在这个方法里就可以把和这个</span><span>MovableObject</span><span>相关的</span><span>renderable</span><span>送入</span><span>RenderQueue</span><span>了。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这里要说说</span><span>MovableObject</span><span>，</span><span>MovableObject</span><span>主要是用于表示场景中离散的物体，如</span><span>Entity</span><span>，顾名思义，能移动的物体，不过它的&#8220;能移动&#8221;这个能力是要通过</span><span>SceneNode</span><span>来实现的，所以</span><span>MovableObject</span><span>来能显示出来，首先得先挂接在某个场景节点上，通过场景节点来定位。你可以控制</span><span>MovableObject</span><span>的一些属性，如某个</span><span>MovableObject</span><span>是否要显示，是否要隐藏，都可以通过</span><span>MovableObject::setVisible</span><span>方法来实现。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;检测完该节点上的</span><span>MovableObject</span><span>之后，就继续调用所有子节点的</span><span>_findVisibleObjects</span><span>方法，一直递归下去。这样，就能把场景中所有要渲染的</span><span>renderable</span><span>所加入到</span><span>RenderQueue</span><span>中了。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;至此，我们就拥有了要渲染的物体的信息了，接下来就是对这些物体进行渲染了，你会发现跟</span><span>D3D</span><span>或</span><span>OpenGL</span><span>的代码很类似的调用：</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>mDestRenderSystem-&gt;clearFrameBuffer</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mDestRenderSystem-&gt;_beginFrame<span>&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>mDestRenderSystem-&gt;_setProjectionMatrix</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>mDestRenderSystem-&gt;_setViewMatrix </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>_renderVisibleObjects();</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mDestRenderSystem-&gt;_endFrame();</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这些</span><span>api</span><span>的作用和</span><span>D3D</span><span>中的类似调用的作用都差不多，这里再说一下</span><span>_renderVisibleObjects()</span><span>，在这个函数中，会对</span><span>RenderQueue</span><span>中的每个</span><span>renderable</span><span>进行渲染，用的是</span><span>visitor</span><span>模式来遍历操作每个</span><span>renderable</span><span>，最终在</span><span>SceneManager::renderSingleObject</span><span>中取出每个</span><span>renderable</span><span>所保存的顶点，索引，世界矩阵等信息，来进行渲染。这其中还包括了查找离该</span><span>renderable</span><span>最近的光源等操作，比较复杂。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;到这里，</span><span>SceneManager::_renderScene</span><span>的流程基本走完了，也就是说，</span><span>OGRE</span><span>一帧中的渲染流程差不多也结束了，你应该也发现，这个流程跟你用</span><span>D3D</span><span>写一个简单程序的流程基本是一样的，在这个流程的基础上，再去看具体的实现，如怎么样设置纹理，怎么样调用你熟悉的</span><span>D3D</span><span>或</span><span>OpenGL</span><span>的</span><span>API</span><span>来渲染物体，应该会简单得多。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;对</span><span>OGRE</span><span>的渲染流程的大概介绍到这里也结束了，很多细节都没涉及，以后有机会再写吧。</span></p>
<script type=text/javascript>
//<![cdata[
Sys.WebForms.PageRequestManager._initialize('AjaxHolder$scriptmanager1', document.getElementById('Form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tAjaxHolder$UpdatePanel1'], [], [], 90);
//]]&gt;
</script>
</span>
<img src ="http://www.cppblog.com/life02/aggbug/94891.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/life02/" target="_blank">life02</a> 2009-08-31 16:21 <a href="http://www.cppblog.com/life02/articles/94891.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>