本文介绍 OGRE 3D 1.9 程序的启动过程,即从程序启动到3D图形呈现,背后有哪些OGRE相关的代码被执行。会涉及的OGRE类包括:

  1. Root
  2. RenderSystem
  3. RenderWindow
  4. ResourceGroupManager
  5. LogManager
  6. Viewport
  7. SceneManager
  8. Camera
  9. SceneNode
  10. Entity
  11. Light

建议在阅读本文时参考OGRE API Reference,OGRE官方给的API Reference没有类的协作图,可以自己用Doxygen生成API文档,见:Bullet的学习资源(用Doxygen生成API文档)

关于如何安装OGRE和如何配置一个可以运行的OGRE HelloWorld程序见:OGRE 1.9 的第一个程序(OGRE HelloWorld程序)

本节所有代码如下,可以先迅速浏览,然后看后面详细解释,后面将用“启动代码”来指代这段代码:

 #include<OgreRoot.h>
#include<OgreRenderSystem.h>
#include<OgreRenderWindow.h>
#include<OgreConfigFile.h>
#include<OgreResourceGroupManager.h>
#include<OgreLogManager.h>
#include<OgreViewport.h>
#include<OgreSceneManager.h>
#include<OgreCamera.h>
#include<OgreLight.h>
#include<OgreEntity.h> int main(int argc, char *argv[])
{
Ogre::Root* mRoot;
Ogre::RenderWindow* mWindow;
Ogre::SceneManager* mSceneMgr;
Ogre::Camera* mCamera; // 创建Root,在调用OGRE任何功能之前必须已经创建了Root
mRoot = new Ogre::Root("plugins.cfg","ogre.cfg","Ogre.log"); // 设定 RenderSystem
Ogre::RenderSystem *rs =
mRoot->getRenderSystemByName("OpenGL Rendering Subsystem");
mRoot->setRenderSystem(rs);
rs->setConfigOption("Full Screen", "No");
rs->setConfigOption("Video Mode", "800x600 @ 32-bit colour");
// 另一种方法是: if(!mRoot->showConfigDialog()) return false; // 初始化 RenderSystem
mRoot->initialise(false); // 创建 RenderWindow
int hWnd = ;
Ogre::NameValuePairList misc;
misc["externalWindowHandle"] = Ogre::StringConverter::toString((int)hWnd);
mWindow = mRoot->createRenderWindow("Win Ogre", , , false, &misc);
// 上2步的另一种实现是: mWindow = mRoot->initialise(true, "Win Ogre"); // 创建SceneManager,将渲染目标绑定到RenderWindow
mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
// Create one camera
mCamera = mSceneMgr->createCamera("PlayerCam");
mCamera->setNearClipDistance();
// Create one viewport, entire window
Ogre::Viewport* vp = mWindow->addViewport(mCamera);
vp->setBackgroundColour(Ogre::ColourValue(,,));
// Alter the camera aspect ratio to match the viewport
mCamera->setAspectRatio(
Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight())); // 加载资源,该歩不能早于RenderSystem的初始化和RenderWindow的创建
// 如果使用OverlaySystem,该歩也不能早于OverlaySystem的创建
Ogre::ConfigFile cf; cf.load("resources.cfg");
Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
Ogre::String secName, typeName, archName;
while( seci.hasMoreElements() ){
secName = seci.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
for( i=settings->begin(); i!=settings->end(); ++i ){
typeName = i->first;
archName = i->second;
Ogre::ResourceGroupManager::getSingleton().
addResourceLocation(archName, typeName, secName);
}
}
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); // 构造及设置场景
mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f)); Ogre::Entity* entNinja = mSceneMgr->createEntity("entNinja", "ninja.mesh");
Ogre::SceneNode* nodeNinja = mSceneMgr->createSceneNode("nodeNinja");
mSceneMgr->getRootSceneNode()->addChild(nodeNinja);
nodeNinja->attachObject(entNinja);
Ogre::Entity* entSphere = mSceneMgr->createEntity("entSphere", "sphere.mesh");
Ogre::SceneNode* nodeSphere = mSceneMgr->createSceneNode("nodeSphere");
mSceneMgr->getRootSceneNode()->addChild(nodeSphere);
nodeSphere->attachObject(entSphere);
nodeNinja->setPosition(-,-,);
nodeSphere->translate(,,);
Ogre::Light* pointLight1 = mSceneMgr->createLight("pointLight1");
pointLight1->setType(Ogre::Light::LT_POINT);
pointLight1->setDiffuseColour(Ogre::ColourValue::White);
pointLight1->setSpecularColour(Ogre::ColourValue::White);
pointLight1->setPosition(-,,-); mCamera->setPosition(Ogre::Vector3(,,-));
mCamera->lookAt(Ogre::Vector3(,,)); // 渲染循环
Ogre::LogManager::getSingleton().logMessage(">>Rendering");
mRoot->startRendering(); // 释放资源,目前只需释放Root
delete mRoot; return ;
}

运行结果截图:

1. 启动过程概览

我们概要地看OGRE的启动,OGRE WIKI Basic Tutorial 6: The Ogre Startup Sequence中摘出下面这段,注意它和上面的代码(“启动代码”)是有差别的,各步骤的顺序不同:

The basic Ogre life cycle looks like this:

  1. Create the Root object.
  2. Define the resources that Ogre will use.
  3. Choose and set up the RenderSystem (that is, DirectX, OpenGL, etc).
  4. Create the RenderWindow (the window which Ogre resides in).
  5. Initialise the resources that you are going to use.
  6. Create a scene using those resources.
  7. Set up any third party libraries and plugins.
  8. Create any number of frame listeners.
  9. Start the render loop.

总的来说,先是初始化,最后启动渲染循环。我将所有这些类的关系总结如下图(不是什么UML图,就大致理解吧):

看完后面的详细解释后可以回过头来看这段,那时你就会对OGRE的启动有个大致印象。

2. 创建Root

在调用OGRE任何功能之前,首先要实例化一个Root类,该Root实例将直接或间接指向所有其他类的实例。一个OGRE程序有且只有一个Root对象,因此Root类使用Singleton设计模式(单例模式,继承自Singleton<Root>)。说到Singleton,OGRE的很多类都是Singleton,后面还会讲的。

Root类的构造函数原型如下:

Root (const String &pluginFileName="plugins"OGRE_BUILD_SUFFIX".cfg",
const String &configFileName="ogre.cfg", const String &logFileName="Ogre.log")

其中OGRE_BUILD_SUFFIX宏在Release下定义为空,Debug下定义为"_d"。三个参数是三个文件名。

pluginFileName是插件配置文件,该文件指示OGRE要加载哪些插件,一个plugins.cfg文件的例子如下,其中#表示注释

# Defines plugins to load
# Define plugin folder
PluginFolder=.
# Define plugins
# Plugin=RenderSystem_Direct3D9
Plugin=RenderSystem_GL
Plugin=Plugin_ParticleFX
Plugin=Plugin_BSPSceneManager
Plugin=Plugin_CgProgramManager
Plugin=Plugin_PCZSceneManager
Plugin=Plugin_OctreeZone
Plugin=Plugin_OctreeSceneManager

configFileName文件设置渲染系统(OpenGL或者Direct3D)及其参数,如抗锯齿采样数(FSAA),一个针对OpenGL驱动的配置文件ogre.cfg例子如下:

Render System=OpenGL Rendering Subsystem
[OpenGL Rendering Subsystem]
Colour Depth=
Display Frequency=N/A
FSAA=
Fixed Pipeline Enabled=Yes
Full Screen=No
RTT Preferred Mode=FBO
VSync=No
VSync Interval=
Video Mode= x
sRGB Gamma Conversion=No

logFileName文件是OGRE程序的日志文件,在OGRE程序可以插入写日志的代码,日志文件方便对OGRE程序的调试。向日志文件写入信息的代码的一个例子如下:

Ogre::LogManager::getSingleton().logMessage(">>Rendering");

这里的LogManager是另一个使用Singleton设计模式的类,这种类使用静态方法getSingleton获取全局唯一的类实例。

“启动代码”中创建Root对象的代码在第21行:

mRoot = new Ogre::Root("plugins.cfg","ogre.cfg","Ogre.log");

3. 设定RenderSystem,初始化

RenderSystem类对渲染系统(底层的OpenGL或Direct3D)进行抽象,它相当于是执行渲染的设备。给 Root 添加一个RenderSystem实例的最简单方式是调用Ogre::Root:: showConfigDialog方法,运行时系统将弹出如下对话框,让用户选择要使用的图形驱动,以及相应的参数:

if(!mRoot->showConfigDialog()) return false;

我们在这个对话框所做的设置被记录在ogre.cfg文件中(见上面第2节)。也可以不用对话框,而在程序中设置,也就是说在程序中设置我们在对话框所选的项:

Ogre::RenderSystem *rs = mRoot->getRenderSystemByName("OpenGL Rendering Subsystem");
mRoot->setRenderSystem(rs);
rs->setConfigOption("Full Screen", "No");
rs->setConfigOption("Video Mode", "800x600 @ 32-bit colour");

“启动代码”使用的是后者,代码在第24-27行。

如果不想每次都弹出对话框选择渲染系统,可以用如下代码:

if( !(mRoot->restoreConfig() || mRoot->showConfigDialog()) )
return false;

restoreConfig方法读入ogre.cfg文件来代替对话框设置,还记得C/C++逻辑运算表达式求值的短路性质吧,如果mRoot->restoreConfig()返回true(存在ogre.cfg文件),mRoot->showConfigDialog()将不被执行。

RenderSystem对象创建后需要初始化,Ogre::Root::initialise(bool, const String, const String)方法就是初始化root的RenderSystem的,如果第一个bool参数为true,将自动创建窗口,“启动代码”没有这样做,在第31行:

mRoot->initialise(false);

另外还要说的是,OGRE作为一个跨平台的高层3D图形库,对图形系统进行了高度抽象,这种抽象使用户不需要关心底层技术(如OpenGL或Direct3D、win32或Xwindow),但程序的执行必然会用到底层功能(如具体渲染任务必然是OpenGL或Direct3D执行)。OGRE(或者其他很多开源程序库)是这样做到这一点的:用户使用基类(如RenderSystem和RenderWindow)接口和OGRE进行交互,代码执行时程序自动根据系统配置调用相应子类的实现来执行命令(这得益于面向对象的继承性和多态性)。RenderSystem类的继承图如下:

对于我们,使用的是OpenGL图形驱动,所以到程序执行时,实际使用的是GLRenderSystem的实现。其实RenderSystem压根就是个抽象类,不能被实例化。

4. 创建 RenderWindow

RenderWindow是对窗口的抽象,该窗口用来显示渲染结果(对于离线渲染或渲染到纹理则不需要窗口)。创建窗口最简单的方法是在调用Ogre::Root::initialise方法时传入true作为第一个参数:

mWindow = mRoot->initialise(true, "Win Ogre");

但“启动代码”为了代码的清晰,使用了手动创建RenderWindow的方法:

int hWnd = ;
Ogre::NameValuePairList misc;
misc["externalWindowHandle"] = Ogre::StringConverter::toString((int)hWnd);
mWindow = mRoot->createRenderWindow("Win Ogre", , , false, &misc);

注意上面使用的NameValuePairList类是用来构造参数的,你可能发现了,OGRE的很多参数都使用string数据类型。

5. 创建SceneManager,将渲染目标绑定到RenderWindow

SceneManager类管理OGRE的场景图形(Scene Graph),《Ogre 3D 1.7 Beginner's Guide》的Chapter 6中将SceneManager的功能总结为两个方面:

  1. 管理Camera, SceneNode, Entity, Light等场景中的对象,作为Factory提供create方法如createEntity(), createLight()(也负责释放它们);
  2. 管理场景图形,包括维护场景树的可用性,计算节点的Transform信息,隐藏面剔除(Culling)。

SceneManager不是Singleton,可以从Root创建一个(或多个)SceneManager,“启动代码”的第41行创建了一个普通类型的SceneManager:

mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);

有了SceneManager,就可以从这个Factory创建场景的对象了,主要是Camera, SceneNode, Entity, Light,构建场景(构建场景树)留到后面说,这里说Camera和RenderWindow的对应关系。

Camera是场景到窗口输出的媒介,负责将3D场景映射到2D窗口,这个映射涉及到另一个类Viewport,Viewport将Camera对场景的“拍摄”结果“贴”到窗口的全部可绘制区域的一个矩形部分。一个Root可以有多个SceneManager,一个SceneManager中也可以有多个Camera,但每个Camera都需要一个Viewport对应到窗口的一个矩形区域。现在你应该知道怎样把一个场景的不同视角,或者多个场景绘制到一个窗口的不同区域了吧。

“启动代码”中创建Camera的代码在第43行:

mCamera = mSceneMgr->createCamera("PlayerCam");
mCamera->setNearClipDistance();

其中也设置了Camera的近裁剪面。“启动代码”中创建建Viewport的代码在随后的第46行:

// Create one viewport, entire window
Ogre::Viewport* vp = mWindow->addViewport(mCamera);
vp->setBackgroundColour(Ogre::ColourValue(,,));
// Alter the camera aspect ratio to match the viewport
mCamera->setAspectRatio(
Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));

其中也设置了Viewport背景颜色和Camera长宽比例,addViewport是RenderWindow的方法,并以Camera为参数,这样就把RenderWindow和Camera联系起来了,正如我们所分析的。另外addViewport方法还有其他参数用来指定Viewport在窗口的哪一区域,上述代码使用了缺省参数,即将Viewport对应到整个窗口。

同第4节最后说的,RenderWindow是抽象类,具体的和窗口相关的功能实际是由子类实现的,在windows上,这个子类是Win32Window。

6. 加载资源

OGRE的资源文件是OGRE的一个特色,最常见的资源文件莫过于Mesh(.mesh)和Material(.material)了,注意Mesh是一个可渲染物体,而不仅仅是一个网格,Material定义可渲染物体除几何信息外的其他所有属性,可以是而不限于颜色、纹理、着色器什么的。

资源文件的一个好处就是当修改了物体的外观等信息之后,不需要重新编译程序,如果将物体的顶点数据什么的写在代码里那就当然要重新编译啦。资源文件的缺点,程序在启动时要对资源文件进行解析(分析脚本),这增加了程序启动时间,这也是HelloWorld程序需要好几秒之后才能看见图形的原因。另一个缺点,对于初学者来说,最初可能就是想画一个长方体,但在OGRE里,你就需要创建Mesh资源。当然啦,OGRE作为面向而不限于3D游戏的3D引擎,强大的资源管理能力可以大大提高开发效率,应当说,正是资源文件的庞杂换来了程序代码的简洁。

有关OGRE对资源文件处理的细节见:Resources and ResourceManagers,要使用一个程序外定义(即脚本定义)的资源,需要:

  1. 用ResourceGroupManager::addResourceLocation方法添加资源文件所在目录;
  2. 用ResourceGroupManager::declareResource方法声明(declare)资源,可选的;
  3. 用ResourceGroupManager::initialiseResourceGroup或ResourceGroupManager::initialiseAllResourceGroups方法初始化所添加目录中的资源文件脚本;
  4. 默认下,资源文件的数据直到该资源使用时才被加载,如一个纹理的图片并不是在纹理定义时加载,而是在纹理被首次使用时加载至内存,也可以手动调用ResourceGroupManager::loadResourceGroup加载。

上面的第一步在“启动代码”中对应代码如下,位于第54-67行:

Ogre::ConfigFile cf; cf.load("resources.cfg");
Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
Ogre::String secName, typeName, archName;
while( seci.hasMoreElements() ){
secName = seci.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
for( i=settings->begin(); i!=settings->end(); ++i ){
typeName = i->first;
archName = i->second;
Ogre::ResourceGroupManager::getSingleton().
addResourceLocation(archName, typeName, secName);
}
}

其中"resources.cfg"是资源文件名字,文件内容如下(为了简洁,删减了一些):

# Resources required by the sample browser and most samples.
[Essential]
Zip=../../media/packs/SdkTrays.zip
Zip=../../media/packs/profiler.zip
FileSystem=../../media/thumbnails # Common sample resources needed by many of the samples.
# Rarely used resources should be separately loaded by the
# samples which require them.
[Popular]
FileSystem=../../media/fonts
FileSystem=../../media/models
Zip=../../media/packs/cubemap.zip
Zip=../../media/packs/cubemapsJS.zip [General]
FileSystem=../../media # Materials for visual tests
[Tests]
FileSystem=../../media/../../Tests/Media

Ogre::ConfigFile是一个资源配置文件解析的辅助类,类似于XML解析,和代码对应,Essential、Popular、Popular是secName,这是OGRE为方便对资源进行管理而分的组,每个settings的格式为:typeName= archName(参数类型=参数值)。

注意下面这句代码:

Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);

OGRE有很多xxxManager类,它们负责管理特定事物,如ResourceGroupManager提供对资源组的操作,LogManager提供写日志文件功能,SceneManager管理场景图形等等。这些Manager中的很多,比如LogManager和ResourceGroupManager使用Singleton设计模式,可以调用静态方法getSingleton获取全局唯一的实例。但SceneManager不是单例模式的,因为一个Root可以有多个场景图形(场景树)。

“启动代码”中declare资源的代码如下,在第68行:

Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

这里采取的是简单粗暴的方式,解析资源目录的所有脚本,怪不得程序启动后要等那么久了。

注意declare资源有时不能进行的太早,例如,不能早于RenderSystem的初始化和RenderWindow的创建,如果使用OverlaySystem,也不能早于OverlaySystem的创建。原因同第3节最后分析的,因为资源的解析具体是由子类实现的,在没有确定使用的是RenderSystem和RenderWindow的哪个子类前,不能确定使用哪个解析资源的子类,例如,RenderSystem的不同子类GLRenderSystem或D3D9RenderSystem,使用的纹理解析的类不同,如下图:

    截止目前你应该了解plugins.cfg、ogre.cfg、Ogre.log、resources.cfg文件的作用了吧。

7. 构造场景树

目前大多数3D图形库采用了场景图形(Scene Graph)技术,即用场景树来组织场景的所有物体,场景树的节点可分为两种:分支节点和叶节点。分支节点SceneNode(继承自Node)主要负责空间位置变化,叶节点可以为:Entity(可绘制实体),Light,Camera等。关于场景树,最需要了解的是,叶节点对象在世界坐标中的最终位置是由父节点级联决定的。一个典型的场景树如下图:

Entity3的世界坐标由Node5、Node4、Node2联合决定(世界坐标计算方式可以修改)。每个Node都有一些空间变换方法:setPosition、setOrientation、setScale、translate、rotate、scale,其中前三个是覆盖式修改,后三个是增量式修改。用Ogre::Node::addChild方法连接两个Node,用Ogre::SceneNode::attachObject方法连接Node和叶节点。上图有一个容易混淆的地方:Light1是不是只作用于Node4子树呢,答案是否定的,Light1作用于整个场景树,Camera1也是类似的。Light和Camera总是作用于整个场景树,其上级Node只起到对其世界坐标进行变换的作用。

注意,一个可用的场景树不能有循环路径,如下图的场景树,OGRE程序运行时会抛出异常:

可以调用Ogre::SceneManager::setShadowTechnique方法设置阴影,Ogre::SceneManager::setSkyBox方法设置天空,Ogre::SceneManager::setFog方法设置雾效果。

“启动代码”构建了一个简单的场景,代码在第71-91行:

mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f)); Ogre::Entity* entNinja = mSceneMgr->createEntity("entNinja", "ninja.mesh");
Ogre::SceneNode* nodeNinja = mSceneMgr->createSceneNode("nodeNinja");
mSceneMgr->getRootSceneNode()->addChild(nodeNinja);
nodeNinja->attachObject(entNinja);
Ogre::Entity* entSphere = mSceneMgr->createEntity("entSphere", "sphere.mesh");
Ogre::SceneNode* nodeSphere = mSceneMgr->createSceneNode("nodeSphere");
mSceneMgr->getRootSceneNode()->addChild(nodeSphere);
nodeSphere->attachObject(entSphere);
nodeNinja->setPosition(-,-,);
nodeSphere->translate(,,);
Ogre::Light* pointLight1 = mSceneMgr->createLight("pointLight1");
pointLight1->setType(Ogre::Light::LT_POINT);
pointLight1->setDiffuseColour(Ogre::ColourValue::White);
pointLight1->setSpecularColour(Ogre::ColourValue::White);
pointLight1->setPosition(-,,-); mCamera->setPosition(Ogre::Vector3(,,-));
mCamera->lookAt(Ogre::Vector3(,,));

8. 渲染循环

调用Root::startRendering方法进入渲染循环,渲染结束释放Root:

Ogre::LogManager::getSingleton().logMessage(">>Rendering");
mRoot->startRendering(); // 释放资源,目前只需释放Root
delete mRoot;

其中使用LogManager这个Singleton类的功能向日志文件中写入了信息。

startRendering函数实现如下:

void Root::startRendering(void)
{
assert(mActiveRenderer != ); mActiveRenderer->_initRenderTargets(); // Clear event times
clearEventTimes(); // Infinite loop, until broken out of by frame listeners
// or break out by calling queueEndRendering()
mQueuedEnd = false; while( !mQueuedEnd )
{
//Pump messages in all registered RenderWindow windows
WindowEventUtilities::messagePump(); if (!renderOneFrame())
break;
}
}

Root::renderOneFrame方法代码如下:

bool Root::renderOneFrame(void)
{
if(!_fireFrameStarted())
return false; if(!_updateAllRenderTargets())
return false; return _fireFrameEnded();
}

也可以自行构造渲染循环,这样就可以解决“启动代码”点击关闭窗口程序也不退出的问题了:

while(true)
{
// Pump window messages for nice behaviour
Ogre::WindowEventUtilities::messagePump(); if(mWindow->isClosed())
{
return false;
} // Render a frame
if(!mRoot->renderOneFrame()) return false;
}

9. 总结

本文要点总结如下:

  1. OGRE程序总是从创建Root实例开始;
  2. OGRE的很多xxxManager类使用了Singleton设计模式,可以调用类的静态方法getSingleton来获取全局唯一的类实例,如:ResourceGroupManager、LogManager、TextureManager、MeshManager等等,但SceneManager不是;
  3. OGRE对图形系统进行了高度抽象,用户使用基类接口和OGRE交互,程序执行时会自动根据系统配置调用特定子类的实现,如RenderSystem和RenderWindow;
  4. OGRE的场景数据用场景图形(Scene Graph)来组织,其本质是树(Tree),由SceneManager来组织和管理;
  5. 每个Camera通过一个Viewport映射到窗口的一个矩形部分(当然也可以渲染到纹理);
  6. OGRE的资源文件是其一大特色,资源需要特定程序加载到执行期间的程序;
  7. OGRE采用配置文件,本文涉及的有plugins.cfg、ogre.cfg、Ogre.log、resources.cfg文件,你应该清楚它们的作用了;
  8. 场景树的可用性要求场景树不能有循环。

好了,关于OGRE的基本启动过程你应该了解的吧,本文并没涉及WindowEventListener、FrameListener等一些事件的处理,也没有涉及鼠标键盘输入,甚至,“启动代码”运行起来后关闭窗口都不能结束程序,这些留到以后再讲吧。

参考文献:

OGRE WIKI Basic Tutorial 6: The Ogre Startup Sequence

OGRE WIKI: Resources and ResourceManagers

Ogre 3D 1.7 Beginner's Guide (Felix Kerger, 2010)

OGRE API Reference(OGRE SDK下载包中有离线版本)

OGRE 1.9 的第一个程序(OGRE HelloWorld程序)

OGRE启动过程详解(OGRE HelloWorld程序原理解析)的更多相关文章

  1. Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)

    启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...

  2. Linux启动过程详解

    Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...

  3. Android 核心分析 之八Android 启动过程详解

    Android 启动过程详解 Android从Linux系统启动有4个步骤: (1) init进程启动 (2) Native服务启动 (3) System Server,Android服务启动 (4) ...

  4. 【STM32H7教程】第13章 STM32H7启动过程详解

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第13章       STM32H7启动过程详解 本章教 ...

  5. (转)Linux 开机引导和启动过程详解

    Linux 开机引导和启动过程详解 编译自:https://opensource.com/article/17/2/linux-boot-and-startup作者: David Both 原创:LC ...

  6. fabric网络环境启动过程详解

    这篇文章对fabric的网络环境启动过程进行讲解,也就是我们上节讲到的启动测试fabric网络环境时运行network_setup.sh这个文件的执行流程 fabric网络环境启动过程详解 上一节我们 ...

  7. linux 开机启动过程详解

    Linux开机执行内核后会启动init进程,该进程根据runlevel(如x)执行/etc/rcx.d/下的程序,其下的程序是符号链接,真正的程序放在/etc/init.d/下.开机启动的程序(服务等 ...

  8. 转-Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)

    http://blog.chinaunix.net/space.php?uid=10167808&do=blog&id=26042   1)BIOS自检2)启动Grub/Lilo3)加 ...

  9. [转载] Linux启动过程详解-《别怕Linux编程》之八

    本原创文章属于<Linux大棚>博客,博客地址为http://roclinux.cn.文章作者为rocrocket.为了防止某些网站的恶性转载,特在每篇文章前加入此信息,还望读者体谅. = ...

随机推荐

  1. Linux 关机命令

    正确的关机流程是:sync –> shutdown/reboot/halt/poweroff sync 将数据由内存同步到硬盘中. shutdown 关机指令.例如你可以运行如下命令关机: sh ...

  2. 伪静态重写模块rewrite.dll及httpd.ini文件参考下载

    伪静态重写模块rewrite.dll及httpd.ini文件参考下载 http://www.ledaokj.com/download/rewrite.rar 服务器端开启伪静态,可以查看以下文章< ...

  3. CALayer 2 详解 -----转自李明杰

    CALayer2-创建新的层   本文目录 一.添加一个简单的图层 二.添加一个显示图片的图层 三.为什么CALayer中使用CGColorRef和CGImageRef这2种数据类型,而不用UICol ...

  4. 设计模式(2)--单例模式(Singleton Pattern)

    概述 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称):当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的 ...

  5. hypermesh2flac3d

    hypermesh2ansys2flac3d 目的: 将hypermesh中划分的网格输出到flac3d中.过程是hypermesh12.0-ansys13.0-flac3d3.0. 视频教程详见:h ...

  6. Dojo的subscribe和publish的简单使用

    问题描述: 假设在你的页面有一个对话框,对话框中包含FilteringSelect这样的需要store的控件,在打开对话框时需要对这个控件做两件事情,一是给该控件的store填充数据,二是要给该控件设 ...

  7. Windows 8.1 with Update MSDN 简体/英文/繁体

    PS:请不要使用离线下载,以免镜像损坏! 1.CN: 文件名:cn_windows_8.1_enterprise_with_update_x64_dvd_4048578.isoSHA1:2D9BFE9 ...

  8. css2----兼容----ie67的3像素bug

    发生条件:当浮动元素和非浮动元素相邻 时候,ie67下,两个元素就会多出3像素的间隔,其实是浮动元素产生的margin值 解决办法:1:让没有浮动的元素也浮动: 2:让浮动元素产生margin-*:- ...

  9. 排序系列 之 折半插入排序算法 —— Java实现

    基本思想: 折半插入算法是对直接插入排序算法的改进,排序原理同直接插入算法: 把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素:排序过程即每次从无序表中 ...

  10. Bootstrap 3 模态框播放视频

    Bootstrap 3 模态框播放视频 I'm trying to use the Modal feature from Bootstrap 3 to show my Youtube video. I ...