文章来源:http://www.cnblogs.com/oneDouble/articles/2840906.html
我们在学习这本书的过程中了解了很多基础的知识。这一章将会学习一个尚未了解的主题:如何不依赖ExampleApplication创建我们自己的程序。当我们学习完这个主题,这章会重复一些之前章节学过的专题,使用一个新的程序的类来做各种示例。
这一章,我们将会:
- 学习如何启动我们的Ogre 3D。
- 解析resources.cfg 文件来加载我们需要的模型。
- 结合我们之前章节所学来创建一个小的示例程序来展示我们之前所学。
那么我们开始吧….
【 启动Ogre 3D】
目前为止,ExampleApplication类已经为我们启动和初始化了Ogre 3D;现在我们将会自己来实现这个过程。
这次我们将会在一个空白的文件上开始。
1. 用一个空白的代码文件作为开始,引入 Ogre3d.h头文件,并创建一个空的主函数:
#include "OgreOgre.h"
int main (void)
{
return 0;
}
2. 创建一个Ogre 3D Root类的实例;这个类需要plugin.cfg作为参数:
Ogre::Root* root = new Ogre::Root("plugins_d.cfg");
3. 如果配置对话框不能使用或者用户选择退出,关闭应用程序:
if(!root->showConfigDialog())
{
return -1;
}
4. 创建一个渲染窗口:
Ogre::RenderWindow* window = root->initialise(true,"Ogre3D Beginners Guide");
5. 下一步创建一个新的场景管理器:
Ogre::SceneManager* sceneManager = root->createSceneManager(Ogre::ST_GENERIC);
6. 创建一个摄像机并给其命名:
Ogre::Camera* camera = sceneManager->createCamera("Camera");
camera->setPosition(Ogre::Vector3(0,0,50));
camera->lookAt(Ogre::Vector3(0,0,0));
camera->setNearClipDistance(5);
7. 使用这个摄像机,创建一个视口并设置背景颜色为黑色:
Ogre::Viewport* viewport = window->addViewport(camera);
viewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.0));
8. 现在,使用视口并设置摄像机的纵横比:
camera->setAspectRatio(Ogre::Real(viewport->getActualWidth())/
Ogre::Real(viewport->getActualHeight()));
9. 最后,告诉root开始渲染:
root->startRendering();
10. 编译运行程序;你将会看到正常的配置对话框然后是黑色的窗口。这个窗口不能按Escape来关闭,因为我们目前没有添加按键控制。你可以在启动程序的控制台中按CTRL+C来关闭程序。
【 刚刚发生了什么?】
我们第一次没有在ExampleApplication帮助下创建了Ogre 3D程序。因为我们不再使用 ExampleApplication了,需要引入Ogre3D.h ,它之前被事先在引入在ExampleApplication.h中。在我们使用Ogre 3D之前,我们需要一个root实例。这个root是一个可以管理Ogre 3D高层的类,有着创建和保存其他对象的工厂函数,加载和卸载需要的插件和做其他的一些东西。我们给root实例一个参数:定义加载插件的文件。
除了插件配置文件的名称,函数还需要Ogre配置文件的名称和日志文件。我们需要改变第一个文件的名称,因为我们使用的是debug版的程序,因此我们想要加载debug插件。默认的文件名为plugins.cfg,是Ogre 3D SDK release文件夹下的文件名,但是我们程序是在debug文件夹运行,所以文件名称为plugins_d.cfg。
ogre.cfg包含了我们在Ogre程序开始时在配置对话框中选择的选项。这是方便了用户每次可以以相同的配置启动程序。通过这个文件,Ogre 3D可以记住他的选项并使用它们作为下次启动的选项。如果文件不存在,文件将会自动创建,所以我们不需要添加_d到这个文件名后面,可以使用默认的参数;这种方式对于日志文件也是相同的。
使用root实例,第三步我们让Ogre 3D给用户显示了配置对话框。当用户退出对话框或发生了什么错误,我们将会返回-1并让程序关闭。否则,我们会执行第四步的创建一个新的渲染窗口和一个新的场景管理器。使用场景管理器,我们创建了一个摄像机,使用摄像机我们创建了视口;然后使用视口,我们计算了摄像机的纵横比。摄像机和视口的创建不是什么新的知识了;我们在第三章(摄像机,灯光和阴影)已经学习过它们。在创建所有的需求之后,我们告诉根实例来开始渲染,这样我们的结果就可视了。下面的图示显示了创建别的对象需要什么对象:
【 添加资源 】
我们现在创建了我们第一个不需要ExampleApplication 类的Ogre 3D程序。但是一个重要的东西遗漏了:我们目前还未加载和渲染一个模型。
我们已经有了基本的程序,现在让我们添加一个模型。
1. 在设置完纵横比和开始渲染之前,添加包含Sinbad模型的zip压缩文件到我们的资源:
Ogre::ResourceGroupManager::getSingleton().addResourceLocation("http://www.cnblogs.com/Media/packs/Sinbad.zip","Zip");
2. 我们现在不想要索引更多的资源,所以现在仅索引添加的资源:
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
3. 现在创建一个Sinbad mesh的实例并添加只场景:
Ogre::Entity* ent = sceneManager->createEntity("Sinbad.mesh");
sceneManager->getRootSceneNode()->attachObject(ent);
4. 编译运行程序;你应看到Sinbad在屏幕正中央:
【 刚刚发生了什么?】
我们使用了ResourceGroupManager来检索包含Sinbad mesh和纹理文件的zip压缩包,做完这个,我们第三步告诉程序用createEntity()来加载数据。
【 使用 resources.cfg 】
为每一个我们想要加载的zip或者文件夹新添加一行代码是一个非常乏味的工作,所以为避免这样。ExampleApplication类使用了一个称为resources.cfg配置文件,这个配置文件列出了每个文件夹和压缩包,并且使用这个文件我们加载了所有内容。让我们现在重复实现一下这个功能。
使用我们之前的程序代码,这里我们将会解析resources.cfg。
1. 替换加载zip包的代码为一个指向resources_d.cfg的配置文件的实例:
Ogre::ConfigFile cf;
cf.load(“resources_d.cfg”);
2. 首先获取一可以遍历配置文件的每个区块的迭代器:
Ogre::ConfigFile::SectionIterator sectionIter = cf.getSectionIterator();
3. 定义三个字符串以保存从配置文件中提取的存数据并且遍历每个区块:
Ogre::String sectionName, typeName, dataname;
while (sectionIter.hasMoreElements())
{
4. 获取区块的名称:
sectionName = sectionIter.peekNextKey();
5. 获取区块中包含的设置,同时,提前创建一个区块的迭代器:
Ogre::ConfigFile::SettingsMultiMap *settings = sectionIter.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
6. 在区块中遍历每个设置:
for (i = settings->begin(); i != settings->end(); ++i)
{
7. 使用迭代器来获取资源的名称和类型:
typeName = i->first;
dataname = i->second;
8. 使用资源名称,类型和区块的名称并添加至资源索引:
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(dataname,typeName, sectionName);
9. 编译运行程序,你将会看到和之前一样的场景:
【 刚刚发生了什么?】
在第一步,我们使用了Ogre 3D的另一个助手类。这个类可以简单的的加载和解析简单的配置文件,这个文件由name-value对组成。我们把带debug模式字尾的文件名写入程序;这并不是好的习惯并且在实际的程序中我们将会使用#ifdef来依据debug和release模式以改变资源文件的文件名。ExampleApplication类就是这样做的;让我们看下ExampleApplication.h第384行:
#if OGRE_DEBUG_MODE
cf.load(mResourcePath + "resources_d.cfg");
#else
cf.load(mResourcePath + "resources.cfg");
#endif
【 配置文件的结构】
配置文件由助手类加载,其有着一个简单的结构;这里是resource.cfg的例子。当然你的resources.cfg里面包含着不同的路径:
[General]
FileSystem=D:/programming/ogre/ogre_trunk_1_7/Samples/Media
以[General]作为一区段的开始,以另一个[sectionname]的出现作为结束。每个配置文件可以包含很多的区段;在第二步我们创建了一个迭代器来遍历文件中所有的区段,并在第三步我们使用了一个while循环,以遍历完每个区段作为结束。
一个区段包含不同的设置并且每个设置等同于一个键和一个值。我们赋值给键FileSystem值为D:/programming/ogre/ogre_trunk_1_7/Samples/ Media。在第四步,我们创建了一个新的迭代器,这样我们就可以遍历每个设置了。设置是一name-value数对。我们遍历这个map ,对于每个实例我们使用map的键作为资源的类型,map的数据作为路径。使用区段名作为资源组,我们在第八步使用资源组管理器来添加资源。只要我们解析完这个文件,我们就索引完毕所有的文件。
【 创建应用程序类 】
我们现在已经有自己Ogre 3D程序的主要部分了,但是所有代码都在main函数中,这并不是我们想要的那种可重用的代码。
使用之前用过的代码,我们现在将会创建一个类来实现Ogre代码从main函数中的分离。
1. 创建有两个私有的指针的MyApplication类,一个指向Ogre 3D SceneManager,另一个指向Root类:
class MyApplication
{
private:
Ogre::SceneManager* _sceneManager;
Ogre::Root* _root;
2. 这个类的剩余部分应为public:
public:
3. 创建一个loadResources()函数,这个函数加载resources.cfg 配置文件:
void loadResources()
{
Ogre::ConfigFile cf;
cf.load("resources_d.cfg");
4. 遍历配置文件的所有区段:
Ogre::ConfigFile::SectionIterator sectionIter =
cf.getSectionIterator();
Ogre::String sectionName, typeName, dataname;
while (sectionIter.hasMoreElements())
{
5. 获取区段名称和设置的迭代器:
sectionName = sectionIter.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = sectionIter.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
6. 迭代所有的设置并添加每个资源:
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
dataname = i->second;
Ogre::ResourceGroupManager::getSingleton().
addResourceLocation(
dataname, typeName, sectionName);
}
}
Ogre::ResourceGroupManager::getSingleton().
initialiseAllResourceGroups();
}
7. 同样创建一个startup()函数,使用plugins.cfg创建一个Ogre 3D root类的实例:
int startup()
{
_root = new Ogre::Root("plugins_d.cfg");
8. 显示配置窗口当用户退出时,返回-1并关闭程序:
if(!_root->showConfigDialog())
{
return -1;
}
9. 创建RenderWindow和SceneManager:
Ogre::RenderWindow* window = _root->initialise(true,"Ogre3D
Beginners Guide");
_sceneManager = _root->createSceneManager(Ogre::ST_GENERIC);
10. 创建一摄像机和一个视口:
Ogre::Camera* camera = _sceneManager->createCamera("Camera");
camera->setPosition(Ogre::Vector3(0,0,50));
camera->lookAt(Ogre::Vector3(0,0,0));
camera->setNearClipDistance(5);
Ogre::Viewport* viewport = window->addViewport(camera);
viewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.0));
camera->setAspectRatio(Ogre::Real(viewport->getActualWidth())/
Ogre::Real(viewport->getActualHeight()));
11. 调用函数来加载我们的资源并且然后函数来创建创建一个场景;最后,Ogre 3D 可以开始渲染了:
loadResources();
createScene();
_root->startRendering();
return 0;
12. 然后创建包含创建SceneNode 和Entity 的createScene()函数:
void createScene()
{
Ogre::Entity*ent=_sceneManager->createEntity("Sinbad.mesh"); _sceneManager->getRootSceneNode()->attachObject(ent);
}
13. 我们需要构造函数设置两个指针为NULL,即使它不被赋值,这样我们也可以删除它:
MyApplication()
{
_sceneManager = NULL;
_root = NULL;
}
14. 当实例摧毁的时候,我们需要删除root实例,所以声明一个析构函数来处理:
~MyApplication()
{
delete _root;
}
15. 最后剩下的事情是修改main函数:
int main (void)
{
MyApplication app;
app.startup();
return 0;
}
16. 编译运行程序;场景依旧没有改变。
【 刚刚发生了什么?】
我们重构了开始的代码这样不同的函数得到了更好的组织。我们也添加了一个析构函数这样当程序关闭的时候创建的实例可以得到删除。一个问题就是我们的析构函数不能够得到调用,因为startup()函数永远不返回,这样就没有什么方法来关闭我们的程序。我们需要添加一个FrameListener来告诉Ogre 3D停止渲染。
【 添加一个帧监听】
我们之前已经使用过ExampleFrameListener;这一次我们将会使用我们自己声明的接口。
使用之前的代码,我们将会添加我们自己的FrameListener声明
1. 创建一个新的称为MyFrameListener,并列出三个公有的事件响应函数:
class MyFrameListener : public Ogre::FrameListener
{
public:
2. 首先,声明frameStarted函数,现在这个函数以返回false来结束函数:
bool frameStarted(const Ogre::FrameEvent& evt)
{
return false;
}
3. 同样我们需要一个frameEnded函数,也是返回false:
bool frameEnded(const Ogre::FrameEvent& evt)
{
return false;
}
4. 最后我们声明的一个函数是frameRenderingQueued函数,也是返回false:
bool frameRenderingQueued(const Ogre::FrameEvent& evt)
{
return false;
}
5. 类的主体需要一个指针来存储FrameListener:
MyFrameListener* _listener;
6. 切记在构造函数用设置监听初始化的值为NULL:
_listener = NULL;
7. 让析构函数删除这个实例:
delete _listener;
8. 最后创建一个FrameListener的实例,并添加它到root对象;这些代码在startup()函数中:
_listener = new MyFrameListener();
_root->addFrameListener(_listener);
9. 编译运行程序;程序运行会直接关闭。
【 刚刚发生了什么?】
我们创建自己的FrameListener类,这个类不依赖于ExampleFrameListener的实现。这次我们直接从FrameListener接口继承。这个接口包含了三个虚函数,这三个虚函数需要我们在自己的FrameListener中覆写。我们已经知道frameStarted()函数,但是其他两个是新的函数。所有三个函数返回false,都是通知Ogre 3D停止渲染并且关闭程序。使用我们的实现,添加一个FrameListener到root实例并且开始渲染程序;没什么特别的,这里程序直接关闭。
【研究FrameListener的功能 】
我们的FrameListener实现有三个函数;每个都是在不同的时刻调用。我们将会研究一下他们是在渲染的哪个过程被调用的。
当FrameListener调用时,利用控制台的输出检查哪里被调用。
1. 首先当每个函数调用时,使其输出一个消息到控制台。
bool frameStarted(const Ogre::FrameEvent& evt)
{
std::cout<<"Frame started"<<std::endl;
return false;
}
bool frameEnded(const Ogre::FrameEvent& evt)
{
std::cout<<"Frame ended"<<std::endl;
return false;
}
bool frameRenderingQueued(const Ogre::FrameEvent& evt)
{
std::cout<<"Frame queued"<<endl;
return false;
}
2. 编译运行程序;在控制台中你会看到第一个字符串Frame started。
【 刚刚发生了什么?】
我们添加了一个debug消息输出到每个FrameListener的函数里面以观察哪个函数被调用了。运行程序,我们注意到只有第一个debug信息输出了。这是因为frameStarted返回了false,这是一个请求root实例关闭程序的信号。
既然我们知道当frameStarted()返回false,那让我们看一下当frameStarted()返回true的时候发生了什么。
【 在frameStarted函数中返回true 】
现在我们将会修改FrameListener的行为以研究,这个修改是如何影响了其行为。
1. 改变frameStarted并返回true:
bool frameStarted(const Ogre::FrameEvent& evt)
{
std::cout<<"Frame started"<<endl;
return true;
}
2. 编译运行程序。在程序关闭之前,你将会看到渲染的场景闪了一下并且在控制台输出里会有下面两行:
Frame started
Frame queued
【 刚刚发生了什么?】
现在,frameStarted函数返回true并且这使得Ogre 3D继续渲染,直到frameRenderingQueued返回false为止。我们这次在frameRenderingQueued函数调用的时候看了一个场景,渲染的缓存在程序关闭之前得到了交换。
【 双缓存】
当一个场景被渲染的时候,它并不是直接渲染到会直接显示到显示设备中缓存中。正常来说,场景被渲染到第二缓存,并且当渲染结束的时候,缓存得到了交换。这样可以避免如果在渲染同一缓存时,一些尚未处理完成的效果就会显示在显示屏上。FrameListener 的frameRenderingQueued函数,当场景渲染转至未显示的后台缓存后被调用。在缓存交换之前,渲染已经完毕但尚未显示。直接调用完frameRenderingQueued,缓存得到交换,然后程序得到return的值,最后关闭程序。这就是我们只看到一眼图片。现在,我们将会研究当frameRenderingQueued返回true后会有什么发生。
【 在frameRenderingQueued返回true 】
我们再次修改代码来测试FrameListener的行为。
1. 改变frameRenderingQueued并返回true:
bool frameRenderingQueued (const Ogre::FrameEvent& evt)
{
std::cout<<"Frame queued"<<endl;
return true;
}
2. 编译运行程序。你将会在程序关闭之前看到Sinbad一小会,下面这三行是控制台输出的:
Frame started
Frame queued
Frame ended
【 刚刚发生了什么?】
既然frameRenderingQueued函数返回为true,那么Ogre 3D将会继续渲染直到frameEnded方法返回false。
如我们上一个例子,渲染的缓存交换了,这样我们可以看到短时间的场景。当帧被渲染后,frameEnded将会返回false,这将会关闭程序,在这种情况下,不需要改变对FrameListener的理解。
【 在frameEnded函数中返回true 】
现在让我们测试最后一种可能性:
1。改变frameEnded(译注* 原文错为frameRenderingQueued),让它返回true:
bool frameEnded (const Ogre::FrameEvent& evt)
{
std::cout<<"Frame ended"<<endl;
return true;
}
2. 编译运行程序。你将会看到Sinbad的实例,并且下面的三行输出会一直循环:
Frame started
Frame queued
Frame ended
【 刚刚发生了什么 ? 】
现在,所有的事件处理函数都是返回true,并且,除非我们自己关闭程序,程序永不会直接关闭。
【 添加输入 】
既然我们已经知道FrameListener是如何工作的了,那么让我们添加一些输入吧。
1. 我们需要引入OIS头文件来使用OIS:
#include "OISOIS.h"
2. 删除FrameListener中的所有函数,并添加两个私有成员来存储InputManager和Keyboard:
OIS::InputManager* _InputManager;
OIS::Keyboard* _Keyboard;
3. FramListener需要一个指向RenderWindow类的指针来初始化OIS,所以我们需要一个以窗口指针作为参数的构造函数:
MyFrameListener(Ogre::RenderWindow* win)
{
4. OIS使用参数列表来初始化,我们同样需要一个字符串形式的窗口句柄来构建参数列表;创建三个必要的变量来保存数据:
OIS::ParamList parameters;
unsigned int windowHandle = 0;
std::ostringstream windowHandleString;
5. 获得RenderWindow的句柄并转换它为一个字符串:
win->getCustomAttribute("WINDOW", &windowHandle);
windowHandleString << windowHandle;
6. 使用键值”WINDOW”来添加字符串类型的句柄到参数表
parameters.insert(std::make_pair("WINDOW", windowHandleString.
str()));
7. 使用参数列表来创建InputManager:
_InputManager = OIS::InputManager::createInputSystem(parameters);
8. 用管理器来创建keyboard:
_Keyboard = static_cast<OIS::Keyboard*>(_InputManager-
>createInputObject( OIS::OISKeyboard, false ));
9. 还有,记着我们构造函数中创建的,我们需要在析构函数在把他摧毁:
~MyFrameListener()
{
_InputManager->destroyInputObject(_Keyboard);
OIS::InputManager::destroyInputSystem(_InputManager);
}
10. 创建一个新的frameStarted函数,来获取当前的键盘状态,并且如果按下了Escape,将会返回false;否则,他会返回true:
bool frameStarted(const Ogre::FrameEvent& evt)
{
_Keyboard->capture();
if(_Keyboard->isKeyDown(OIS::KC_ESCAPE))
{
return false;
}
return true;
}
11 最后要做的一件事就是使用一个指针来改变FrameListener的实例,这样在startup函数中就可以渲染窗口了:
_listener = new MyFrameListener(window);
_root->addFrameListener(_listener);
12 编译运行程序。你会看到场景,并且现在可以按下Escape键来关闭程序。
【 刚刚发生了什么?】
我们用之前第四章(获得用户输入和使用帧监听)讲过的方式给FramListener添加了输入的能力。但是这次有点不同的是,我们没有使用任何example中给的类,这次是我们自己写出来的版本。
【 我们自己的主循环】
我们已经试了了startRenering函数来启动我们的程序。但在这之后,依靠FrameListener是我们目前唯一渲染帧的方式。但是一些时候我们放弃渲染的主循环是不太可能或者不是我们期望的;为了解决这个情况,Ogre 3D 提供了另一个方式,不需要我们去放弃渲染的主循环
使用我们之前的代码,现在我们将会使用我们的渲染循环。
1. 我们的程序需要知道是否要一直循环下去;所以添加一个布尔类型的私有成员变量以记录成员的状态:
bool _keepRunning;
2. 删除在startup函数中的startRendering函数。
3. 添加一个新的称为renderOneFrame的函数,这个函数调用了root实例的renderOneFrame函数,并且可以把返回值保存在_ keepRunning成员变量中。在调用这个之前,添加一个函数在处理所有的窗口事件:
void renderOneFrame()
{
Ogre::WindowEventUtilities::messagePump();
_keepRunning = _root->renderOneFrame();
}
4. 添加一个获取_keepRunning成员变量的函数:
bool keepRunning()
{
return _keepRunning;
}
5. 添加一个while循环到主函数中,只要keepRunning函数返回true,循环将一直进行下去。在循环体里,提用程序的renderOneFrame函数:
while(app.keepRunning())
{
app.renderOneFrame();
}
6. 编译并运行程序。看到的效果和上一个例子没有什么明显的不同。
【 刚刚发生了什么】
我们把主循环的控制权从Ogre 3D移到我们的程序之中。在改变之前,Ogre 3D使用了一个内部的主循环,这个主循环之前我们是无法控制的,并且不得不依赖FrameListener来通知我们帧是否被渲染了。
现在我们得有自己的主循环。为实现这个功能,我们需要一个Boolean成员变量,这个变量来通知我们程序是否想要继续渲染;这个变量我们在第一步中添加完毕。在第二步中移除了之前调用的startRendering函数,这样我们就不会把渲染的主循环交给Ogre了。在第三步,我们创建了一个函数,此函数首先调用了Ogre 3D的一个帮助函数,这个函数来处理来自操作系统的窗口事件信息。然后他发送自从上一帧结束后所有的信息,这样程序才能在操作系统的窗口系统中表现正常。
在这之后我们调用了Ogre 3D的renderOneFrame函数,这个函数正如它的名字所暗示的那样:渲染每帧,并且调用frameStarted ,frameRenderingQueued和frameEnded在帧监听中注册的事件处理程序,如果其中的一个子函数false,那么此函数返回false。我们通过把返回值赋值给_ keepRunning成员变量,这样我们就可以检测程序是否在持续运行。当renderOneFrame返回true的时候,程序应该改运行下去;如果返回false,我们知道是帧监听想要关闭程序,这样我们设置_keepRunning变量为false。第四步仅添加了getter函数来获取_keepRunning的值。
第五步,我们使用了_keepRunning变量作为循环的条件。这意味着如果_keepRunning返回true,他将持续运行下去,直到一个FrameListener返回false,这将会导致while循环退出然后整个程序关闭。在while循环内我们调用程序的renderOneFrame函数以最新的渲染信息更新渲染窗口。这就是我们需要创建自己主循环的必要条件。
【 添加一个帧监听 】
使用我们自己的帧监听,我们将会添加用户控制的摄像机。
1. 为控制摄像机我们需要一个鼠标的指针,一个摄像机的指针和一个定义摄像机移动速度的的变量:
OIS::Mouse* _Mouse;
Ogre::Camera* _Cam;
float _movementspeed;
2. 修改构造函数并添加摄像机指针作为一个新的参数并设置移动的速度为50:
MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera* cam)
{
_Cam = cam;
_movementspeed = 50.0f;
3. 使用InputManager初始话mouse指针:
_Mouse = static_cast<OIS::Mouse*>(_InputManager->createInputObject( OIS::OISMouse, false ));
4. 并记住在析构函数里面摧毁它:
_InputManager->destroyInputObject(_Mouse);
5. 添加WASD键来移动摄像机的代码和frameStarted事件处理中函数来控制摄像机移动速度的代码:
Ogre::Vector3 translate(0,0,0);
if(_Keyboard->isKeyDown(OIS::KC_W))
{
translate += Ogre::Vector3(0,0,-1);
}
if(_Keyboard->isKeyDown(OIS::KC_S))
{
translate += Ogre::Vector3(0,0,1);
}
if(_Keyboard->isKeyDown(OIS::KC_A))
{
translate += Ogre::Vector3(-1,0,0);
}
if(_Keyboard->isKeyDown(OIS::KC_D))
{
translate += Ogre::Vector3(1,0,0);
}
_Cam->moveRelative(translate*evt.timeSinceLastFrame * _movementspeed);
6. 鼠标控制同理:
_Mouse->capture();
float rotX = _Mouse->getMouseState().X.rel * evt.
timeSinceLastFrame* -1;
float rotY = _Mouse->getMouseState().Y.rel * evt.
timeSinceLastFrame * -1;
_Cam->yaw(Ogre::Radian(rotX));
_Cam->pitch(Ogre::Radian(rotY));
7. 最后一件事就是改变FrameListener实例化的部分:
_listener = new MyFrameListener(window,camera);
8. 编译运行程序。场景虽然没有改变,但是现在我们可以控制摄像机了:
【 刚刚发生了什么 ?】
我们使用之前章节的知识添加了一个用户可以控制的摄像机。下一步要做的就是添加合成器和别的特性,使我们的程序看起来更为有趣,并学习和补充我们之前学过的知识。
【 添加合成器 】
之前,我们创建了三个合成器,现在我们将会给程序添加可以使用键盘响应开关闭各个合成器.
我们已经几乎完成了我们的程序,我们将会添加一些合成器是我们的程序更为有趣一些:
1. 我们将会在帧监听中使用合成器,所以我们需要一个包含视口的成员变量:
Ogre::Viewport* _viewport;
2. 我们同样添加三个布尔类型的成员变量来控制合成器的开关:
bool _comp1, _comp2, _comp3;
3. 我们使用键盘输入来切换合成器的开关。为了能够区分按键,我们需要记录之前按键的状态:
bool _down1, _down2, _down3;
4. 改变FrameListener的构造函数来并添加视口参数:
MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera* cam,Ogre::Viewport* viewport)
5. 把构造函数的视口参数赋值给成员变量,并初始化所有的布尔类型的变量为false:
_viewport = viewport;
_comp1 = false;
_comp2 = false;
_comp3 = false;
_down1 = false;
_down2 = false;
_down3 = false;
6. 如果按下1键,并且1键之前没有按过,改变其状态为按下,改变合成器的状态,并使用改变的值来激活或注销合成器,这些代码应该写在frameStarted函数中:
if(_Keyboard->isKeyDown(OIS::KC_1) && ! _down1)
{
_down1 = true;
_comp1 = !comp1;
Ogre::CompositorManager::getSingleton().setCompositorEnabled(_viewport, "Compositor2",_comp1);
}
7. 用同样的办法来使用另外两个合成器:
if(_Keyboard->isKeyDown(OIS::KC_2) && ! _down2)
{
_down2 = true;
_comp2 = !comp2;
Ogre::CompositorManager::getSingleton().setCompositorEnabled(_
viewport, "Compositor3", _comp2);
}
if(_Keyboard->isKeyDown(OIS::KC_3) && ! _down3)
{
_down3 = true;
_comp3 = !comp3;
Ogre::CompositorManager::getSingleton().setCompositorEnabled(_
viewport, "Compositor7", _comp3);
}
8. 如果刚按下的键不再按下了,我们需要改变键的状态:
if(!_Keyboard->isKeyDown(OIS::KC_1))
{
_down1 = false;
}
if(!_Keyboard->isKeyDown(OIS::KC_2))
{
_down2 = false;
}
if(!_Keyboard->isKeyDown(OIS::KC_3))
{
_down3 = false;
}
9. 在startup()函数中,在函数的末尾添加三个合成器到视口:
Ogre::CompositorManager::getSingleton().addCompositor(viewport,
"Compositor2");
Ogre::CompositorManager::getSingleton().addCompositor(viewport,
"Compositor3");
Ogre::CompositorManager::getSingleton().addCompositor(viewport,
"Compositor7");
10. 记得改变FrameListener的实例并添加视口的指针作为参数:
_listener = new MyFrameListener(window,camera,viewport);
11 . 编译运行程序。使用1,2,3键,你就能自由开关不同的合成器。1键是让图像看起来为黑白,2键是反选图片效果,3键是使图像看起来为更小的分辨率;你可以把任何想要的效果合成起来:
【 刚刚发生了什么?】
我们在这章中添加一些合成器,并实现了可以使用1,2,3按键来开关他们。我们使用了当不只有一个合成器时,Ogre 3D会自动合成合成器的特性。
【 添加添加一个平面和一个灯光 】
没有地面作为参照物,在3D的空间中找到方向是比较困难的,所以现在让我们添加一个地面。
这次所有的添加的内容是在createScene()函数中进行的:
1. 首先我们需要添加一个平面的定义:
Ogre::Plane plane(Ogre::Vector3::UNIT_Y, -5);
Ogre::MeshManager::getSingleton().createPlane("plane",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
1500,1500,200,200,true,1,5,5,Ogre::Vector3::UNIT_Z);
2. 然后创建平面的实例,并绑定其到场景,并且改变其材质:
Ogre::Entity* ground= _sceneManager->createEntity("LightPlaneEnti
ty", "plane");
_sceneManager->getRootSceneNode()->createChildSceneNode()-
>attachObject(ground);
ground->setMaterialName("Examples/BeachStones");
3. 同样我们也想在场景中添加一些灯光;所以这里我们使用一个方向光:
Ogre::Light* light = _sceneManager->createLight("Light1");
light->setType(Ogre::Light::LT_DIRECTIONAL);
light->setDirection(Ogre::Vector3(1,-1,0));
4. 添加一些阴影会有更好的效果:
_sceneManager->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_
ADDITIVE);
5. 编译运行程序。你将会看到一个有着石头纹理的平面,在地面上有投射有Sinbad的阴影。
【 刚刚发生了什么?】
我们再一次使用了之前的知识来创建了一个平面和一个灯光,并给场景添加了阴影。
【 添加用户控制 】
我们已经在平面上有了模型实例,但是我们目前并不能移动它;让我们现在改变它吧。
现在我们通过添加用户控制模型的功能来添加场景的交互性。
1. FrameListener需要添加两个成员变量:一个是我们想要移动的结点,另一个是我们想要移动的速度:
float _WalkingSpeed;
Ogre::SceneNode* _node;
2. 在构造函数中添加结点参数,以传递结点到构造函数中:
MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera*
cam,Ogre::Viewport* viewport,Ogre::SceneNode* node)
3. 把结点指针赋值给成员变量并设置移动速度为50:
_WalkingSpeed = 50.0f;
_node = node;
4. 在frameStarted函数中我们需要两个新的变量,这两个变量将会保存用户应用于结点的旋转和平移:
Ogre::Vector3 SinbadTranslate(0,0,0);
float _rotation = 0.0f;
5. 然后我们需要添加代码实现根据用户输入来计算位移和旋转:
if(_Keyboard->isKeyDown(OIS::KC_UP))
{
SinbadTranslate += Ogre::Vector3(0,0,-1);
_rotation = 3.14f;
}
if(_Keyboard->isKeyDown(OIS::KC_DOWN))
{
SinbadTranslate += Ogre::Vector3(0,0,1);
_rotation = 0.0f;
}
if(_Keyboard->isKeyDown(OIS::KC_LEFT))
{
SinbadTranslate += Ogre::Vector3(-1,0,0);
_rotation = -1.57f;
}
if(_Keyboard->isKeyDown(OIS::KC_RIGHT))
{
SinbadTranslate += Ogre::Vector3(1,0,0);
_rotation = 1.57f;
}
6. 然后我们需要应用平移和旋转到结点:
_node->translate(SinbadTranslate * evt.timeSinceLastFrame * _
WalkingSpeed);
_node->resetOrientation();
_node->yaw(Ogre::Radian(_rotation));
7. 程序本身需要来存储我们想要控制实体的结点指针:
Ogre::SceneNode* _SinbadNode;
8. FrameListener实例需要这个指针:
_listener = new MyFrameListener(window,camera,viewport,_
SinbadNode);
9. createScene函数需要使用上面的指针来创建和存储我们想要移动实体的结点;修改函数中的相应代码:
_SinbadNode = _sceneManager->getRootSceneNode()-
>createChildSceneNode();
_SinbadNode->attachObject(sinbadEnt);
10. 编译运行程序。你将会可以使用方向键来移动实体:
【 刚刚发生了什么?】
我们通过在FrameListener添加代码,使实体可以通过方向键来移动。现在我们的Sinbad可以像魔术师一样来平面中飘动了。
【 添加动画 】
让模型无动作的平移不是我们所想要的;所以让我们添加一些动画吧。
我们的模型可以移动,但是现在却不能做动作,让我们接下来改变它:
1. FrameListener类需要两个动画状态:
Ogre::AnimationState* _aniState;
Ogre::AnimationState* _aniStateTop;
2. 为在构造函数中获得动画状态,我们需要一个实体的指针:
MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera*
cam,Ogre::Viewport* viewport,Ogre::SceneNode* node,Ogre::Entity*
ent)
3. 通过使用这个指针我们可以检索动画状态,并在接下来的使用中保存他们:
_aniState = ent->getAnimationState("RunBase");
_aniState->setLoop(false);
_aniStateTop = ent->getAnimationState(«RunTop»);
_aniStateTop->setLoop(false);
4. 既然我们有了AnimationState,我们需要在frameStarted函数中有一个开关,这个开关描述了实体这帧是否移动了。我们添加这个开关到if判断中来查询键盘的状态:
bool walked = false;
if(_Keyboard->isKeyDown(OIS::KC_UP))
{
SinbadTranslate += Ogre::Vector3(0,0,-1);
_rotation = 3.14f;
walked = true;
}
if(_Keyboard->isKeyDown(OIS::KC_DOWN))
{
SinbadTranslate += Ogre::Vector3(0,0,1);
_rotation = 0.0f;
walked = true;
}
if(_Keyboard->isKeyDown(OIS::KC_LEFT))
{
SinbadTranslate += Ogre::Vector3(-1,0,0);
_rotation = -1.57f;
walked = true;
}
if(_Keyboard->isKeyDown(OIS::KC_RIGHT))
{
SinbadTranslate += Ogre::Vector3(1,0,0);
_rotation = 1.57f;
walked = true;
}
5. 如果模型移动了,我们激活动画;如果动画结束,我们重新使它循环
if(walked)
{
_aniState->setEnabled(true);
_aniStateTop->setEnabled(true);
if(_aniState->hasEnded())
{
_aniState->setTimePosition(0.0f);
}
if(_aniStateTop->hasEnded())
{
_aniStateTop->setTimePosition(0.0f);
}
}
6. 如果模型没有移动,我们注销动画,并设置动画为其开始的位置:
else
{
_aniState->setTimePosition(0.0f);
_aniState->setEnabled(false);
_aniStateTop->setTimePosition(0.0f);
_aniStateTop->setEnabled(false);
}
7. 在每一帧中,我们需要添加经过的时间给动画;否则,它就不会移动:
_aniState->addTime(evt.timeSinceLastFrame);
_aniStateTop->addTime(evt.timeSinceLastFrame);
8. 程序需要一个指向实体的指针
Ogre::Entity* _SinbadEnt;
9. 我们在初始化FrameListener的时候使用了这个指针:
_listener = new MyFrameListener(window,camera,viewport,_
SinbadNode,_SinbadEnt);
10. 同样,创建实体:
_SinbadEnt = _sceneManager->createEntity("Sinbad.mesh");
11. 编译运行程序。当模型移动的时候就可以播放相应的移动动画了:
【 刚刚发生了什么?】
我们给我们的模型添加了动画并设置在模型运动的时候运行。
【 总结 】
本章中我们学习了很多关于激活我们程序和运行我们Ogre 3D程序的相关知识。
特别的,我们包含了以下几个方面:
1. Ogre 3D是怎么运行的。
2. 怎样使我们的渲染主循环运行。
3. 写出我们自己的一个应用和帧监听的实现
一些专题我们前面已经涉及到了,但是现在我们把它们结合在一起形成一个更加复杂的应用。
现在我们已经学习了创建我们自己的Ogre 3D应用所需要的所有内容。下一章节中我们会把重点放在使用其他的库或者一些附加的功能使应用程序更加完美。