Ogre 摄象机
Mage小组 著
Email: norman_chen@163.com
renwind@163.com
QQ: 18725262
http://www.173d8.com
http://blog.csdn.net/pizi0475
摄象机
OGRE中的摄象机支持透视投影(缺省投影方式、近大远小)和正射投影(大小与距离无关,是CAD设计中的常用投影方式)。摄象机还支持线画模式、纹理模式、灰度阴影模式等几种渲染模式。OGRE场景中可以有多台摄象机,可以将摄象机“看到”的结果渲染到多个窗口,甚至还能实现分屏和画中画功能。OGRE中的摄象机可以独立于场景节点树(摄象机本身也具有位置、旋转属性及控制方法),也可以被attach到场景节点上,通过对场景节点的控制来达到对摄象机的控制。
Camera类
对摄象机的抽象。成员函数说明如下:
标准构造函数
Camera(String name, SceneManager* sm);
标准析构函数
virtual ~Camera();
返回渲染该摄像机的scenemanager的指针
SceneManager* getSceneManager(void) const;
取得摄像机的名字
virtual const String& getName(void) const;
设定投影模式(正射或透视),缺省为透视
void setProjectionType(ProjectionType pt);
取得使用的投影模式的信息
ProjectionType getProjectionType(void) const;
设定该摄像机需要的渲染细节级别
void setDetailLevel(SceneDetailLevel sd);
取得该摄像机的渲染细节级别
SceneDetailLevel getDetailLevel(void) const;
设定摄像机的位置
void setPosition(Real x, Real y, Real z);
void setPosition(const Vector3& vec);
取得摄像机的位置
const Vector3& getPosition(void) const;
移动摄像机
void move(const Vector3& vec);
void moveRelative(const Vector3& vec);
设定摄像机的方向向量
void setDirection(Real x, Real y, Real z);
void setDirection(const Vector3& vec);
取得摄像机的方向
Vector3 getDirection(void) const;
这是一个辅助方法用来自动计算摄像机的方向向量,在当前位置和所看的点,参数targetPoint是一个向量指明所看的点。
void lookAt( const Vector3& targetPoint );
void lookAt(Real x, Real y, Real z);
将摄像机绕z轴逆时针旋转指定角度
void roll(Real degrees);
绕y轴逆时针旋转指定角度
void yaw(Real degrees);
绕x轴上下逆时针旋转
void pitch(Real degrees);
旋转任意角度
void rotate(const Vector3& axis, Real degrees);
使用四元组绕任意轴旋转
void rotate(const Quaternion& q);
指定摄像机是绕本地y轴还是指定的固定轴旋转
void setFixedYawAxis( bool useFixed, const Vector3& fixedAxis = Vector3::UNIT_Y );
设定y方向的视野域,水平方向的视野域将依此计算
void setFOVy(Real fovy);
取得y方向的视野域
Real getFOVy(void) const;
设定到近裁减面的距离
void setNearClipDistance(Real nearDist);
取得到近裁减面的距离
Real getNearClipDistance(void) const;
设定到远裁减面的距离
void setAspectRatio(Real ratio);
取得当前纵横比
Real getAspectRatio(void) const;
内部使用,取得该摄像机的投影矩阵
const Matrix4& getProjectionMatrix(void);
内部使用,取得该摄像机的观察矩阵
const Matrix4& getViewMatrix(void);
取得平截台体的特定面
const Plane& getFrustumPlane( FrustumPlane plane );
测试给定的包容器是否在平截台体中
bool isVisible(const AxisAlignedBox& bound, FrustumPlane* culledBy = 0);
bool isVisible(const Sphere& bound, FrustumPlane* culledBy = 0);
测试给定的顶点是否在平截台体中
bool isVisible(const Vector3& vert, FrustumPlane* culledBy = 0);
返回摄像机的当前方向
const Quaternion& getOrientation(void) const;
设定摄像机的方向
void setOrientation(const Quaternion& q);
输出流功能
friend std::ostream& operator<<(std::ostream& o, Camera& c);
取得摄像机继承的方向,包括从附着节点继承的任何旋转
Quaternion getDerivedOrientation(void);
取得继承的位置,包括从附着节点继承的任何平移
Vector3 getDerivedPosition(void);
取得继承的方向向量
Vector3 getDerivedDirection(void);
覆盖MovableObject的方法
void _notifyCurrentCamera(Camera* cam);
const AxisAlignedBox& getBoundingBox(void) const;
void _updateRenderQueue(RenderQueue* queue);
const String getMovableType(void) const;
使能/使不能自动跟踪scenenode
void setAutoTracking(bool enabled, SceneNode* target = 0,
const Vector3& offset = Vector3::ZERO);
Camera使用举例一
打开OGRE提供的Demo_EnvMapping那个例子程序,运行之。对于这个例子我们应该很熟悉了,通过键盘和鼠标可以控制摄象机在场景中漫游,那么摄象机的创建代码在哪里呢?从EnvMapping.h和EnvMapping.cpp中都找不到创建摄象机的代码!不要忘了我们是基于OGRE的应用框架建立的这个例子,在OGRE应用框架的ExampleApplication.h里为我们创建了摄象机,打开ExampleApplication.h文件可以发现如下函数:
virtual void createCamera(void)
{
// 创建摄象机
mCamera = mSceneMgr->createCamera(“PlayerCam”);
// 将该摄象机放到0,0,500位置上
mCamera->setPosition(Vector3(0,0,500));
// 让摄象机“看”向Z轴负方向(从屏幕外向屏幕里)以模拟你的眼睛
mCamera->lookAt(Vector3(0,0,-300));
// 设置摄象机平截台体的“近面”距离
mCamera->setNearClipDistance(5);
}
每一个通过OGRE应用框架创建的应用程序都会拥有一个通过ExampleApplication类的createCamera函数创建出来的摄象机,该摄象机站在0,0,500位置上看向场景中心。
摄象机的创建代码有了,那通过鼠标和键盘控制摄象机在场景中漫游的代码在哪里呢?在OGRE应用框架中ExampleFrameListener类的frameStarted函数里。该函数又调用processUnbufferedInput函数,我们可以在processUnbufferedInput函数中发现如下代码:
mInputDevice->capture();
……(省略若干行)
mCamera->yaw(rotX);
mCamera->pitch(rotY);
mCamera->moveRelative(vec);
首先获取鼠标状态,而后根据该状态计算摄象机的旋转和移动量,最后通过Camera的几个控制方法控制其运动。
注意到这里的摄象机是独立于场景节点树之外的。我们已经了解到场景节点树上可以挂接Entity、摄象机和光。通过对场景节点的空间位置控制可以达到改变其下挂接的Entity、摄象机和光的位置的目的。但注意Entity和摄象机不一样。在OGRE引擎的设计中Entity是完全没有移动、旋转等能力的,所以它只能把这些任务交给场景节点来完成,而摄象机具有移动和旋转函数,所以它并不一定要完全靠场景节点来完成这些任务。这就引出一个有趣的话题,摄象机放到场景节点中和不放进去的区别究竟有多大。一般来讲,摄象机如果不放在场景节点中,它就非常自由,程序员可以用程序任意控制它,就象在这个例子中一样。想象一下在CS中,你牺牲后,你依然可以控制你的眼睛(灵魂?摄象机?)在场景中穿墙过屋,并为同伴通风报信,就可以体会到这种自由。而如果把摄象机挂接到场景节点中,那么摄象机就和此节点和同在本节点下的其它Entity绑在一起了,一般在这种情况下就不再直接操作摄象机移动位置,而是和Entity一样交给场景节点来做。墙上来回转动的监视器就是由挂接在同一节点下的Entity(监视器模型)和摄象机组成的。还有场景中的人,他们的身体(Entity)和眼睛(Camera)总是在一起,就因为他们同属于一个场景节点。
“人”的组织方法一
“人”的组织方法二
以上两图都将Camera放到了节点下,都可以实现身体和眼睛的同步。但第一种方法更好,因为第二种方法眼睛和身体同属于一个场景节点,它们之间无法实现相对位移,那么眼睛就可能会长在人的肚子里(节点的空间中心)。
将摄象机放到场景节点树中的做法使用也很普遍,下一个例子里我们将看到这样的情况。
Camera使用举例二
思路
实现如下的场景节点树:
在该节点树中有一个食人魔、一个机器人和一架飞机。通过FrameListener来控制这三个Player都在自动旋转。通过按TAB键把Camera轮流挂接到三个Player所在的节点上,这样我们就会发现屏幕上会出现不同Player的以各自的视角所看到的世界。
为了便于对Player的控制,程序中使用一个std::map来保存Player列表,该列表中保存每个Player的所属节点名称和节点指针。
部分代码
// myExample.h
// 定义PlayerList
typedef std::map<std::string,SceneNode*> PlayerList;
// 由应用框架中的ExampleFrameListener派生出myFrameListener
class myFrameListener : public ExampleFrameListener
{
protected:
// 接收myapp传过来的Player列表,以在这里控制其旋转
PlayerList *mPlayerList;
// 保存当前Player的迭代子
PlayerList::iterator currentPlayer;
public:
myFrameListener(RenderWindow* win, Camera* cam, PlayerList *pPlayerList)
: ExampleFrameListener(win, cam)
{
mPlayerList = pPlayerList;
// 缺省Player是列表中的第一人
currentPlayer = mPlayerList->begin();
// 将摄象机挂接到该Player所在的场景节点
currentPlayer->second->attachCamera(mCamera);
}
bool frameStarted(const FrameEvent& evt)
{
// 对TAB键的反应
if (mInputDevice->isKeyDown(KC_TAB))
{
// 把摄象机从当前Player上卸下来
currentPlayer->second->detachObject(mCamera->getName());
// 切换当前Player
currentPlayer++;
if(currentPlayer == mPlayerList->end())
currentPlayer = mPlayerList->begin();
// 再把摄象机挂接到当前Player上来。
currentPlayer->second->attachObject(mCamera);
}
// 让不同Player以不同的速度旋转
mPlayerList->find(“Robot”)->second->yaw(evt.timeSinceLastFrame * 30);
mPlayerList->find(“Head”)->second->yaw(evt.timeSinceLastFrame * -60);
mPlayerList->find(“Razor”)->second->yaw(evt.timeSinceLastFrame * 120);
// 调用基类的frameStarted函数
return ExampleFrameListener::frameStarted(evt);
}
};
// 由应用框架的ExampleApplication派生出myApp
class myApp :public ExampleApplication
{
public:
myApp(){}
protected:
// Player列表
PlayerList mPlayerList;
// 创建场景
void createScene(void)
{
SceneNode *pNodeRobot,*pNodeHead,*pNodeRazor;
// 设置环境光
mSceneMgr->setAmbientLight(ColourValue(1, 1, 1));
// 创建天空盒
mSceneMgr->setSkyBox(true, “Examples/SpaceSkyBox”, 50 );
// 以下代码创建场景树
// Create Robot Entity and attach it to a SceneNode
pNodeRobot = mSceneMgr->getRootSceneNode()->createChild(“Robot”);
Entity *pEntityRobot = mSceneMgr->createEntity(“Robot”, “Robot.mesh”);
pNodeRobot->attachObject(pEntityRobot);
mPlayerList.insert(PlayerList::value_type(pNodeRobot->getName(),pNodeRobot));
// Create OGREHead Entity and attach it to a SceneNode
pNodeHead = mSceneMgr->getRootSceneNode()->createChild(“Head”);
pNodeHead->translate(200,0,0);
Entity *pEntityHead = mSceneMgr->createEntity(“Head”, “ogrehead.mesh”);
pNodeHead->attachObject(pEntityHead);
mPlayerList.insert(PlayerList::value_type(pNodeHead->getName(),pNodeHead));
// Create OGREHead Entity and attach it to a SceneNode
pNodeRazor = mSceneMgr->getRootSceneNode()->createChild(“Razor”);
pNodeRazor->translate(-200,0,0);
// Create head1 entity and attach it to pNodeHead1
Entity *pEntityRazor = mSceneMgr->createEntity(“Razor”, “Razor.mesh”);
pNodeRazor->attachObject(pEntityRazor);
mPlayerList.insert(PlayerList::value_type(pNodeRazor->getName(),pNodeRazor));
}
//创建myFrameListener
void createFrameListener(void)
{
mFrameListener= new myFrameListener(mWindow, mCamera, &mPlayerList);
mRoot->addFrameListener(mFrameListener);
}
// 重新实现基类的createCamera函数,关键是让摄象机与其所在场景节点的相对位置为0,100,0。即高100个长度单位,防止摄象机在Entity的肚子里出现。
virtual void createCamera(void)
{
// Create the camera
mCamera = mSceneMgr->createCamera(“PlayerCam”);
// 设置摄象机位置
//mCamera->setPosition(Vector3(0,0,500));
mCamera->setPosition(Vector3(0,100,0));
// Look back along -Z
mCamera->lookAt(Vector3(0,0,-300));
mCamera->setNearClipDistance(5);
}
};
为了让例子简单一点,这里采用的是前面讲的第二种眼睛与身体的组合方法,摄象机与Entity的相对位置是靠摄象机的setPosition函数完成的,这样做并不是一个很好的方法。建议大家将本例改为前面讲的第一种眼睛与身体的组合方法,将摄象机与Entity的相对位置关系交给场景节点去做,那样摄象机的位置就可以设置为0,0,0。
事情还没有结束,因为摄象机是属于Player的了,我们就不能让键盘再控制摄象机将他移出身体以外,所以需要更改ExampleFrameListener.h中frameStarted函数的代码,因为ExampleFrameListener.h是OGRE应用框架的一部分,所以请注意copy该文件,再更改。
找到frameStarted函数中的如下代码:
mCamera->yaw(rotX);
mCamera->pitch(rotY);
mCamera->moveRelative(vec);
将最后一行注释掉,即可以让摄象机可以受鼠标控制旋转(东张西望?),但不能移动。
转载自:https://blog.csdn.net/onejavaer/article/details/6704729