在osg中,编写以下简单代码
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer(); viewer->setSceneData(osgDB::readNodeFile("glider.osg")); viewer->run();运行可以看到场景中的滑翔机,并通过鼠标操作它的姿态。你有没有觉得很奇怪,使用opengl代码在渲染函数中写下绘制代码时,得到的场景是静止的,需要事件处理函数才可以控制场景的变化。
事实上如此简单的代码,osg替我们作了大量的工作,包括添加了用来操作场景的漫游器。下面我们介绍一下漫游器的相关内容:
首先要理解OpenGL中的模型视图矩阵的概念,在OpenGL中,通过模型视图矩阵,我们将顶点数据从局部坐标系转换到相机坐标系统,OpenGL中模型视图矩阵是一体的,或者说可以理解为一体。但在OSG中它把这两者分离开来,OSG通过场景的树状结构实现模型矩阵(通过场景中的MatrixTransform),把视图矩阵放在漫游器中,通过两者产生模型视图矩阵。关于视图矩阵,有一个重要的结论:相机在世界坐标系统中的位置姿态矩阵等于相机观察矩阵(视图矩阵)的逆矩阵。相机的位置姿态矩阵可以理解为相机坐标系统下的顶点向世界坐标系统转换的变化矩阵,而观察矩阵(视图矩阵)则理解为世界坐标系的顶点转换到相机坐标系中的变化矩阵。对于这个理论的推导,可以参考:推导相机变换矩阵
另外几个需要了解的问题是:1)OSG中的漫游器是到底是什么?2)漫游器是在什么时候被添加的?3)漫游器是怎么起作用的? 4)我们应该怎么样编写自己的漫游器?下面我们就一一分析
viewer.setCameraManipulator(new osgGA::TrackballManipulator);
int Viewer::run() { if (!getCameraManipulator() && getCamera()->getAllowEventFocus()) { setCameraManipulator(new osgGA::TrackballManipulator()); } setReleaseContextAtEndOfFrameHint(false); return ViewerBase::run(); }
void View::setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition) { _cameraManipulator = manipulator; if (_cameraManipulator.valid()) { _cameraManipulator->setCoordinateFrameCallback(new ViewerCoordinateFrameCallback(this)); if (getSceneData()) _cameraManipulator->setNode(getSceneData()); if (resetPosition) { osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent(); _cameraManipulator->home(*dummyEvent, *this); } } }可以看到这个函数同时设置了home位置,也就是说如果我们自己的漫游器想有一个初始的位置,那么可以重新实现home这个虚函数来达到这样的目的。
void ViewerBase::frame(double simulationTime) { if (_done) return; // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl; if (_firstFrame) { viewerInit(); if (!isRealized()) { realize(); } _firstFrame = false; } advance(simulationTime); eventTraversal(); updateTraversal(); renderingTraversals(); }很显然漫游器的对事件的处理应该是在eventTraversal函数中,漫游器更新的代码应该在updateTraversal中,事实上确实如此:
for(osgGA::EventQueue::Events::iterator itr = events.begin(); itr != events.end(); ++itr) { osgGA::Event* event = itr->get(); if (event && _cameraManipulator.valid()) { _cameraManipulator->handle( event, 0, _eventVisitor.get()); } }同样在更新完场景中节点的更新回调与遍历之后,在函数的最后处理漫游器的更新:
if (_cameraManipulator.valid()) { setFusionDistance( getCameraManipulator()->getFusionDistanceMode(), getCameraManipulator()->getFusionDistanceValue() ); _cameraManipulator->updateCamera(*_camera); }updateCamera函数的调用如下(默认实现方式 )
virtual void updateCamera(osg::Camera& camera) { camera.setViewMatrix(getInverseMatrix()); }直接调用相机的观察矩阵,矩阵从漫游器的getInverseMatrix函数中获取,这是我们编写自己漫游器的关键函数,这个函数在所有漫游器基类CameraManipulator中是一个纯虚函数,需要我们实现,它被调用的位置就是在此处。
#include <osgGA/CameraManipulator> //定义操作器 class ZoomManipulator : public osgGA::CameraManipulator { public: //构造函数传入节点计算包围盒 ZoomManipulator(osg::Node *node); ~ZoomManipulator(); //所有漫游器都必须实现的4个纯虚函数 virtual void setByMatrix(const osg::Matrixd& matrix){} virtual void setByInverseMatrix(const osg::Matrixd& matrix){} virtual osg::Matrixd getMatrix() const{return osg::Matrix();} virtual osg::Matrixd getInverseMatrix() const; //获取传入节点,用于使用CameraManipulator中的computeHomePosition virtual const osg::Node* getNode() const { return _root; } virtual osg::Node* getNode() { return _root; } virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us); osg::Vec3 _eye; //视点位置 osg::Vec3 _direction; //视点方向 osg::Vec3 _up; //向上方向 osg::Node* _root; };在实现代码中通过计算鼠标点处的世界坐标,使视点沿着与鼠标点世界坐标的连线移动
#include <osgViewer/Viewer> ZoomManipulator::ZoomManipulator(osg::Node *node) { _root = node; computeHomePosition(); _eye = _homeEye; _direction = _homeCenter - _homeEye; _up = _homeUp; } ZoomManipulator::~ZoomManipulator() { } osg::Matrixd ZoomManipulator::getInverseMatrix() const { osg::Matrix mat; mat.makeLookAt(_eye, _eye + _direction, _up); return mat; } bool ZoomManipulator::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us) { switch(ea.getEventType()) { case (osgGA::GUIEventAdapter::SCROLL): { osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&us); osg::Camera *camera = viewer->getCamera(); osg::Matrix MVPW = camera->getViewMatrix() * camera->getProjectionMatrix() * camera->getViewport()->computeWindowMatrix(); osg::Matrix inverseMVPW = osg::Matrix::inverse(MVPW); osg::Vec3 mouseWorld = osg::Vec3(ea.getX(), ea.getY(), 0) * inverseMVPW; osg::Vec3 direction = mouseWorld - _eye; direction.normalize(); if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP) { _eye += direction * 20.0; } else if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_DOWN) { _eye -= direction * 20.0; } } default: return false; } }