1. 程式人生 > >Ogre的渲染主迴圈解析

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渲染物體時候的流程:

  1. 設定頂點資料,頂點屬性,貼圖資料:這一步可以封裝成讀取模型以及其對應的貼圖操作
  2. 清理顏色緩衝、深度緩衝等
  3. 設定shader並傳入其所需的引數
  4. 繪製頂點並交換視窗緩衝

在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等操作。

參考

http://gad.qq.com/article/detail/14402