OGRE场景管理

Ogre 场景管理

每个3D引擎都会用scene graph 来组织它的可渲染对象。scene graph 总是会为了更快地搜索与查询做
优化,提供给用户查找目标对象附近特定对象的功能,允许查找,排序,剔除多边形,以实现更高效的渲染。偶尔,scene graph也用于碰撞检测。有时,一个单独的scene graph可被用于程序中的所有子系统,包括
音效与物理。
Ogre使用插件机制来实现场景管理功能。ScenceManager只是接口,他可以有很多具体的实现。Ogre允许
在同一时刻同一场景中使用多个Scene Manager,这样在不同的场景类型切换时带来好处。

场景管理器的责任
1,创建,放置场景中的可移动对象,light,camera,并可以在图形遍历中有效地访问它们。
2,加载,装配world geometry(它通常很大,向四处延伸,不可移动)
3,完成场景查询,例如可以回答这样的问题:在世界空间的特定点画一个球体,它会包含哪些对象?
4,剔除不可见对象,把可见对象放入渲染队列进行渲染
5,从当前可渲染的透视图中组织,拣选各方向光照
6,设置,渲染场景中的所有阴影
7 设置,渲染场景中的其他对象(如背景,天空盒)
8 传递组织良好的内容到渲染系统进行渲染

场景管理器类型

以分析源码的方式讨论一下插件的加载机制与特定场景管理器是如何进行运用的。
上一章提到了以手工的方式初始化ogre,包括手工加载场景管理器:
root->loadPlugin("Plugin_OctreeSceneManager");
其实所谓的自动方式下,Root:Root()中也会间接调用到loadPlugin()方法,这已在前面的笔记
(配置文件Plugins.cfg )中提到过。既然特定管理器以插件的形式(dll文件)给出,下面先看如何
加载dll. 进入源码:标号表明执行顺序。
void Root::loadPlugin(const String& pluginName)
 {
 // Load plugin library
    DynLib* lib = DynLibManager::getSingleton().load( pluginName ); //(1)
  // Store for later unload
 mPluginLibs.push_back(lib); //(4)
 // Call startup function
    DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin"); //(5)
 // This must call installPlugin
 pFunc(); //(6)

 }
DynLib* DynLibManager::load( const String& filename)  //(2)
{
        DynLib* pLib = new DynLib(filename);
 pLib->load();       
        mLibList[filename] = pLib;
 return pLib;
}
void DynLib::load() //(3)
{
m_hInst = (DYNLIB_HANDLE)DYNLIB_LOAD( name.c_str() );
}
(3)中的宏定义如下:
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#    define DYNLIB_HANDLE hInstance
#    define DYNLIB_LOAD( a ) LoadLibrary( a )
到此,DLL被加载到内存,第(4)步,mPluginLibs是个STL容器,它存放动态库指针。
(5)步,进入源码可以看到
void* DynLib::getSymbol( const String& strName ) const throw()
    {
        return (void*)DYNLIB_GETSYM( m_hInst, strName.c_str() );
    }
其中宏定义:define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b ),很显然,它取得一个名为
dllStartPlugin的函数指针:不防再看看宏定义: typedef void (*DLL_START_PLUGIN)(void);
说明DLL_START_PLUGIN为参数为空,返回值为空的函数指针。
每个注册到ogre的dll都实现了这个约定函数。对于我们当前讨论的场景管理器Plugin_OctreeSceneManager
来讲,我们可以找到其相应的定义:它在第(6)步中执行

OctreePlugin* octreePlugin;
extern "C" void _OgreOctreePluginExport dllStartPlugin( void )
{
    // Create new scene manager
    octreePlugin = new OctreePlugin(); //(6-1)

    // Register
    Root::getSingleton().installPlugin(octreePlugin); //(6-2)

}
程序执行到(6-1)步,new 来一个OctreePlugin,我们看一下它的定义:
class OctreePlugin : public Plugin
{
 public:
  OctreePlugin();
  const String& getName() const;
  void install();
   void initialise();
   void shutdown();
   void uninstall();
 protected:
  OctreeSceneManagerFactory* mOctreeSMFactory;
  TerrainSceneManagerFactory* mTerrainSMFactory;
  TerrainPageSourceListenerManager* mTerrainPSListenerManager;

};

我们会注意到它包含两个工厂类指针:OctreeSceneManagerFactory,TerrainSceneManagerFactory
他们就是用来生产特定ScenManager的,稍后讨论。先看(6-2)步:
void Root::installPlugin(Plugin* plugin)
{
 mPlugins.push_back(plugin);  //(6-2-1)
 plugin->install();             //(6-2-2)
 // if rendersystem is already initialised, call rendersystem init too
 if (mIsInitialised)
 {
 plugin->initialise();         //(6-2-3)
 }
}
//(6-2-1)步把插件放到容器中。看看(6-2-2)做了什么:
void OctreePlugin::install()
 {
  // Create objects
  mOctreeSMFactory = new OctreeSceneManagerFactory();
  mTerrainSMFactory = new TerrainSceneManagerFactory();
  mTerrainPSListenerManager = new TerrainPageSourceListenerManager();

 }
呵,刚才还说两个工厂类指针,现在把两个工厂建起来,以后可以用来生产东西了。
继续看看(6-2-3):
void OctreePlugin::initialise()
 {
  // Register
  Root::getSingleton().addSceneManagerFactory(mOctreeSMFactory);
  Root::getSingleton().addSceneManagerFactory(mTerrainSMFactory);
 }
哦,把这两个工厂注册到Root中,让Root可以使用它们。啊这句话有点问题,Root只是间接的用到,
直接雇主是 SceneManagerEnumerator* mSceneManagerEnum;它被包含在Root中。于是我们可以看到
void Root::addSceneManagerFactory(SceneManagerFactory* fact) //(6-2-3-1)
 {
  mSceneManagerEnum->addFactory(fact);
 }

工厂已经有了,我们如何利用这个工厂生产出我们想到的东西(SceneManager)呢?
回忆上一章的手工初始化过程中,我们一般用以下语句来创建SceneManager:
  SceneManager *sceneMgr = root->createSceneManager(ST_GENERIC); //(A)
也可以这样用
  sceneMgr = ogre->createSceneManager("OctreeSceneManager"); //(B)
每个工厂类都用一个字符串表示其类型。上面说的两个工厂分别使用的字符串为:“TerrainSceneManager”,"OctreeSceneManager"
(A)语句肯定会调用工厂类的方法来产生实际的SceneManager,下面看源码验证一下:
SceneManager* Root::createSceneManager(const String& typeName,
  const String& instanceName)
 {
  return mSceneManagerEnum->createSceneManager(typeName, instanceName);
 }
继续挖:
SceneManager* SceneManagerEnumerator::createSceneManager(
  const String& typeName, const String& instanceName)
 {
  SceneManager* inst = 0;
  for(Factories::iterator i = mFactories.begin(); i != mFactories.end(); ++i)
  {
   if ((*i)->getMetaData().typeName == typeName)
   {
    if (instanceName.empty())
    {
     // generate a name
     StringUtil::StrStreamType s;
     s << "SceneManagerInstance" << ++mInstanceCreateCount;
     inst = (*i)->createInstance(s.str());
    }
    else
    {
     inst = (*i)->createInstance(instanceName);
    }
    break;
   }
  }
 }
上述代码很简单:因为执行(6-2-3-1)已经过实际工厂类实例进行了注册。于是遍历每个工厂实例,
找到类型相符的。找到之后,如果没有传场景管理器实例的名字,就起个名字。然后用这个名字
生产出一个实例来。CreateInstance没干什么,new 呗。
SceneManager* OctreeSceneManagerFactory::createInstance(
 const String& instanceName)
{
 return new OctreeSceneManager(instanceName);
}
就是这样。OctreeSceneManager and TerrainSceneManager 的功能都是由一个DLL提供的。它们的关系是:class _OgreOctreePluginExport TerrainSceneManager : public OctreeSceneManager,按照一般的类关系逻辑,
我们知道派生类一般功能都比父类强大,父类应用于一般场景,子类针对特定场景。这个逻辑在这里是对的。本来是写读书笔记,跑题了,打住。

 

场景对象创建
场景中的所有对象,包括可移动与不可移动的:lights, cameras, entities,particle system,
billboards, skyboxes, static geometry , world geometry.都由场景管理器来创建。场景中的
任何东西都由场景管理器来管理。任何通过场景管理器得到的东西,都必须由场景管理器来销毁。
用户不能delete通过由场景管理器得到的指针。
场景结点只有一个父结点,可能有多个子结点。可以随意的attach 和 detach 这些场景中的结点。
在明确告诉场景管理器销毁这些结点前,它们总是存在。如果不想渲染场景中的某些结点上的内容,
我们可以很方便地对她们进行detach.场景中总是存在一个Root结点。可以把多个内容对象attach到
同一个结点上。不能同时把一个实例对象挂到两个场景结点上,一个场景结点也不能有两个父结点。
总是场景结点在执行空间操作(平移,旋转,缩放)而不是实体对象。
场景查询
场景管理器的第二个最常用的功能是进行场景查询。包括射线查询,球查询,绑定盒查询,绑定平面
查询,相交查询。Terrain clamping:在崎岖不平的路上,勇敢地向下发射一束光,不管你告诉我你有
多高,我永远把你踩在脚下..(是不是不太像笔记?^_^)。所有这些查询都是 maskble的,这表明可
以在查询时过滤掉不关心的对象类型。如球查询时,只想看看它包含了多少lights,其他的对象即使包含
在球里也不必返回,实际上根本不用计算。
空间关系与3D变换
world , parent , local世界空间中的变换是相对于全局坐标系的源点(0,0,0),这也是root scene node的位置。因此可以认为世界空间中的变换是相对于Root scene node的。父空间中的变换是相对于场点结点的父结点的,本地空间的变换是相对于物体所挂接的结点的。大多数情况下,我们会在父空间中做平移,在本地空间中做旋转,ogre中的这些操作,都是在上述的这种方式下。
object space
从模型导出的顶点数据与它的源点之间的关系是不变的,当它被挂到场景结点上,这个结点就是认为是
它的源点。建模时与导出时不假设测量单位,以世界单位导出(也就是无单位)。
ogre平移与设置位置不同:平移可以有多个参照点(world,local,parent space),而用setPosition()
时,总是相对于parent-space 坐标系的。
可移动的场景对象
基于资源的对象:最普通的是mesh(陪伴着skeleton),这种类型的对象被资源管理系统管理。
场景管理器不负责实际的装载,它调用Ogreuq资源管理器来完成。
基于四边形的对象:粒子系统,公告板,ribbon trail,overlay,天空盒。它们通常是面向相机的,
使用动态材质。它们的主要资源是脚本,这些脚本定义了如何映射材质,以及它们的生命期(对于粒子系统与ribbon trail来说)。天空盒直接用场景管理器来定义。
skyplane,skydome,skybox
主要的相似点是它们与相机保持一个常量的距离。它们可以在场景中其它对象
之前或是之后渲染。它们使用普通的ogre material,因此纹理动画与其他纹理没有什么不同。它们可以
被场景管理器打开或关闭,与相机的距离也可以设置。
skyplane 是一个平面。用距离和法线定义它与相机的位置关系。可以弯曲,可以分多个段,可对纹理进行多次平铺。skydome由五个平面组成,底部空。使用改变纹理坐标的方式来达到外观上的曲率变化。有一个值用来调节,值越低,曲率越柔和,值越高,越陡峭。skybox 像skydome,但他不能“弯曲”材质坐标。它可以使用立方材质。可使用硬件加速功能达到很好渲染效率。

使用光的限制:单个通道通常最大支持8个灯。使用更多的灯,通过多通道。
光与物体之间的距离,决定光对物体实际的影响。Ogre支持点光源,平等光,聚光灯。
世界几何
当创建基于mesh的景物或关卡,应该分成较小的部分,以便于裁减。Paging Scene Manager提供了这样的工具。
空间分割方案
Ogre是基于硬件加速渲染引擎,因此以最大化几何体batching的方式会比基于实际多边形进行空间分割
效果要好的多。Modern GPUs prefer to render a few large batches of geometry instead of
many small batches.
The more visible geometry you can render in a single batch, the better your application’s
performance is likely to be (within reason, of course; other classic issues such as fillrate and
overdraw can still take over if you just blindly blast polygons at the GPU, batched or not).

静态几何使用注意
静态几何在使用之间必须被建立。
使用相同材质的几何体将被放到同一渲染操作中(batch):different materials still require a separate batch.
努力的方向是,最大化一个调用的三角形数,最小化调用的次数。
不能移动被包含在static geometry中的对象。只有一个世界转换应用到完整的static geometry对象上.
比同样规模的movable geometry占用更多的内存。
假如组中的任何东西在视锥之内,那么组中的所有对象都将被渲染。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值