Ogre三维框架基础篇(三)

交互

在讲输入之前,先理清几个概念。

无缓冲输入unbuffered

不断的响应按键信息,直到按键放开。例如,按下上下左右,使得角色持续移动。一般来讲适合于3D场景漫游过程,当在每帧渲染之前,系统捕获输入设备状态,并根据这些状态对场景中的物体和摄象机进行控制。

缓冲输入buffered

在一个按键放开之前,只处理一次输入信息。例如呼出主菜单。适合于GUI界面的情况(如设置菜单),输入设备状态可以被发送到各GUI元素进行处理(如按钮被按下)。

无缓冲输入

动画都是一帧一帧刷新的,故三维动画也不例外。在Ogre框架中,在帧刷新的地方加入了响应方法以便开发者调用。

1
2
3
4
5
6
7
8
9
10
bool Root::renderOneFrame(void)
{
if(!_fireFrameStarted())
return false;

if (!_updateAllRenderTargets())
return false;

return _fireFrameEnded();
}

以上分为三步,帧动画开始前,帧动画开始时,帧动画结束后。这些方法会在每次帧刷新时调用。对用的方法也分别为

1
2
3
fireFrameStarted();
updateAllRenderTargets();
fireFrameEnded();

而在updateAllRenderTargets()方法中

1
2
3
4
5
6
7
8
9
10
11
bool Root::_updateAllRenderTargets(void)
{
mActiveRenderer->_updateAllRenderTargets(false);

bool ret = _fireFrameRenderingQueued();

// thread is blocked for final swap
mActiveRenderer->_swapAllRenderTargetBuffers(
mActiveRenderer->getWaitForVerticalBlank());

...

那么调用关系就清楚了,简单归纳为下列五步:
1.触发所有FrameListener的frameStarted
2.更新所有渲染目标(不翻转)
3.触发所有FrameListener的frameRenderingQueued
4.翻转所有渲染目标
5.触发所有FrameListener的frameEnded。

准备

TutorialApplication.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "BaseApplication.h"

class TutorialApplication : public BaseApplication
{
public:
TutorialApplication();
virtual ~TutorialApplication();

protected:
virtual void createScene();
virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

private:
bool processUnbufferedInput(const Ogre::FrameEvent& fe);

};

TutorialApplication.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "TutorialApplication.h"

TutorialApplication::TutorialApplication()
{
}

TutorialApplication::~TutorialApplication()
{
}

void TutorialApplication::createScene()
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(.25, .25, .25));

Ogre::Light* pointLight = mSceneMgr->createLight("PointLight");
pointLight->setType(Ogre::Light::LT_POINT);
pointLight->setPosition(250, 150, 250);
pointLight->setDiffuseColour(Ogre::ColourValue::White);
pointLight->setSpecularColour(Ogre::ColourValue::White);

Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("ninja.mesh");
Ogre::SceneNode* ninjaNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
ninjaNode->attachObject(ninjaEntity);

}

bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);

return ret;
}

bool TutorialApplication::processUnbufferedInput(const Ogre::FrameEvent& fe)
{
return true;
}

在类中定义变量

1
2
3
4
static bool mouseDownLastFrame = false;
static Ogre::Real toggleTimer = 0.0;
static Ogre::Real rotate = .13;
static Ogre::Real move = 250;

监听鼠标左键

鼠标左键打开和关闭灯源,写入processUnbufferedInput

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool TutorialApplication::processUnbufferedInput(const Ogre::FrameEvent& fe){

//监控鼠标左键,按下则为true
bool leftMouseDown = mMouse->getMouseState().buttonDown(OIS::MB_Left);

//保证鼠标为非长按状态
if (leftMouseDown && !mouseDownLastFrame)
{
Ogre::Light* light = mSceneMgr->getLight("PointLight");
light->setVisible(!light->isVisible());
}

mouseDownLastFrame = leftMouseDown;
return true;
}

监听鼠标右键

鼠标右键有个计时的功能,即只能在一定时差下才能改变灯源的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);

if(!processUnbufferedInput(fe)) return false;

toggleTimer -= fe.timeSinceLastFrame;
//计时器。在0.5秒内右键不能控制灯光的开关
if ((toggleTimer < 0) && mMouse->getMouseState().buttonDown(OIS::MB_Right))
{
toggleTimer = 0.5;

Ogre::Light* light = mSceneMgr->getLight("PointLight");
light->setVisible(!light->isVisible());
}

return ret;
}

键盘监听

继续在frameRenderingQueued方法下添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Ogre::Vector3 dirVec = Ogre::Vector3::ZERO;
//键盘I的监听
if (mKeyboard->isKeyDown(OIS::KC_I))
dirVec.z -= move;
//键盘U的监听
if (mKeyboard->isKeyDown(OIS::KC_U))
dirVec.y += move;
//键盘O的监听
if (mKeyboard->isKeyDown(OIS::KC_O))
dirVec.y -= move;
//键盘J的监听
if (mKeyboard->isKeyDown(OIS::KC_J))
{
//如果同时按下了左shift键,则顺时针旋转模型
if(mKeyboard->isKeyDown(OIS::KC_LSHIFT))
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(5 * rotate));
else
dirVec.x -= move;
}
//键盘L的监听
if (mKeyboard->isKeyDown(OIS::KC_L))
{
//如果同时按下了左shift键,则逆时针旋转模型
if(mKeyboard->isKeyDown(OIS::KC_LSHIFT))
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(-5 * rotate));
else
dirVec.x += move;
}
//移动模型
mSceneMgr->getSceneNode("NinjaNode")->translate(
dirVec * fe.timeSinceLastFrame,
Ogre::Node::TS_LOCAL);

缓冲输入

准备tudohouse.mesh和fw12b.jpg的材质图片。在material Examples的脚本中写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
material Examples/TudorHouse
{
technique
{
pass
{
texture_unit
{
texture fw12b.jpg
tex_address_mode clamp
}
}
}
}

(Ogre1.9中已经包括这些东西)
TutorialApplication.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef TUTORIALAPPLICATION_H
#define TUTORIALAPPLICATION_H

#include "BaseApplication.h"

class TutorialApplication : public BaseApplication
{
public:
TutorialApplication();
virtual ~TutorialApplication();

private:
virtual void createScene();
virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

Ogre::Real mRotate;
Ogre::Real mMove;
Ogre::SceneNode* mCamNode;
Ogre::Vector3 mDirection;

};

#endif

TutorialApplication.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
TutorialApplication::TutorialApplication()
: mRotate(.13),
mMove(250),
mCamNode(0),
mDirection(Ogre::Vector3::ZERO)
{
}

TutorialApplication::~TutorialApplication()
{
}

void TutorialApplication::createScene()
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(.2, .2, .2));

Ogre::Entity* tudorEntity = mSceneMgr->createEntity("tudorhouse.mesh");
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(
"Node");
node->attachObject(tudorEntity);

Ogre::Light* light = mSceneMgr->createLight("Light1");
light->setType(Ogre::Light::LT_POINT);
light->setPosition(Ogre::Vector3(250, 150, 250));
light->setDiffuseColour(Ogre::ColourValue::White);
light->setSpecularColour(Ogre::ColourValue::White);

mCamera->setPosition(0, -370, 1000);

}

bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);

return ret;
}

按键响应和接口

键盘有两个方法,按下和松开

1
2
3
//TutorialApplication.h
virtual bool keyPressed(const OIS::KeyEvent& ke);
virtual bool keyReleased(const OIS::KeyEvent& ke);

1
2
3
4
5
6
7
8
9
10
//TutorialApplication.cpp
bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke)
{
return true;
}

bool TutorialApplication::keyReleased(const OIS::KeyEvent& ke)
{
return true;
}

鼠标有三个方法,鼠标移动,鼠标按下,鼠标松开

1
2
3
4
//TutorialApplication.h
virtual bool mouseMoved(const OIS::MouseEvent& me);
virtual bool mousePressed(const OIS::MouseEvent& me, OIS::MouseButtonID id);
virtual bool mouseReleased(const OIS::MouseEvent& me, OIS::MouseButtonID id);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//TutorialApplication.cpp
bool TutorialApplication::mouseMoved(const OIS::MouseEvent& me)
{
return true;
}

bool TutorialApplication::mousePressed(
const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
return true;
}

bool TutorialApplication::mouseReleased(
const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
return true;
}
```
### 设置两个摄像头结点
在`createScene`方法中的创建灯源后面加上代码
```c++
//创造结点用于设置摄像头
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Ogre::Vector3(1200, -370, 0));
node->yaw(Ogre::Degree(90));
//将结点与摄像头绑定
mCamNode = node;
node->attachObject(mCamera);
//第二个摄像头结点
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Ogre::Vector3(-500, -370, 1000));
node->yaw(Ogre::Degree(-30));

键盘监听并处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke) 
{
switch (ke.key)
{
case OIS::KC_ESCAPE:
mShutDown = true;
break;
//设置摄像头1
case OIS::KC_1:
//解除当前摄像头的绑定
mCamera->getParentSceneNode()->detachObject(mCamera);
mCamNode = mSceneMgr->getSceneNode("CamNode1");
mCamNode->attachObject(mCamera);
break;
//设置摄像头2
case OIS::KC_2:
mCamera->getParentSceneNode()->detachObject(mCamera);
mCamNode = mSceneMgr->getSceneNode("CamNode2");
mCamNode->attachObject(mCamera);
break;
//移动位置
case OIS::KC_UP:
case OIS::KC_W:
mDirection.z = -mMove;
break;

case OIS::KC_DOWN:
case OIS::KC_S:
mDirection.z = mMove;
break;

case OIS::KC_LEFT:
case OIS::KC_A:
mDirection.x = -mMove;
break;

case OIS::KC_RIGHT:
case OIS::KC_D:
mDirection.x = mMove;
break;

case OIS::KC_PGDOWN:
case OIS::KC_E:
mDirection.y = -mMove;
break;

case OIS::KC_PGUP:
case OIS::KC_Q:
mDirection.y = mMove;
break;
default:
break;
}
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bool TutorialApplication::keyReleased(const OIS::KeyEvent& ke) 
{
switch (ke.key)
{
case OIS::KC_UP:
case OIS::KC_W:
mDirection.z = 0;
break;

case OIS::KC_DOWN:
case OIS::KC_S:
mDirection.z = 0;
break;

case OIS::KC_LEFT:
case OIS::KC_A:
mDirection.x = 0;
break;

case OIS::KC_RIGHT:
case OIS::KC_D:
mDirection.x = 0;
break;

case OIS::KC_PGDOWN:
case OIS::KC_E:
mDirection.y = 0;
break;

case OIS::KC_PGUP:
case OIS::KC_Q:
mDirection.y = 0;
break;

default:
break;
}
return true;
}

最后在帧刷新处更新摄像头的位置。frameRenderingQueued

1
mCamNode->translate(mDirection * fe.timeSinceLastFrame, Ogre::Node::TS_LOCAL);

鼠标监听并处理

左键控制灯源的开关mousePressed()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool TutorialApplication::mousePressed(
const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
Ogre::Light* light = mSceneMgr->getLight("Light1");
switch (id)
{
//左键控制灯光开关
case OIS::MB_Left:
light->setVisible(!light->isVisible());
break;
default:
break;
}
return true;
}

右键移动旋转

1
2
3
4
5
6
7
8
9
10
11
bool TutorialApplication::mouseMoved(const OIS::MouseEvent& me) 
{
if (me.state.buttonDown(OIS::MB_Right))
{
//绕y轴旋转
mCamNode->yaw(Ogre::Degree(-mRotate * me.state.X.rel), Ogre::Node::TS_WORLD);
//绕x轴旋转
mCamNode->pitch(Ogre::Degree(-mRotate * me.state.Y.rel), Ogre::Node::TS_LOCAL);
}
return true;
}

运行结果

房屋

结语


这是Ogre框架交互的整合,包括键盘,鼠标以及本文未提及的joystick控制器。主要为两部分:一部分为帧刷新,另一部分则为输入监听。Ogre主要通过这两种方法与用户进行交互。
参考文章:Ogre基础教程5

文章作者: cpacm
文章链接: http://www.cpacm.net/2015/02/13/Ogre三维框架基础篇(三)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 cpacm
打赏
  • 微信
  • 支付宝

评论