在osg中,编写以下简单代码:
1 osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer(); 2 viewer->setSceneData(osgDB::readNodeFile("glider.osg")); 3 viewer->run();
- 基本理论
首先要理解OpenGL中的模型视图矩阵的概念,在OpenGL中,通过模型视图矩阵,我们将顶点数据从局部坐标系转换到相机坐标系统,OpenGL中模型视图矩阵是一体的,或者说可以理解为一体。但在OSG中它把这两者分离开来,OSG通过场景的树状结构实现模型矩阵(通过场景中的MatrixTransform),把视图矩阵放在漫游器中,通过两者产生模型视图矩阵。关于视图矩阵,有一个重要的结论:相机在世界坐标系统中的位置姿态矩阵等于相机观察矩阵(视图矩阵)的逆矩阵。相机的位置姿态矩阵可以理解为相机坐标系统下的顶点向世界坐标系统转换的变化矩阵,而观察矩阵(视图矩阵)则理解为世界坐标系的顶点转换到相机坐标系中的变化矩阵。对于这个理论的推导,可以参考:推导相机变换矩阵
另外几个需要了解的问题是:1)OSG中的漫游器是到底是什么?2)漫游器是在什么时候被添加的?3)漫游器是怎么起作用的? 4)我们应该怎么样编写自己的漫游器?下面我们就一一分析
- OSG中漫游器是什么
漫游器在osgGA库中实现,这个库主要是用来处理用户与三维场景的交互(包括鼠标、键盘、手势、操纵杆等),提供了大量的漫游器展示,文章开头代码中默认添加的轨迹球操作器(TrackballManipulator),这些漫游器都继承自osgGA::CameraManpualtor
osgGA::CameraManipulator继承自osgGA::GUIEventHandler,我们知道GUIEventHandler是用来处理事件的类,从这一点就可以知道漫游器实际上就是一个交互操作修改场景中节点位置和姿态的类,只不过它修改的是最顶层的相机节点,这个节点的修改影响着整个场景。osg的视景器(view)中管理着GUIEventHanlder的列表,一般添加的方式是使用:addEventHandler这种方式,在添加漫游器的时候如果使用这种方式并没有任何意义,因为用于表达相机观察方位的getMatrix和getInverseMatrix函数永远不会被视景器View调用到,正确添加漫游器的方法是:
1 viewer.setCameraManipulator(new osgGA::TrackballManipulator);
- 漫游器什么时候被添加
在文章开头的示例代码中,我们并没有调用setCameraManipulator的代码,那操作器是怎么被添加的呢?答案就在viewer->run()这行代码中,通过查看它的代码得知:
1 int Viewer::run() 2 { 3 if (!getCameraManipulator() && getCamera()->getAllowEventFocus()) 4 { 5 setCameraManipulator(new osgGA::TrackballManipulator()); 6 } 7 setReleaseContextAtEndOfFrameHint(false); 8 return ViewerBase::run(); 9 }
当我们在调用run之前没有设置漫游器,osgViewer::Viewer会为我们默认设置一个轨迹球的漫游器。
1 void View::setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition) 2 { 3 _cameraManipulator = manipulator; 4 5 if (_cameraManipulator.valid()) 6 { 7 _cameraManipulator->setCoordinateFrameCallback(new ViewerCoordinateFrameCallback(this)); 8 9 if (getSceneData()) _cameraManipulator->setNode(getSceneData()); 10 11 if (resetPosition) 12 { 13 osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent(); 14 _cameraManipulator->home(*dummyEvent, *this); 15 } 16 } 17 }
可以看到这个函数同时设置了home位置,也就是说如果我们自己的漫游器想有一个初始的位置,那么可以重新实现home这个虚函数来达到这样的目的。
- 漫游器是如何起作用的
漫游器在添加之后,它必须处理输入事件,并且更新场景,要了解这些内容需要进入到OSG中每一帧绘制的代码中,在osg一帧的绘制中会经历 事件遍历、更新遍历、渲染这三个过程,详细代码可以参看每一帧的代码frame函数:
1 void ViewerBase::frame(double simulationTime) 2 { 3 if (_done) return; 4 5 // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl; 6 7 if (_firstFrame) 8 { 9 viewerInit(); 10 11 if (!isRealized()) 12 { 13 realize(); 14 } 15 16 _firstFrame = false; 17 } 18 advance(simulationTime); 19 20 eventTraversal(); 21 updateTraversal(); 22 renderingTraversals(); 23 }
很显然漫游器的对事件的处理应该是在eventTraversal函数中,漫游器更新的代码应该在updateTraversal中,事实上确实如此:
1 for(osgGA::EventQueue::Events::iterator itr = events.begin(); 2 itr != events.end(); 3 ++itr) 4 { 5 osgGA::Event* event = itr->get(); 6 if (event && _cameraManipulator.valid()) 7 { 8 _cameraManipulator->handle( event, 0, _eventVisitor.get()); 9 } 10 }
同样在更新完场景中节点的更新回调与遍历之后,在函数的最后处理漫游器的更新:
1 if (_cameraManipulator.valid()) 2 { 3 setFusionDistance( getCameraManipulator()->getFusionDistanceMode(), 4 getCameraManipulator()->getFusionDistanceValue() ); 5 _cameraManipulator->updateCamera(*_camera); 6 }
updateCamera函数的调用如下(默认实现方式 ):
1 virtual void updateCamera(osg::Camera& camera) { camera.setViewMatrix(getInverseMatrix()); }
直接调用相机的观察矩阵,矩阵从漫游器的getInverseMatrix函数中获取,这是我们编写自己漫游器的关键函数,这个函数在所有漫游器基类CameraManipulator中是一个纯虚函数,需要我们实现,它被调用的位置就是在此处。
- 如何实现自己的漫游器
1 #include <osgGA/CameraManipulator> 2 3 //定义操作器 4 class ZoomManipulator : public osgGA::CameraManipulator 5 { 6 public: 7 //构造函数传入节点计算包围盒 8 ZoomManipulator(osg::Node *node); 9 ~ZoomManipulator(); 10 11 //所有漫游器都必须实现的4个纯虚函数 12 virtual void setByMatrix(const osg::Matrixd& matrix){} 13 virtual void setByInverseMatrix(const osg::Matrixd& matrix){} 14 virtual osg::Matrixd getMatrix() const{return osg::Matrix();} 15 virtual osg::Matrixd getInverseMatrix() const; 16 17 //获取传入节点,用于使用CameraManipulator中的computeHomePosition 18 virtual const osg::Node* getNode() const { return _root; } 19 virtual osg::Node* getNode() { return _root; } 20 21 virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us); 22 23 osg::Vec3 _eye; //视点位置 24 osg::Vec3 _direction; //视点方向 25 osg::Vec3 _up; //向上方向 26 osg::Node* _root; 27 };
在实现代码中通过计算鼠标点处的世界坐标,使视点沿着与鼠标点世界坐标的连线移动
1 #include <osgViewer/Viewer> 2 3 ZoomManipulator::ZoomManipulator(osg::Node *node) 4 { 5 _root = node; 6 computeHomePosition(); 7 8 _eye = _homeEye; 9 _direction = _homeCenter - _homeEye; 10 _up = _homeUp; 11 } 12 13 ZoomManipulator::~ZoomManipulator() 14 { 15 16 } 17 18 osg::Matrixd ZoomManipulator::getInverseMatrix() const 19 { 20 osg::Matrix mat; 21 mat.makeLookAt(_eye, _eye + _direction, _up); 22 23 return mat; 24 } 25 26 bool ZoomManipulator::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us) 27 { 28 switch(ea.getEventType()) 29 { 30 31 case (osgGA::GUIEventAdapter::SCROLL): 32 { 33 osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&us); 34 osg::Camera *camera = viewer->getCamera(); 35 osg::Matrix MVPW = camera->getViewMatrix() * camera->getProjectionMatrix() * camera->getViewport()->computeWindowMatrix(); 36 37 osg::Matrix inverseMVPW = osg::Matrix::inverse(MVPW); 38 osg::Vec3 mouseWorld = osg::Vec3(ea.getX(), ea.getY(), 0) * inverseMVPW; 39 40 osg::Vec3 direction = mouseWorld - _eye; 41 direction.normalize(); 42 43 if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP) 44 { 45 _eye += direction * 20.0; 46 } 47 else if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_DOWN) 48 { 49 _eye -= direction * 20.0; 50 } 51 } 52 53 default: 54 return false; 55 } 56 }