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);
 将最后一行注释掉,即可以让摄象机可以受鼠标控制旋转(东张西望?),但不能移动。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值