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窗口的创建(一)_第1张图片
函数实现中的变量_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窗口的创建(一)_第2张图片

可以看到,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窗口的创建(二)》


你可能感兴趣的:(OSG,渲染窗口)