天天看點

osg視窗的建立(一)

使用osg時我們首先需要做的第一件事就是建立一個渲染視窗,當我們配置好一個osg的開發環境,一般會編寫以下一段測試程式:

#include <osg/Node>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>

int main()
{
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
	//配置OSG_FILE_PATH環境變量指向glider.osg檔案所在的目錄
	osg::ref_ptr<osg::Node> gliderNode = osgDB::readNodeFile("glider.osg");
	viewer->setSceneData(gliderNode);
	viewer->run();
}
           

osg的視窗建立過程是在viewer->run() 中實作的,下面我們就對該過程做一個完整的剖析。

  • osg的一幀

在osg中,一幀的渲染過程在osgViewer::ViewerBase中,實作如下:

void ViewerBase::frame(double simulationTime)
{
    if (_done) return;

    if (_firstFrame)
    {
        viewerInit();

        if (!isRealized())
        {
            realize();
        }

        _firstFrame = false;
    }
    advance(simulationTime);

    eventTraversal();
    updateTraversal();
    renderingTraversals();
}
           

osgViewer::Viewer繼承于osgViewer::ViewerBase,它并沒有改寫VIewerBase中的實作,這兩個類的繼承關系圖如下:

osg視窗的建立(一)

函數實作中的變量_firstFrame表示是否是第一幀,如果是渲染開始的第一幀那麼需要做一些設定,我們可以猜想視窗的建立過程就在這裡(畢竟沒有視窗就無法進行視窗渲染),那麼讓我們看看這幾行代碼的實作内容

  • viewerInit

viewerInit是osgViewer::ViewerBase中的一個純虛函數,它必然會在子類osgViewer::Viewer中實作,在osgViewer::Viewer中的實作如下:

void viewerInit() 
  { 
    init(); 
  }
           

viewerInit直接調用了osgViewer::Viewer中的init方法,但是檢視osgViewer::viewer的源碼發現它的實作檔案中并沒有init函數,說明在父類中有實作,osgViewer::Viewer隻是直接調用了父類的實作,檢視代碼發現确實如此,在osgViewer::View中實作了init方法,它的實作代碼如下:

void View::init()
{
    OSG_INFO<<"View::init()"<<std::endl;

    osg::ref_ptr<osgGA::GUIEventAdapter> initEvent = _eventQueue->createEvent();
    initEvent->setEventType(osgGA::GUIEventAdapter::FRAME);

    if (_cameraManipulator.valid())
    {
        _cameraManipulator->init(*initEvent, *this);
    }
}
           

  從變量的名稱可以猜測出_eventQueue 的功能,它用于儲存該視景器的事件隊列。OSG中代表事件的類是osgGA::GUIEventAdapter,它可以用于表達各種類型的滑鼠、鍵盤、觸壓筆和視窗事件。在使用者程式中,我們往往通過繼承osgGA::GUIEventHandler 類,并重寫handle函數的方法, 擷取實時的滑鼠/ 鍵盤輸入, 并進而實作相應的使用者代碼( 參見osgkeyboardmouse)。

_eventQueue 除了儲存一個GUIEventAdapter 的連結清單之外,還提供了一系列對連結清單及其元素的操作函數,這其中,createEvent 函數的作用是配置設定和傳回一個新的GUIEventAdapter事件的指針。随後,這個新事件的類型被指定為 FRAME 事件,即每幀都會觸發的一個事件。

_cameraManipulator是視景器中所用的場景漫遊器的執行個體。通常我們都會使用setCameraManipulator 來設定這個變量的内容, 例如軌迹球漫遊器(TrackballManipulator)可以使用滑鼠拖動來觀察場景,而駕駛漫遊器(DriveManipulator)則使用類似于汽車駕駛的效果來實作場景的漫遊。上面的代碼将新建立的 FRAME 事件和Viewer 對象本身傳遞給_cameraManipulator 的init 函數,不同的漫遊器(如TrackballManipulator、DriveManipulator)會重寫各自的init 函數,實作自己所需的初始化工作。如果讀者希望自己編寫一個場景的漫遊器,那麼覆寫并使用osgGA::CameraManipulator::init 就可以靈活地初始化自定義漫遊器的功能了,它的調用時機就在這裡。

  • isRealized

該函數主要用來判斷場景中的視窗設定是否已經準備就緒,它在ViewerBase中同樣是一個純虛函數,在osgViewer::Viewer中實作:

bool Viewer::isRealized() const
{
    Contexts contexts;
    const_cast<Viewer*>(this)->getContexts(contexts);

    unsigned int numRealizedWindows = 0;

    // clear out all the previously assigned operations
    for(Contexts::iterator citr = contexts.begin();
        citr != contexts.end();
        ++citr)
    {
        if ((*citr)->isRealized()) ++numRealizedWindows;
    }

    return numRealizedWindows > 0;
}
           

首先我們看一看 Contexts是什麼? Contexts的定義:

typedef std::vector<osg::GraphicsContext*> Contexts;
           

它是一個osg::GraphicsContext的清單,GraphicsContext是什麼呢,它是圖形渲染上下文。說的更清楚一點就是:這個GrahicsContext是一個可以讓OpenGL在它上面就行繪制的目标,它可以是一個視窗、也可以是一個記憶體空間,所有OpenGL的渲染結果都會在它上面呈現。就像我們在繪畫的時候需要知道我的畫會畫在什麼地方?是紙上、牆上、還是地闆上?這個GraphicsContext就是這樣一個繪制載體的一種抽象的說法。 我們可以看一下它的繼承關系圖:

osg視窗的建立(一)

可以看到,OpenGL的渲染可以是在GrahicsContext的子類 GraphicsWindow中(視窗上),也可以是在PixelBuffer中(記憶體上)在記憶體中的渲染可以做很多事情,比如離屏渲染和渲染到紋理技術就是這樣一個原理,将繪制的内容并不顯示在視窗上來實作某些特殊的效果。

讓我們在看一看 getContexts的實作

typedef std::set<osg::GraphicsContext*> ContextSet;
    ContextSet contextSet;

    contexts.clear();

    if (_camera.valid() &&
        _camera->getGraphicsContext() &&
        (_camera->getGraphicsContext()->valid() || !onlyValid))
    {
        contextSet.insert(_camera->getGraphicsContext());
        contexts.push_back(_camera->getGraphicsContext());
    }

    for(unsigned int i=0; i<getNumSlaves(); ++i)
    {
        Slave& slave = getSlave(i);
        osg::GraphicsContext* sgc = slave._camera.valid() ? slave._camera->getGraphicsContext() : 0;
        if (sgc && (sgc->valid() || !onlyValid))
        {
            if (contextSet.count(sgc)==0)
            {
                contextSet.insert(sgc);
                contexts.push_back(sgc);
            }
        }
    }
           

代碼還是比較清晰的,osg會搜集 Viewer視景器中相機(一個視景器Viewer類包含一個主相機和多個從相機(Slave))的所有渲染上下文,并将這些渲染上下文添加到數組中。

繼續看viewer中isRealized函數體接下來的代碼

for(Contexts::iterator citr = contexts.begin();
        citr != contexts.end();
        ++citr)
    {
        if ((*citr)->isRealized()) ++numRealizedWindows;
    }
           

可以看到viewer中的isRealized會調用GraphicsContext中的isRealized,GraphicsContext中的isRealized的實作如下:

inline bool isRealized() const { return isRealizedImplementation(); }
           

接着追溯下去可以看到 isRealziedImplementation的實作就有很多了,它在GraphicsContext類中是一個純虛函數,實作需要追溯到平台相關的實作中,包括了前面所繪制的類的繼承關系圖中的  GraphicsWindowQt 、GraphicsWindowWin32、GraphicsWindowCocoa、GraphcisWindowCarbon、GraphicsWindowIOS、GraphicsWindowX11,分别對應Qt、Win32、MacOS X、IOS、以及Linux的實作,

關于GraphicsContext實作的後續内容可以繼續閱讀《osg視窗的建立(二)》