您的位置:首页 > 其它

OGRE 3D程序设计(5)

2012-09-04 10:34 253 查看
Ogre提供了相当多的方法,来在场景中任意操作你的摄像机。

void setDirection(Real x, Real y, Real z);

void setDirection(const Vector3& vec);

Vector3 getDirection(void) const;

Vector3 getUp(void) const;

Vector3 getRight(void) const;

void lookAt( const Vector3& angle);

void lookAt(Real x, Real y, Real z);

void roll(const Radian& angle);

void roll (Real degrees){roll (Angle ( degrees ) );}

void yaw(const Radian& angle);

void yaw(Real degrees){yaw (Angle ( degrees ) );}

void pitch(const Radian& angle);

void pitch(Real degrees){yaw (Angle ( degrees ) );}

void rotate(const Vector3& axis, const Radian& angle);

void rotate(const Vector3& axis, Real degrees){

rotate(axis, Angle(degrees));}

void setFixedYawAxis (bool useFixed, const Vector3 &fixedAxis=Vector3::UNIT_Y)

const Quaternion & getOrientation (void) const

void setOrientation(const Quaternion& q);

void setAutoTracking(bool enabled,SceneNode *target=0,const Vector3 &offset=Vector3::ZERO);

可以通过调用roll(),yaw(),以及pitch()来控制摄像机相对于自身的方向进行环绕Z轴(Roll:滚动)、Y轴(Yaw:偏移)或者X轴(Pitch:倾斜)的旋转;setDirection()方法通过一个三维向量来在本地空间(Local space)设置方向; rotate()方法被用来操作摄像机绕着给定的轴向旋转,你既可以通过角度-轴的方法来调用,也可以使用四元数作为参数。lookAt()是一个比较常用的方法,它的作用是直接让摄像机方向对准世界空间内的目标点或者对象,避免你自己对四元数进行几何运算;最后介绍一下setFixedYawAxis()这个方法,它可以锁定某一个轴向的自由度。比如在第一人称的设计游戏中,摄像机一般都只会在X-Z平面上移动,这时候你就需要锁定Y轴的自由度,让它不能绕Y轴旋转。然而,如果在飞行模拟游戏中,可能就用不着做任何锁定了,你需要完全自由的摄像机操作。

setAutoTracking()是一个有趣的方法,你可以让你的摄像机总是盯着你场景中的某一个节点。值得注意的是,这和第三人称射击游戏中所描述的跟踪有本质上的区别,游戏中摄像机需要对准控制角色所观察的方向,而不是我们这里得到的一直盯着这个节点。方法中第一个参数确定是否打开自动跟踪,在任何一帧渲染之前都可以重新设置它。并且需要注意在关掉自动跟踪之前,要确保所被跟踪的节点没有被删除(否则系统会抛出异常)。方法的第二个参数是被跟踪节点得指针,除非你第一个参数是false(这时候可以用NULL),否则你必须确定调用的时候指针指向的节点必须有效。有时候你可能发现你所要跟踪的物体太大了,以至于你都不知道“看”哪里才好,这时候你可以设置第三个参数来定着眼点,它是一个本地空间中相对于场景节点的定位点。

下面的几个方法提供了一些得到摄像机当前的方向的信息,这些信息和被绑定场景节点旋转和变换有关(在其中有“Derived”关键字的方法中,同时被自身的矩阵影响)。

const Quaternion& getDerivedOrientation(void) const;

const Vector3& getDerivedPosition(void) const;

Vector3 getDerivedDirection(void) const;

Vector3 getDerivedUp(void) const;

Vector3 getDerivedRight(void) const;

const Quaternion& getRealOrientation(void) const;

const Vector3& getRealPosition(void) const;

Vector3 getRealDirection(void) const;

Vector3 getRealUp(void) const;

Vector3 getRealRight(void) const;

其中有“Real”关键字的方法返回的是世界空间的坐标,而拥有“Derived”关键字的方法的返回值是在“轴绑定”的本地坐标系中(也就是说这个坐标系原点是摄像机所在的点,而它的轴向和世界坐标系相同)。

高级摄像机属性

Ogre支持立体渲染,通过调整摄像机(视截体)的setFrustumOffset()和setFocalLength()方法来实现。例如,你可以在同一水平线上布置两个摄像机,用来模拟人两眼所看到的实景。你需要把两个摄像机渲染的场景调整一个不同的角度,然后分别输出到使用者的两眼中(至少需要使用一个红蓝眼镜[2]),进而产生“3D”电影,在美国50年代很流行这些玩应儿。不过,在今天看来似乎这个用处不大,并且显得有些“学究”气。但是系统还是提供了这个功能以备不时之需(如果你在实验室里面工作,可能会需要)。

Ogre同时允许你自己来操作视图和投影(View&Projection)矩阵。这确实是一个极其“高级”的话题,所以只有你拥有“知道怎么做和为什么这么做”的前提条件下,才能很好的使用这些特性。这样你才可以很好的通过自己来处理矩阵来控制摄像机的位置和方向。下面的方法提供了可被用于操作的视图和投影(view and projection)矩阵。

const Matrix4& getProjectionMatrixRS(void) const;

const Matrix4& getProjectionMatrixWithRSDepth(void) const;

const Matrix4& getProjectionMatrix(void) const;

const Matrix4& getViewMatrix(void) const;

void setCustomViewMatrix(bool enable,

const Matrix4& viewMatrix = Matrix4::IDENTITY);

bool isCustomViewMatrixEnabled(void) const;

void setCustomProjectionMatrix(bool enable,

const Matrix4& projectionMatrix = Matrix4::IDENTITY);

bool isCustomPeojectionMatrixEnabled(void) const;

最上面两个函数返回渲染系统相关(system-specific)投影矩阵。getProjectionMatrixRS()返回一个符合系统本地渲染系统所提供坐标系的矩阵(可能是左手或者右手坐标系),而getProjectionMatrixWithRSDepth()返回的矩阵是符合Ogre内建的坐标系规格(右手坐标系)。在不同的系统中坐标系深度范围既可能是[0,1]或者是[-1,1]。你可以用getProjectionMatrix()方法代替getProjectionMatrixWithRSDepth()方法来取得统一的深度[-1,1]。

当你决定自己手动定制视图和投影(view and projection)矩阵,你必须关闭Ogre自动对视截体(摄像机)的变换和定位的矩阵处理,取而代之的是当你要进行移动变换的时候要手动更新矩阵。你可以通过调用setCustomViewMatrix()和setCustomProjectionMatrix()的enable参数来开关手动对矩阵的更新。当关掉的时候,系统重新接管对矩阵的自动更新。

在大多时候,系统都能很好的完成对LoD(细节等级level-of-detail)的控制。但如果你希望手动控制这些操作,Ogre提供了Camera中的方法来进行操作:

void setLodBias(Real factor = 1.0);

Real getLodBias(void) const;

其中setLodBias()方法并不总是好用;因为对于实体(elements)来说,LoD只不过是场景或者程序提供的一种有提示性质的信息,实体可能会忽略这种指示。方法中的factor参数控制摄像机增加(或者减少)渲染的细节等级。当参数大于1的时候增加渲染精度,否则当小于的时候就相应减少。这种方法对于比如实现监视器功能时候会很有用,可以忽略整个视口的具体等级信息。

从世界空间转换到屏幕空间(World Space to Screen Space)

这个方法最常用于你希望用鼠标在应用程序中点选物品,这时候就执行了世界空间到屏幕空间的坐标转换。摄像机的中心线(精确点说,就是世界中摄像机中心的“视线”)和屏幕的焦点被定为点[0.5, 0.5](单位坐标系上面的坐标)。当你在屏幕上面移动你鼠标的光标的时候,你会希望有一条线连接你屏幕的坐标到世界中要选取的点。这条线被称为ray(射线),可以通过getCameraToViewportRay()方法来得到:

//x和y都是单位坐标系(范围从0.0到1.0)屏幕上的坐标

Ray getCameraToViewportRay(Real x, Real y) const;

通过这条射线,就可以继续向场景管理器查询有哪些物体和此线相交。在这本书后面的章节里面会具体讨论场景管理器的具体细节。

视口(Viewport)

回顾一下4-2图所展示的画面,X和Y围成了一个矩形。这个矩形就是当前摄像机的视口,或者更精确的说,应该是这个摄像机的“一个”视口。如果你能回忆起前面章节的代码,Viewport对象实例是从RenderWindow的方法中创建,Camera(摄像机)对象只不过是用来构建Viewport的一个参数。这就意味着,一个单独的Camera实例可以绑定任意数量的Viewport实例。

视口的抽象有助于解决一些不好的影响。默认的,Ogre在渲染视口之后再进行深度缓存和颜色缓存的清理,所有的工作都在其他视口的“上面”,这样就能避免深度混合的问题。你可以根据程序的需要(在内存允许范围内)构建很多的摄像机和视口。这么做很重要的用途就是在游戏中实现 “画中画”的放大功能。

提示:对摄像机的画面进行放大的并没有想象中的简单,你要关注一些视口和摄像机的问题。这是因为虽然在进行画面放大,Ogre却仍然通过摄像机原来的位置来进行细节等级(LoD)的计算,结果仍然采用了原来的细节程度,在放大后的窗口中就会感觉物体很粗糙。解决的办法:当你放大的时候,简单把摄像机拉近放大目标;或者使用另外一台摄像机来处理(就是我们所说的画中画放大),当然你也可以通过手动设置Camera::setLodBias()方法来增加渲染的细节。

视口也拥有自己的z-order(z次序)用来决定哪个是视口在哪个视口前面。更高的次序决定在堆栈中“更高”的位置(意味着当z-order值为0的时候,这个视口就在最下面)。一个z-order对应一个视口。就是说,如果你把两个视口的z-order都设为0的话就会抛出一个运行期异常。

视口拥有自己的背景颜色属性。你可以做下面的事情来验证:首先建立一个覆盖整个窗口的视口,把它的背景颜色设置为黑色,然后把它的z-order设置为0,这样就把它放在了最底下;然后构建一个小一点的绿色窗口,覆盖在上面(设置一个大一点的z-order值,比如说1)。下面的代码能帮你实现我们所讲的动作:

//假定window是一个已经存在渲染窗口实例的指针,camera是一个摄像机实例的指针

Viewport *vpTop, *vpBottom;

//第二个参数是z-order值,后面忽略了位置和尺寸的参数,从而使用了默认的值。

vpBottom = window->addViewport(camera, 0);

//在屏幕中心建立一个屏幕25%大小的视口,

vpTop = window->addViewport(camera, 1

0.375f, 0.375f,

0.25, 0.25 );

//把上层视口的背景色设置成为蓝色(因为默认颜色为黑色,所以我们不用设置底层的)。

vpTop->setBackgroundColour(ColourValue(0.0f, 0.0f, 1.0f));

//可以用一种简单的办法来设置蓝色,象下面这样

//vpTop->setBackgroundColour(ColourValue::Blue);

默认情况下,Ogre系统在每一帧更新深度缓存和颜色缓存;你可以通过视口的方法对其进行管理。

//在默认的情况下,两种缓存的更新都被设置成为true,

//这等同于调用setClearEveryFrame(true, FBT_COLOUR|FBT_DEPTH)。

vpTop->setClearEveryFrame(true, FBT_COLOUR);

vpTop->setClearEveryFrame(false);

另外一个很重要的问题,当你使用“画中画”的渲染方式处理你的视口的时候,程序界面仍然默认的显示在所有视口中。但可能这并不是你需要的结果(你不会希望HUD操作台显示在缩影窗口中),同样的,你也可以关闭前端视口的天空(天空盒等)和阴影,下面是实现的方法。

vpTop->setOverlaysEnable(false);

vpTop->setSkiesEnable(false);

vpTop->setShadowsEnabled(true);

还有很多关于视口的高级话题,比如改变渲染队列的顺序,或者选择另外的前端视口所拥有的材质主题。但是我们把这些话题留给后面的章节去具体介绍。

主渲染循环(Main Rendering Loop)

使用Ogre基础的应用程序典型的运行过程是:不间断的循环渲染每帧画面,直到你停止程序。在我们之前的章节中通过调用Root::startRendering()方法来执行这个循环。其实,在这个方法里面也只是简单的循环调用renderOneFrame()(渲染一帧)来进行程序运行工作。

renderOneFrame()方法的存在有很重要的作用。比如当你希望把Ogre插入到一个已经存在的程序框架中,这时候你可能以希望自己来处理循环,而不是依赖Ogre自身的startRendering()/FrameListener结构框架。这时候你可能只需要一个类似3D渲染API的工作。

采用renderOneFrame()还有一个很重要的作用。当你使用startRendering()的时候,因为渲染循环交给了系统,所以没有办法把Ogre结合到窗口系统的消息循环中去。我们考虑处理WM_PAINT消息的时候,当Windows程序处理这个消息时,将会重新绘制窗口用户区域的内容(就算被别的窗口覆盖了,也至少会执行让窗口无效的操作)。如果你希望把Ogre的渲染窗口插入到处理WM_PAINT消息的过程中来,一定会希望在这个消息处理完之后再进行Ogre的窗口渲染(通过renderOneFrame()),而是用startRendering()就没有办法掌握这个过程。

然而,选用renderOneFrame()进行手动处理渲染循环的最主要原因是(对于某一些3D游戏引擎或者程序来说)使用startRendering()和帧监听结构来处理主循环并不是一个明智的选择。举例来说,当构建一个局域网络游戏的引擎时,可能服务器端和客户端采用相同的游戏循环,但是并不需要进行渲染的支持。这时候仍然把程序的主循环交给Ogre来驱动的话,就会变得很别扭了。

幸运的是,Ogre并没有强制在你的程序必须使用某一种方法:你可以自由选择最适合你情况的方法。如果你最后决定手动来进行渲染循环,就可以使用renderOneFrame()方法。

代码4-8展示了一个简短的程序段落;它只是用来告诉你如何在你的程序中创建一个手动循环,在你之后的代码中也可以直接使用它作为基础。

代码4-8:手动渲染循环的框架例程

bool keepRendering = true;

//在这里填入所有这个章节之前提到的设置过程:

//载入插件,创建渲染窗口和场景管理器以及摄像机和视口,

//然后再场景中填入你希望的内容。

while(keepRendering)

{

//在这里处理引擎使用的网络消息。

//在这里处理引擎使用的输入消息。

//根据消息更新场景管理状态。

//根据新的场景管理状态,渲染下一帧。

root->renderOneFrame();

//检查是否需要退出渲染循环

//注意:NextMessageInQueue()这个函数只是为了便于解释程序含义

//而虚构出来的函数——Ogre引擎中并没有实现它。

If(NextMessageInQueue() == QUIT)

{

keepRendering = false;

}

}

//在这里进行你需要的清理工作

//然后,关闭Ogre

delete root;

在执行renderOneFrame()的时候,你仍然可以良好的使用FrameListener类来处理程序。renderOneFrame()方法可以正确的处理注册在Root类实例中的帧监听,代码4-9清楚的描述了过程。

代码4-9:Root中renderOneFrame()的全部代码(在Ogre源码的root.cpp中)

bool Root::renderOneFrame(void)

{

if(!_fireFrameStarted())

return false;

_updateAllRenderTargets();

return _fireFrameEnded();

}

代码4-9中包含了整个renderOneFrame()的代码。就像你看到的,分别发送了“帧开始(frame-started)”和“帧结束(frame-ended)”事件到你的帧监听中执行,所以当你手动处理渲染循环的时候,renderOneFrame()和FrameListener也能很好的协作。

结语

在这个章节中,你学到了如何初始化并启动一个Ogre程序,以及如何控制多样的支持显示的对象(RenderWindow,Camera,Viewport)协同工作来展示场景。在下一个章节中,我们将要开始具体的控制场景运作,那时候你就会了解现在所学的这些知识有多么有趣了。

注:转载源地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: