Ogre的渲染主迴圈解析
渲染迴圈
一般由應用程式呼叫root->startRendering(void)開啟渲染迴圈。
void Root::startRendering(void) { ... //遊戲渲染迴圈是一個無限迴圈,除非被frame listeners或者queueEndRendering()終止 mQueuedEnd = false; while( !mQueuedEnd ) { ... if (!renderOneFrame()) break; } }
從RenderTarget到場景
而Root::renderOneFrame()函式將呼叫Root::_updateAllRenderTargets(void)
class _OgreExport Root : public Singleton<Root>, public RootAlloc { protected: RenderSystem* mActiveRenderer; ... bool Root::_updateAllRenderTargets(void) { // update all targets but don't swap buffers mActiveRenderer->_updateAllRenderTargets(false); ... } ... }
Root::_updateAllRenderTargets(void)將呼叫Root當前啟用的RenderSystem例項的__updateAllRenderTargets(false)方法,在這裡,RenderSystem對所有的RenderTarget進行update,RenderTarget指的是渲染的目的地,一種是RenderTexture,即渲染到紋理,另一種是RenderWindow,即渲染到視窗。
/** The render targets, ordered by priority. */ RenderTargetPriorityMap mPrioritisedRenderTargets; void RenderSystem::_updateAllRenderTargets(bool swapBuffers) { // Update all in order of priority // This ensures render-to-texture targets get updated before render windows RenderTargetPriorityMap::iterator itarg, itargend; itargend = mPrioritisedRenderTargets.end(); for( itarg = mPrioritisedRenderTargets.begin(); itarg != itargend; ++itarg ) { if( itarg->second->isActive() && itarg->second->isAutoUpdated()) itarg->second->update(swapBuffers); } }
在當前啟用的RenderSystem例項中,是用RenderTargetPriorityMap來儲存RenderTarget,這是因為有的RenderTarget需要在其他RenderTarget之前渲染。
而RenderTarget::update最終會呼叫到RenderTarget::_updateAutoUpdatedViewports(bool updateStatistics)
void RenderTarget::_updateAutoUpdatedViewports(bool updateStatistics)
{
// Go through viewports in Z-order
// Tell each to refresh
ViewportList::iterator it = mViewportList.begin();
while (it != mViewportList.end())
{
Viewport* viewport = (*it).second;
if(viewport->isAutoUpdated())
{
_updateViewport(viewport,updateStatistics);
}
++it;
}
}
可以看到,RenderTarget會遍歷mViewPortList,對每一個Viewport呼叫_updateViewport(…)函式,即一個RenderTarget對應著多個Viewport,ViewPort是視窗中被更新的地方,一個視窗可以有多個ViewPort,多個Viewport可以在一個視窗中。例如下圖中只有一個視窗,但是卻有三個Viewport。
RenderTarget::_updateViewport(viewport,updateStatistics)隨後會呼叫到Viewport::update(void)
void Viewport::update(void)
{
if (mCamera)
{
if (mCamera->getViewport() != this)
mCamera->_notifyViewport(this);
// Tell Camera to render into me
mCamera->_renderScene(this, mShowOverlays);
}
}
對於每一個Viewport,只有一個Camera引用。一個Viewport是對一個Camera所拍攝到的場景的反映。所以說一個Viewport只能繫結一個Camera。Camera::_renderScene(Viewport *vp, bool includeOverlays)函式的作用主要為呼叫其繫結的Scene的渲染函式:
SceneManager *mSceneMgr;
void Camera::_renderScene(Viewport *vp, bool includeOverlays)
{
...
//render scene
mSceneMgr->_renderScene(this, vp, includeOverlays);
...
}
一個Camera同時只可以拍攝一個場景,所以一個Camera只可以繫結一個場景。
從場景到渲染佇列
Ogre中,場景定義了一個遊戲世界的組織,比如說主角在哪,怪物的出生點在哪,目的地在哪,可以類比Unity中的scene。每一個場景對應一個SceneManager,其負責場景物體的建立和刪除,以及進行場景查詢和呼叫渲染佇列的渲染。
其中,場景查詢的作用為決定哪些物件被送入到RenderSystem中進行渲染,通過減少需要渲染的物體來獲得更好的渲染效能表現。
上節說到_renderScene函式,該函式會更新場景的變換資訊(Transform)以及進行場景查詢的操作:
SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)
{
...
_updateSceneGraph(camera);
...
setViewport(vp);
...
prepareRenderQueue();
...
_findVisibleObjects(camera, &(camVisObjIt->second),
mIlluminationStage == IRS_RENDER_TO_TEXTURE? true : false);
...
}
其中_renderScene中會呼叫__updateSceneGraph函式更新整個場景的變換資訊:
void SceneManager::_updateSceneGraph(Camera* cam)
{
...
// Cascade down the graph updating transforms & world bounds
// ...
getRootSceneNode()->_update(true, false);
...
}
接下來是setViewport函式,它會呼叫當前的RenderSystem的setViewport操作,設定Viewport中所包含的RenderTarget為當前所要渲染的目標,而Viewport中的區域為當前所要渲染的目標區域。
void SceneManager::setViewport(Viewport* vp)
{
mCurrentViewport = vp;
// Set viewport in render system
mDestRenderSystem->_setViewport(vp);
// Set the active material scheme for this viewport
MaterialManager::getSingleton().setActiveScheme(vp->getMaterialScheme());
}
接下來一個重要的概念RenderQueue。可以簡單把它想成是一個容器,裡面的元素就是Renderable,每個Renderable可以看成是每次繪製時需要渲染的物體,可以是一個模型,也可以是模型的一部分。在RenderQueue中,它會按材質Material來分組這些Renderable,還會對Renderable進行一定規則的排序。之所以這麼做,是因為使用相同材質的Material會使用同樣的Shader,通過減少Shader切換的頻率可以達到優化渲染效率的作用。
在每一次呼叫SceneManager::_renderScene時,都會呼叫SceneManager::prepareRenderQueue來清理RenderQueue,然後再呼叫SceneManager::__findVisibleObjects來把當前攝像機所能看見的物體都加入到RenderQueue中,該動作是為了減少送入渲染佇列中的物體數量:
void SceneManager::_findVisibleObjects(
Camera* cam, VisibleObjectsBoundsInfo* visibleBounds, bool onlyShadowCasters)
{
// Tell nodes to find, cascade down all nodes
getRootSceneNode()->_findVisibleObjects(cam, getRenderQueue(), visibleBounds, true,
mDisplayNodes, onlyShadowCasters);
}
渲染開始
在進行完場景查詢之後,我們已經將需要渲染的物體送入了渲染佇列中,回憶一下使用圖形API渲染物體時候的流程:
- 設定頂點資料,頂點屬性,貼圖資料:這一步可以封裝成讀取模型以及其對應的貼圖操作
- 清理顏色緩衝、深度緩衝等
- 設定shader並傳入其所需的引數
- 繪製頂點並交換視窗緩衝
在SceneManager::_renderScene函式中,當執行完場景查詢之後,便進入了實際渲染流程:
...
// Clear the viewport if required
if (mCurrentViewport->getClearEveryFrame())
{
mDestRenderSystem->clearFrameBuffer(
mCurrentViewport->getClearBuffers(),
mCurrentViewport->getBackgroundColour(),
mCurrentViewport->getDepthClear() );
}
...
// Set rasterisation mode
mDestRenderSystem->_setPolygonMode(camera->getPolygonMode());
// Set initial camera state
mDestRenderSystem->_setProjectionMatrix(mCameraInProgress->getProjectionMatrixRS());
mCachedViewMatrix = mCameraInProgress->getViewMatrix(true);
...
...
setViewMatrix(mCachedViewMatrix);
// Render scene content
{
...
_renderVisibleObjects();
}
可以看到清理緩衝以及設定好Projection Matrix、ViewMatrix等操作。