使用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();
}
函数实现中的变量_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就是这样一个绘制载体的一种抽象的说法。 我们可以看一下它的继承关系图:
可以看到,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窗口的创建(二)》