天天看點

OGRE - RenderProcess (渲染流程)

        很早以前就想寫一些關于OGRE的文章了,一直沒機會。

了解一個渲染引擎,我覺得最重要的是先抓住了它的主架構,它的主線,渲染流程,不然的話,一個引擎幾萬行,甚至幾十萬行的代碼,光是打開solution就能吓你一跳了,OGRE也有十幾萬行的代碼量,我一開始看它的時候也是無從下手,感覺代碼太多了,不知道從哪開始看好,這個class看看,那個class看看,由于對整個引擎沒有一個清晰的認識,看過了也印象不深,是以,最後,還是決定先找出它的主線,了解它的渲染流程,這樣才能有機地把各個部分聯系起來。

      這篇短文也是對OGRE的主要渲染流程的一個介紹,可能對一些class不會太多地去介紹具體的實作細節。我所用的代碼都是取自于OGRE的最新的CVS版本。

      讀者最好對OGRE有一定的了解,至少得看懂它的example,不然可能一些東西了解起來比較困難。對D3D,OPENGL有一定了解更好。

D3D Render Process

      如果你看過D3D SDK中帶的例子,你一定知道一個比較簡單的3D程式要運作起來,至少都會涉及以下的幾部分:

      首先是資料的來源,包括頂點資料,紋理資料等,這些資料可以從檔案中讀取,也可以在程式運作時生成。

      接下來,我們會建立頂點緩沖區把頂點儲存起來,

      建立texture對象來表示texture,對頂點組成的物體設定它在世界坐标系下的坐标,

      設定錄影機的位置,視點,設定viewport的位置和大小,

      然後就可以在渲染循環中開始調用渲染操作了,

      經過了front buffer和back buffer的交換,我們就能在螢幕上看到3D圖形了,

僞代碼如下:

      setupVertexBuffer

      setWorldTransform

      setCamera

      setProjectionTransform

      setViewport

       beginFrame

      setTexture

      drawObject

      endFrame

OGRE RenderProcess

Introduction

1. _fireFrameStarted()

    2. 按某種優先級更新所有渲染目标

      2.1 firePreUpdate()

      2.2 更新所有視口

        2.2.1 fireViewportPreUpdate()

        2.2.2 場景管理的渲染函數

          2.2.2.1 更新陰影

          2.2.2.2 更新動畫

          2.2.2.3 更新節點

          2.2.2.4 更新一些參數

          2.2.2.5 清空渲染隊列

          2.2.2.6 填充渲染隊列 進行可見性判别

            2.2.2.6.1 firePreFindVisibleObjects()

            2.2.2.6.2 查找可見節點并加入渲染隊列

            2.2.2.6.3 firePostFindVisibleObjects()

          2.2.2.7 依次渲染每個隊列組

            2.2.2.7.1 fireRenderQueueStarted()

            2.2.2.7.2 渲染該隊列組

            2.2.2.7.3 fireRenderQueueEnded()

        2.2.3 fireViewportPostUpdate()

      2.3 firePostUpdate()

    3. _fireFrameRenderingQueued()

    4. swapBuffer()

    5. _fireFrameEnded()

More For Detail

SceneManager::_renderScene

|

|-------_updateSceneGraph(camera);

|

|-------prepareRenderQueue();

|

|-------_findVisibleObjects

|

|

|

|

|

|

|------_renderVisibleObjects

|      |

|      |-----renderVisibleObjectsDefaultSequence

|            |

|            |-------_renderQueueGroupObjects

|                    |

|                    |---------renderBasicQueueGroupObjects

|                              |

|                              |---------sort

|                              |---------renderObjects

|                              |         |

|                              |         |--------acceptVisitorGrouped

|                              |                  |

|                              |                  |---------visit

|                              |                            |

|                                                           |-------------renderSingleObject

      以下就是渲染一個物體的主要步驟,在我看來,這就是3D程式的主線,同樣道理,無論你多複雜的渲染引擎,都得實作上述的這些步驟,其他的一些效果如陰影,光照等,都是附着在這條主線上的,是以,如果你能在你所研究的渲染引擎上也清晰地看到這條主線,可能對你深入地研究它會大有幫助

下面,我們就一起來找到OGRE中的這條主線。

      Root::renderOneFrame

      OGRE的渲染循環都是起源于Root::renderOneFrame,這個函數在OGRE自帶的example中是不會顯式調用的,因為example都調用了Root::startRendering,由startRendering來調用renderOneFrame,如果你用OGRE來寫真正的遊戲,或者編輯器,你可能就需要在的消息主循環中調用renderOneFrame了,顧名思義,這個函數就是對整個OGRE進行一幀的更新,包括動畫,渲染狀态的改變,渲染api的調用等,在這個函數中,會包括了我們上述僞代碼的幾乎全部内容,是以是本文的重點所在。

      進入renderOneFrame,可以看到頭尾兩個fire函數,這種函數在OGRE中經常出現,一般都是fire…start和fire…end一起出現的,在這些函數中,可能會處理一些使用者自定義的操作,如_fireFrameStarted就會對是以的frameListener進行處理,這些fire函數可以暫時不用理會,繼續看_updateAllRenderTargets,在這個函數中,會委派目前所用的renderer對所有建立出來的render target進行update,render target也就是渲染的目的地,一般會有兩種,一種是render texture,一種是render buffer,接着進入RenderSystem::_updateAllRenderTargets,可以看到在render system中,對建立出來的render target是用RenderTargetPriorityMap來儲存的,以便按照一定的順序來對render target進行update,因為在渲染物體到render buffer時,一般會用到之前渲染好的render texture,是以render texture形式的render target需要在render buffer之前進行更新。

      RenderTarget

      進入render target的update,可以看到,它仍然把update操作繼續傳遞下去,調用所有挂在這個render target上的viewport的update。

     Viewport

      Viewport其實就是定義了render target上的一塊要進行更新的區域,是以一個render target是可以挂多個viewport的,以實作多人對戰時分屏,或者是畫中畫等效果,可以把OGRE中的viewport看成是儲存camera和rendertarget這兩者的組合,把viewport中所定義的camera所看到的場景内容渲染到viewport所定義的render target的區域裡。

      Viewport還有一個重要資訊是ZOrder,可以看到RenderTarget中的ViewportList帶有一個比較函數,是以在RenderTarget::update中,ZOrder越小的,越先被渲染,是以,如果兩個viewport所定義的區域互相重疊了,而且ZOrder又不一樣,最終的效果就是ZOrder小的viewport的内容會被ZOrder大的viewport的内容所覆寫。

      繼續進入Viewport::update,就像前面所說,它調用它所引用的camera來渲染整個場景,而在Camera::_renderScene中,是調用SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)。SceneManager::_renderScene裡就是具體的渲染流程了。從函數名稱還有參數也可以看出來,這個函數的作用就是利用所指定的camera和viewport,來把場景中的内容渲染到viewport所指定的render target的某塊區域中。根據camera,我們可以定出view matrix,projection matrix,還可以進行視錐剔除,隻渲染看得見的物體。注意,我們這裡隻看标準的SceneManager的方法,不看BspSceneManager派生類的方法,而且,我們會抛開跟主線無關的内容,如對shadow的setup,骨骼動畫的播放,shader參數的傳遞等,因為我們隻注重渲染的主流程。

     SceneManager

     在SceneManager::_renderScene中所應看的第一個重要函數是_updateSceneGraph,OGRE對場景的組織是通過節點樹來組織的,一個節點,你可以看成是空間中的某些變換的組合,如位置,縮放,旋轉等,這些變換,會作用到挂接在這些節點上的具體的物體的資訊,也就是說,節點儲存了world transform,對具體的物體,如一個人,在空間中的定位,都是通過操作節點來完成的。同時節點還儲存了一個世界坐标的AABB,這個AABB能容納所有它所挂接的物體的大小,主要是用于視錐裁減的,如果目前錄影機看不見某個節點的AABB,那麼說明錄影機看不見節點所挂接的所有物體,是以在渲染時可以對這個節點視而不見。

      _updateSceneGraph的内部處理比較繁瑣,我們隻需知道,經過了_updateSceneGraph,場景節點樹中的每個節點都經過了更新,包括位置,縮放,和方位,還有節點的包圍盒。

      繼續回到SceneManager::_renderScene,接下來要看的是setViewport,它會調用具體的renderer的setviewport的操作,設定viewport中所挂接的render target為目前所要渲染的目标,viewport中的區域為目前所要渲染的目标中的區域。

     Render Queue

     接下來要碰到OGRE渲染流程中的一個重要的概念,Render Queue。這個東西實在内容比較多,還是以後有機會單獨提出來說吧,你可以簡單把它想成是一個容器,裡面的元素就是renderable,每個renderable可以看成是每次調用drawprimitive函數所渲染的物體,可以是一個模型,也可以是模型的一部分。在RenderQueue中,它會按材質來分組這些renderable,還會對renderable進行排序。

      在每一次調用SceneManager::_renderScene時,都會調用SceneManager::prepareRenderQueue來清理RenderQueue,然後再調用SceneManager::__findVisibleObjects來把目前錄影機所能看見的物體都加入到RenderQueue中。

      SceneManager::__findVisibleObjects是一個遞歸的處理過程,它從場景的根節點開始,先檢查錄影機是否能看見這個節點的包圍盒(包圍盒在_updateSceneGraph時已經計算好了),如果看不見,那麼這個節點,還有它的子節點都不用管了。如果能看見,再檢測挂在這個節點上的所有MovableObject,如果目前所檢測的MovableObject是可見的,就會調用它的_updateRenderQueue方法,一般在這個方法裡就可以把和這個MovableObject相關的renderable送入RenderQueue了。

     MovableObject

     這裡要說說MovableObject,MovableObject主要是用于表示場景中離散的物體,如Entity,顧名思義,能移動的物體,不過它的“能移動”這個能力是要通過SceneNode來實作的,是以MovableObject來能顯示出來,首先得先挂接在某個場景節點上,通過場景節點來定位。你可以控制MovableObject的一些屬性,如某個MovableObject是否要顯示,是否要隐藏,都可以通過MovableObject::setVisible方法來實作。

      檢測完該節點上的MovableObject之後,就繼續調用所有子節點的_findVisibleObjects方法,一直遞歸下去。這樣,就能把場景中所有要渲染的renderable所加入到RenderQueue中了。

      至此,我們就擁有了要渲染的物體的資訊了,接下來就是對這些物體進行渲染了,你會發現跟D3D或OpenGL的代碼很類似的調用:

       mDestRenderSystem->clearFrameBuffer

      mDestRenderSystem->_beginFrame    

       mDestRenderSystem->_setProjectionMatrix

       mDestRenderSystem->_setViewMatrix

       _renderVisibleObjects();

      mDestRenderSystem->_endFrame();

      這些api的作用和D3D中的類似調用的作用都差不多,這裡再說一下_renderVisibleObjects(),在這個函數中,會對RenderQueue中的每個renderable進行渲染,用的是visitor模式來周遊操作每個renderable,最終在SceneManager::renderSingleObject中取出每個renderable所儲存的頂點,索引,世界矩陣等資訊,來進行渲染。這其中還包括了查找離該renderable最近的光源等操作,比較複雜。

      到這裡,SceneManager::_renderScene的流程基本走完了,也就是說,OGRE一幀中的渲染流程差不多也結束了,你應該也發現,這個流程跟你用D3D寫一個簡單程式的流程基本是一樣的,在這個流程的基礎上,再去看具體的實作,如怎麼樣設定紋理,怎麼樣調用你熟悉的D3D或OpenGL的API來渲染物體,應該會簡單得多。

      對OGRE的渲染流程的大概介紹到這裡也結束了,很多細節都沒涉及,以後有機會再寫吧。

繼續閱讀