OGRE启动过程详解(OGRE HelloWorld程序原理解析)
本文介绍 OGRE 3D 1.9 程序的启动过程,即从程序启动到3D图形呈现,背后有哪些OGRE相关的代码被执行。会涉及的OGRE类包括:
- Root
- RenderSystem
- RenderWindow
- ResourceGroupManager
- LogManager
- Viewport
- SceneManager
- Camera
- SceneNode
- Entity
- 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:
- Create the Root object.
- Define the resources that Ogre will use.
- Choose and set up the RenderSystem (that is, DirectX, OpenGL, etc).
- Create the RenderWindow (the window which Ogre resides in).
- Initialise the resources that you are going to use.
- Create a scene using those resources.
- Set up any third party libraries and plugins.
- Create any number of frame listeners.
- 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的功能总结为两个方面:
- 管理Camera, SceneNode, Entity, Light等场景中的对象,作为Factory提供create方法如createEntity(), createLight()(也负责释放它们);
- 管理场景图形,包括维护场景树的可用性,计算节点的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,要使用一个程序外定义(即脚本定义)的资源,需要:
- 用ResourceGroupManager::addResourceLocation方法添加资源文件所在目录;
- 用ResourceGroupManager::declareResource方法声明(declare)资源,可选的;
- 用ResourceGroupManager::initialiseResourceGroup或ResourceGroupManager::initialiseAllResourceGroups方法初始化所添加目录中的资源文件脚本;
- 默认下,资源文件的数据直到该资源使用时才被加载,如一个纹理的图片并不是在纹理定义时加载,而是在纹理被首次使用时加载至内存,也可以手动调用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. 总结
本文要点总结如下:
- OGRE程序总是从创建Root实例开始;
- OGRE的很多xxxManager类使用了Singleton设计模式,可以调用类的静态方法getSingleton来获取全局唯一的类实例,如:ResourceGroupManager、LogManager、TextureManager、MeshManager等等,但SceneManager不是;
- OGRE对图形系统进行了高度抽象,用户使用基类接口和OGRE交互,程序执行时会自动根据系统配置调用特定子类的实现,如RenderSystem和RenderWindow;
- OGRE的场景数据用场景图形(Scene Graph)来组织,其本质是树(Tree),由SceneManager来组织和管理;
- 每个Camera通过一个Viewport映射到窗口的一个矩形部分(当然也可以渲染到纹理);
- OGRE的资源文件是其一大特色,资源需要特定程序加载到执行期间的程序;
- OGRE采用配置文件,本文涉及的有plugins.cfg、ogre.cfg、Ogre.log、resources.cfg文件,你应该清楚它们的作用了;
- 场景树的可用性要求场景树不能有循环。
好了,关于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程序原理解析)的更多相关文章
- Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)
启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...
- Linux启动过程详解
Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...
- Android 核心分析 之八Android 启动过程详解
Android 启动过程详解 Android从Linux系统启动有4个步骤: (1) init进程启动 (2) Native服务启动 (3) System Server,Android服务启动 (4) ...
- 【STM32H7教程】第13章 STM32H7启动过程详解
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第13章 STM32H7启动过程详解 本章教 ...
- (转)Linux 开机引导和启动过程详解
Linux 开机引导和启动过程详解 编译自:https://opensource.com/article/17/2/linux-boot-and-startup作者: David Both 原创:LC ...
- fabric网络环境启动过程详解
这篇文章对fabric的网络环境启动过程进行讲解,也就是我们上节讲到的启动测试fabric网络环境时运行network_setup.sh这个文件的执行流程 fabric网络环境启动过程详解 上一节我们 ...
- linux 开机启动过程详解
Linux开机执行内核后会启动init进程,该进程根据runlevel(如x)执行/etc/rcx.d/下的程序,其下的程序是符号链接,真正的程序放在/etc/init.d/下.开机启动的程序(服务等 ...
- 转-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)加 ...
- [转载] Linux启动过程详解-《别怕Linux编程》之八
本原创文章属于<Linux大棚>博客,博客地址为http://roclinux.cn.文章作者为rocrocket.为了防止某些网站的恶性转载,特在每篇文章前加入此信息,还望读者体谅. = ...
随机推荐
- Linux 关机命令
正确的关机流程是:sync –> shutdown/reboot/halt/poweroff sync 将数据由内存同步到硬盘中. shutdown 关机指令.例如你可以运行如下命令关机: sh ...
- 伪静态重写模块rewrite.dll及httpd.ini文件参考下载
伪静态重写模块rewrite.dll及httpd.ini文件参考下载 http://www.ledaokj.com/download/rewrite.rar 服务器端开启伪静态,可以查看以下文章< ...
- CALayer 2 详解 -----转自李明杰
CALayer2-创建新的层 本文目录 一.添加一个简单的图层 二.添加一个显示图片的图层 三.为什么CALayer中使用CGColorRef和CGImageRef这2种数据类型,而不用UICol ...
- 设计模式(2)--单例模式(Singleton Pattern)
概述 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称):当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的 ...
- hypermesh2flac3d
hypermesh2ansys2flac3d 目的: 将hypermesh中划分的网格输出到flac3d中.过程是hypermesh12.0-ansys13.0-flac3d3.0. 视频教程详见:h ...
- Dojo的subscribe和publish的简单使用
问题描述: 假设在你的页面有一个对话框,对话框中包含FilteringSelect这样的需要store的控件,在打开对话框时需要对这个控件做两件事情,一是给该控件的store填充数据,二是要给该控件设 ...
- Windows 8.1 with Update MSDN 简体/英文/繁体
PS:请不要使用离线下载,以免镜像损坏! 1.CN: 文件名:cn_windows_8.1_enterprise_with_update_x64_dvd_4048578.isoSHA1:2D9BFE9 ...
- css2----兼容----ie67的3像素bug
发生条件:当浮动元素和非浮动元素相邻 时候,ie67下,两个元素就会多出3像素的间隔,其实是浮动元素产生的margin值 解决办法:1:让没有浮动的元素也浮动: 2:让浮动元素产生margin-*:- ...
- 排序系列 之 折半插入排序算法 —— Java实现
基本思想: 折半插入算法是对直接插入排序算法的改进,排序原理同直接插入算法: 把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素:排序过程即每次从无序表中 ...
- Bootstrap 3 模态框播放视频
Bootstrap 3 模态框播放视频 I'm trying to use the Modal feature from Bootstrap 3 to show my Youtube video. I ...