|
当前位置:osgViewer/Viewer.cpp第549行,osgViewer::Viewer::eventTraversal()
OSG的事件遍历与更新遍历是两个不同的顺序执行的过程,我们常说的更新回调(UpdateCallback)就是在更新遍历函数updateTraversal中被调用的。而现在我们将要介绍的事件遍历,除了可以执行用户设置的事件回调(EventCallback)之外,更重要的工作是为所有的用户交互和系统事件提供一个响应的机制。
上一日我们已经介绍过,视景器中保存有一个事件处理器组_eventHandlers,它负责处理由图形窗口设备传递到事件队列_eventQueue的各种事件。根据我们已有的知识,新的事件处理器可以通过View::addEventHandler添加,除了RecordCameraPathHandler,StatsHandler等常见的处理器工具之外,通过继承事件处理器的基类osgGA::GUIEventHandler,并重写handle函数,我们还可以实现自定义的交互事件响应流程,这在osgviewer,osgkeyboardmouse等例子中均有详细的演示。
那么,eventTraversal函数的主要任务也就明了了:它必须在每一帧的仿真过程中,取出已经发生的所有事件,摒弃那些对场景不会有助益的(例如,在视口以外发生的鼠标移动事件和胡乱点击),依次交付给各个事件处理器,最后清空现有的事件队列,等待下一帧的到来。
eventTraversal函数的前几行负责记录这个函数的起始时刻,相应的,在最后部分会记录函数的结束时刻,并将函数的总共运行时间送入_stats记录器。这有助于我们了解每一帧当中事件遍历,更新遍历和渲染遍历运行所占用的时间比例。
在记录了起始时刻之后,OSG系统执行了以下几项工作:
- 1、取得事件队列的状态事件(EventQueue::getCurrentEventState);
- 2、取得主摄像机的视口范围(如果它存在的话,正如我们在前面所论述的,主摄像机并不一定存在Viewport视口,也不一定存在GraphicsContext图形设备),并设置为事件队列的“响应范围”(EventQueue::setInputRange);
- 3、计算主摄像机的VPW矩阵。
这其中有一些名词需要加以解释,首先是“状态事件”。OSG的事件队列实际上可以理解为一个由GUIEventAdapter事件组成的链表,诸如鼠标的移动,键盘上的按键被按下,窗口的尺寸被改变等动作,都会作为一个新的GUIEventAdapter对象插入到链表中,插入事件的方法是由图形窗口GraphicsWindow执行EventQueue类的成员函数mouseMotion,keyPress和windowResize,并间接地调用EventQueue::addEvent函数。而这些事件之间可能共通的参数和状态就从“状态事件”中读取。
举例来说,鼠标移动的事件之后,再触发键盘按键的事件(这种操作在《反恐精英》等游戏中司空见惯),则前者将负责更新“状态事件”中的鼠标X,Y坐标参数;而后者就从中取得此坐标,并再次更新“状态事件”中按键相关的信息。
因此,当我们在处理有关按键的GUIEventAdapter事件时,同样也可以使用成员函数GUIEventAdapter::getX和getY取得当前的鼠标位置,而不必担心键盘事件与鼠标的操作无关。
既然事件队列中存在一个公用的“状态事件”,那么存在公用的“响应范围”就不难理解了,EventQueue::setInputRange函数的主要工作是设置鼠标活动的最大和最小范围,如果同时还开启了鼠标范围的限定标志(EventQueue::setUseFixedMouseInputRange),那么鼠标移动的范围将自动限制在这个范围之内(不过此选项默认是关闭的)。
至于VPW矩阵,对世界坐标和窗口坐标变换有所研究的朋友可能有所了解,现介绍如下:
1、V表示摄像机的观察矩阵(View Matrix),它的作用是把对象从世界坐标系变换到摄像机坐标系。因此,对于世界坐标系下的坐标值worldCoord(x0, y0, z0),如果希望使用观察矩阵VM将其变换为摄像机相对坐标系下的坐标值localCoord(x’, y’, z’),则有:- localCoord = worldCoord * VM
复制代码 此外,观察矩阵可以理解为“摄像机在世界坐标系下的变换矩阵的逆矩阵”,因此Camera类也专门提供了getInverseViewMatrix这样一个函数,它的实际意义是表示摄像机在世界坐标系下的位置。
2、P表示投影矩阵(Projection Matrix),当我们使用setProjectionMatrixAsPerspective之类的函数设置摄像机的投影矩阵时,我们相当于创建了一个视截锥体,并尝试把包含在其中的场景对象投影到镜头平面上来。如果投影矩阵为PM,而得到的投影坐标为projCoord(x”, y”, 0)的话,那么:- projCoord = localCoord * PM
复制代码 3、W表示视口矩阵(Window Matrix),它负责把投影坐标变换到指定的二维视口中去,对于视口矩阵WM,通过下面的公式可以得到最终的窗口坐标windowCoord(x, y, 0):- windowCoord = projCoord * WM
复制代码 将所有的公式整合之后,得到:- windowCoord = worldCoord * VM * PM * WM
复制代码 而这个所谓的窗口坐标windowCoord,实际上也就是世界坐标系下的坐标值worldCoord在指定的摄像机视口中(也就是我们的屏幕上)对应的平面位置。怎么样,不知不觉中,我们已经实现了gluProject函数所完成的功能了,而反转这三个步骤就可以得到视口中指定位置所对应的世界坐标了(也就是gluUnProject的工作)。
上面那么一大段论述对于精通数学的您来说可能是废话,不过能够了解OpenGL函数gluProject和gluUnProject的原理(它们其实就是这样实现的,信不信由你),相信我们还是有所斩获的。好了,折腾了这么久,总算明白了VPW矩阵的概念,那么代码中这一段也就很好理解了:- osg::Matrix masterCameraVPW =
- getCamera()->getViewMatrix() * getCamera()->getProjectionMatrix();
- if (getCamera()->getViewport())
- {
- osg::Viewport* viewport = getCamera()->getViewport();
- masterCameraVPW *= viewport->computeWindowMatrix();
- ……
- }
复制代码 继续开拔之前,今日让我们先休息一下。
解读成果:
osgGA::EventQueue,VPW矩阵。 悬疑列表:
类变量_cameraWithFocus的意义是什么?如何调度和实现OSG的多线程机制? |
|