三、摄像机控制

(最近更新:2006314)

原文地址:http://www.flmnware.com/


目的:通过本教程,学会用键盘和鼠标实现摄像机的控制。





1、事件处理模型

OpenSceneGraph里,类GUIEventHandler为任何想要处理GUI事件的类提供了一个基本接口。处理事件的方法是:


virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);

virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);

virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);



在这个方法里,有两个参数,第一个是GUI事件的供给者,第二个参数用来handle方法对GUI进行反馈,它可以让GUIEventHandler根据输入事件让GUI进行一些动作。

如果要进行事件处理,可以从GUIEventHandler继承出自己的类,然后覆盖handle方法,在里面进行事件处理。osgProducer::Viewer类维护一个GUIEventHandler队列,事件在这个队列里依次传递,handle的返回值决定这个事件是否继续让后面的GUIEventHandler处理,如果返回true,则停止处理,如果返回false,后面的GUIEventHandler还有机会继续对这个事件进行响应。

如果想向Viewer类加入自己的GUIEventHandler,可以这样,假设我的GUIEventHandlerCMyGUIEventHandler


viewer.getEventHandlerList().push_back(new CmyGUIEventHandler());

viewer.getEventHandlerList().push_back(new CmyGUIEventHandler());

viewer.getEventHandlerList().push_back(new CmyGUIEventHandler());



如果想向节点加入自己的GUIEventHandler,就设置节点的事件回调。


node.setEventCallback(new CmyGUIEventHandler());

node.setEventCallback(new CmyGUIEventHandler());

node.setEventCallback(new CmyGUIEventHandler());



GUIEventHandler可处理的事件类型可以用ea.getEventType()得到,包括以下几种:


事件类型

说明

PUSH

鼠标按键按下

RELEASE

鼠标按键抬起

DOUBLECLICK

鼠标按键双击

DRAG

鼠标按下按键拖动

MOVE

鼠标移动

KEYDOWN

键盘按键按下

KEYUP

键盘按键抬起

FRAME

每帧触发的事件

RESIZE

窗口改变大小

SCROLLUP

鼠标滚轮上滚

SCROLLDOWN

鼠标滚轮下滚

SCROLLLEFT

鼠标滚轮左滚

SCROLLRIGHT

鼠标滚轮右滚


还可以通过ea.getKey()得到键盘按键码,通过ea.getButtonMask()得到鼠标按键码。因为跨平台的关系,OpenSceneGraph自己定义了键盘码,具体可参考GUIEventAdapter头文件。



2、视图矩阵控制模型

OpenSceneGraph的摄像机操作,由osgGA::MatrixManipulator的派生类来完成,可以调用osgProducer::Viewer类的addCameraManipulator方法把自己的摄像机加入进去。默认情况下,Viewer自带了5个摄像机,这些在第一篇里讲过,可用数字键在这些摄像机之间切换,我们自定义的,也依次排在后面。

OpenSceneGraph里,所有的视图矩阵操作都是通过矩阵来完成的,不同摄像机之间的交互也通过矩阵,这样就提供了一个通用的模型,不管你习惯使用gluLookAt方式的,还是习惯操作摄像机位置姿态方式的,都可以很容易嵌入OpenSceneGraph的框架中,因为所有方式的最后结果就是矩阵。

要实现自己的摄像机,关键是实现osgGA::MatrixManipulator类的以下五个纯虚函数:


virtual void setByMatrix(const osg::Matrixd& matrix);

virtual void setByMatrix(const osg::Matrixd& matrix);

virtual void setByMatrix(const osg::Matrixd& matrix);



这个函数在从一个摄像机切换到另一个摄像机时调用,用来把上一个摄像机的视图矩阵传过来,这样就可依此设定自己的初始位置了。

virtual void setByInverseMatrix(const osg::Matrixd& matrix);

virtual void setByInverseMatrix(const osg::Matrixd& matrix);

virtual void setByInverseMatrix(const osg::Matrixd& matrix);



这个方法当在外部直接调用ViewersetViewByMatrix方法时,把设置的矩阵传过来,让摄像机记住新更改的位置。

virtual osg::Matrixd getMatrix() const;

virtual osg::Matrixd getMatrix() const;

virtual osg::Matrixd getMatrix() const;



SetByMatrix方法需要的矩阵就是用这个方法得到的,用来向下一个摄像机传递矩阵。

virtual osg::Matrixd getInverseMatrix() const;

virtual osg::Matrixd getInverseMatrix() const;

virtual osg::Matrixd getInverseMatrix() const;



这个是最重要的方法,这个方法每帧会被调用,它返回当前的视图矩阵。

virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);

virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);

virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);



在这个方法里进行时间的处理,改变自己的状态,进而在 getInverseMatrix 被调用时,改变场景内摄像机的位置姿态。


3、类第一人称射击游戏的摄像机

这个摄像机最早在OpenGVS里实现,在OpenGVS里,摄像机的操作是把它想象成场景中一个有位置姿态的物体。这个摄像机的功能包括adws控制左右前后行走,用HomeEnd控制上下移动,鼠标控制转向,并用空格控制是否使用鼠标的转向功能,用=-控制移动步长。

摄像机的位置用位置和姿态来表示,键盘鼠标控制位置姿态的改变,值得注意的是将位置姿态转化为视图矩阵,看方法 getInverseMatrix()


osg::Matrixd

FPSCamera::getInverseMatrix() const

{

osg::Matrixd mat;

mat.makeRotate(_vRotation._v[0], osg::Vec3(1.0f, 0.0f, 0.0f),

_vRotation._v[1], osg::Vec3(0.0f, 1.0f, 0.0f),

_vRotation._v[2], osg::Vec3(0.0f, 0.0f, 1.0f));

return osg::Matrixd::inverse(mat * osg::Matrixd::translate(_vPosition));

}

osg::Matrixd

FPSCamera::getInverseMatrix() const

{

osg::Matrixd mat;

mat.makeRotate(_vRotation._v[0], osg::Vec3(1.0f, 0.0f, 0.0f),

_vRotation._v[1], osg::Vec3(0.0f, 1.0f, 0.0f),

_vRotation._v[2], osg::Vec3(0.0f, 0.0f, 1.0f));

return osg::Matrixd::inverse(mat * osg::Matrixd::translate(_vPosition));

}

osg::Matrixd

FPSCamera::getInverseMatrix() const

{

osg::Matrixd mat;

mat.makeRotate(_vRotation._v[0], osg::Vec3(1.0f, 0.0f, 0.0f),

_vRotation._v[1], osg::Vec3(0.0f, 1.0f, 0.0f),

_vRotation._v[2], osg::Vec3(0.0f, 0.0f, 1.0f));

return osg::Matrixd::inverse(mat * osg::Matrixd::translate(_vPosition));

}



其中_vPosition是位置,_vRotation是姿态,代码中,先得到旋转矩阵,可以用矩阵类的 makeRotate 方法,参数是旋转角度和旋转轴,然后用矩阵类的translate方法得到平移矩阵,用旋转矩阵左乘平移矩阵,就得到摄像机的旋转变换矩阵,但是视图矩阵是变换矩阵的反矩阵,所以最后要用矩阵类的 inverse方法的到反矩阵。



总结:OpenSceneGraph提供的事件处理非常直接好用,而视图变换这种基于矩阵的设计,一般化了视图矩阵的操作,可以用各种方法实现摄像机的控制,很容易把以前的程序移植过来。



作者:flmn

网站:http://www.flmnware.com

作者:flmn

网站:http://www.flmnware.com

作者:flmn

网站:http://www.flmnware.com



2条评论

  1. 记得以前搞OSG时碰到问题还在QQ上向小鸡请教过。唉,一晃两年过去了

  2. dakoulian现在在搞哪方面呢?

发表评论

评论也有版权!