现在的位置: 首页 > 综合 > 正文

Ogre 场景结构体系

2018年02月16日 ⁄ 综合 ⁄ 共 11159字 ⁄ 字号 评论关闭

Mage小组 著
Email: norman_chen@163.com 
renwind@163.com
QQ: 18725262
http://www.173d8.com
http://blog.csdn.net/pizi0475

场景结构体系
 3D场景是一个3D程序的实质性的内容。如果3D物体及各种光被胡乱堆砌在场景里,那么场景也就不是场景而是垃圾场了。场景需要有序的组织,这是任何一个3D引擎都必须解决的问题。
 OGRE为了解决场景管理的问题提出了几个重要的概念并将它们实现为引擎中的类:
 Entity:场景元素,Mesh(模型)在场景中的实例。
 Light:场景元素,现实世界的光在场景中的实例。
 Camera:场景元素,现实世界的观察者在场景中的实例。
 SceneNode:抽象的场景管理单元。
 注意到SceneNode和其它几个概念不一样,其它几个概念都是现实事物的程序抽象,而SceneNode在现实世界中并不存在,它是为了组织场景而提出的抽象概念。
OGRE的思想是这样的:将现实世界中的场景划分成抽象的不同空间,区域中还可以划分成不同的小空间,每个空间由一个SceneNode对象来管理,SceneNode将处理移动、旋转和缩放等与空间相关的行为。在每个SceneNode上可以挂接各种场景元素(如:Entity 、Light、Camera等),场景元素本身并不负责与空间位置相关的行为,全部交给SceneNode来做(Light 和Camera类也保存自己的位置,但很多时候也都挂接到SceneNode上来处理,见后详述)。 OGRE将大量SceneNode按照空间的划分层次组织成树状结构,从而完成对整个场景的有序组织。场景结构图如下:
 
场景结构图
根节点的位置往往在场景的中心。每个节点对象都用数据成员保存相对于父节点的相对位移和相对旋转量(这一点很重要)。节点类提供节点的移动和旋转函数。这样当节点1发生移动和旋转的时候,其全部子节点及它们挂接的场景元素也将被动地移动和旋转。想象一下5个人和一条狗坐在汽车里的情况,当车转弯的时候,5个人和狗的相对位置虽然没有变,但他们都随着汽车转动了!这就是用节点树组织场景的巨大威力。再想一下,当缩小并旋转根节点的时候,整个世界都缩小和旋转起来。
OGRE提供一个SceneManager类来负责整个场景节点树的维护。SceneManager还负责Entity、Light和Camera的管理和维护。所以我们在对场景进行操作的时候更多的是与SceneManager打交道。
Entity类
 Mesh模型在场景中的实例。
重要函数
void setMaterialName(const String& name);
参数为材质脚本文件(还记得OGRE运行环境中扩展名为.material的脚本文件吗?)中定义的某个材质名称。将该材质指定为本Entity的材质。

注意:我们几乎不在程序中直接创建一个Entity,对场景中的Entity的创建和管理一般是由SceneManager来完成的,所以我们都通过调用SceneManager的createEntity函数来创建Entity。
SceneNode类
 场景节点类。
重要函数
从当前节点对象创建子节点。系统自动为子节点命名。参数是子节点相对于当前节点的位移和旋转量。
SceneNode* createChild(const Vector3& translate = Vector3::ZERO, const Quaternion& rotate = Quaternion::IDENTITY);
从当前节点对象创建子节点。第一个参数是该子节点的名称,其余参数是子节点相对于当前节点的位移和旋转量。
SceneNode* createChild(const String& name, const Vector3& translate = Vector3::ZERO, const Quaternion& rotate = Quaternion::IDENTITY);
按索引号获取子节点
SceneNode* getChild(unsigned short index) const;
按名称获取子节点
SceneNode* getChild(const String& name) const;
脱钩指定索引号的子节点
SceneNode* removeChild(unsigned short index);
脱钩指定名称的子节点
SceneNode* removeChild(const String& name);
在当前节点下挂接MovableObject。MovableObject是Entity、Light、Camera类的基类。
void attachObject(MovableObject* obj);
在当前节点下挂接光,在内部委托attachObject完成。
void attachLight(Light* l);
在当前节点下挂接摄象机,在内部委托attachObject完成。
void attachCamera(Camera* ent);
脱钩指定索引号的MovableObject
MovableObject* detachObject(unsigned short index);
脱钩指定名称的MovableObject
MovableObject* detachObject(const String& name);
还有几个函数没写完,从Node类继承而来的大量函数,如setScle( )等
SceneManager类
 场景管理类,是场景管理的核心类。
 SceneManager类内部保存std::map类型的CameraList、LightList、EntityList和SceneNodeList,并提供各种方法对它们进行管理和维护。SceneNode自身的设计使SceneNodeList实际上是一个树状结构。在每个SceneNode中引用CameraList、LightList、EntityList中的元素实现了场景元素在SceneNode上的挂接。SceneManager类还直接保存场景节点树的根节点指针以提供该树的访问入口。
重要函数
缺省构造函数
SceneManager();
缺省析构函数
virtual ~SceneManager();

摄像机相关函数
virtual Camera* createCamera(const String& name);
virtual Camera* getCamera(const String& name);
virtual void removeCamera(Camera *cam);
virtual void removeCamera(const String& name);
virtual void removeAllCameras(void);

光相关函数
virtual Light* createLight(const String& name);
virtual Light* getLight(const String& name);
virtual void removeLight(const String& name);
virtual void removeLight(Light* light);
virtual void removeAllLights(void);

材质相关函数
virtual Material* createMaterial(const String& name);
返回一个指向缺省材质设定的指针,用这个指针可以改变材质的设定
缺省设定如下:
                - ambient = ColourValue::White
                - diffuse = ColourValue::White
                - specular = ColourValue::Black
                - emmissive = ColourValue::Black
                - shininess = 0
                - No texture layers (& hence no textures)
                - SourceBlendFactor = SBF_ONE
                - DestBlendFactor = SBF_ZERO (no blend, replace with new
                  colour)
                - Depth buffer checking on
                - Depth buffer writing on
                - Depth buffer comparison function = CMPF_LESS_EQUAL
                - Culling mode = CULL_CLOCKWISE
                - Ambient lighting = ColourValue(0.5, 0.5, 0.5) (mid-grey)
                - Dynamic lighting enabled
                - Gourad shading mode
                - Bilinear texture filtering
virtual Material* getDefaultMaterialSettings(void);
virtual void addMaterial(const Material& mat);
virtual Material* getMaterial(const String& name);
virtual Material* getMaterial(int handle);

场景节点相关函数
virtual SceneNode* createSceneNode(void);
virtual SceneNode* createSceneNode(const String& name);
virtual void destroySceneNode(const String& name);
取得根节点,在整个场景中只有一个根节点
virtual SceneNode* getRootSceneNode(void) const;
virtual SceneNode* getSceneNode(const String& name) const;

用Mesh创建一个实体
virtual Entity* createEntity(const String& entityName, const String& meshName);
用预先提供的shape创建实体
PrefabType是一个枚举,提供预制的shape,不需要模型
enum PrefabType {
     PT_PLANE
};
virtual Entity* createEntity(const String& entityName, PrefabType ptype);
virtual Entity* getEntity(const String& name);
销毁实体,注意实体必须没有被SceneNode所attach,如果你不确定是否还有实体被attach,使用SceneManager::clearScene()
virtual void removeEntity(Entity* ent);
virtual void removeEntity(const String& name);
virtual void removeAllEntities(void);

清空场景,包括SceneNodes,Cameras,Entities,Lights
virtual void clearScene(void);

设定环境光,缺省的环境光是ColourValue::Black
void setAmbientLight(ColourValue colour);
ColourValue getAmbientLight(void);

设定世界坐标系
virtual void setWorldGeometry(const String& filename);

从SceneManager处取得一个推荐的viewpoint,通常这个方法返回原点,除非通过SceneManager::setWorldGeometry设定了坐标系或者世界坐标系推荐了起始点。如果有多于一个的推荐点,将返回第一个,如果参数random为真,将随机的返回一个。
virtual ViewPoint getSuggestedViewpoint(bool random = false);

为SceneManager设定一个特殊实现的选项
virtual bool setOption( const String& strKey, const void* pValue ) { return false; }
virtual bool getOption( const String& strKey, void* pDestValue ) { return false; }
确认SceneManager是否有一个特殊实现的选项
virtual bool hasOption( const String& strKey ) { return false; }
virtual bool getOptionValues( const String& strKey, std::list<SDDataChunk>& refValueList ) { return false; }
virtual bool getOptionKeys( std::list<String>& refKeys ) { return false; }

Enables / Disables一个天空面
virtual void setSkyPlane(
            bool enable,
            const Plane& plane,
const String& materialName,
Real scale = 1000,
            Real tiling = 10, bool drawFirst = true, Real bow = 0 );

Enables / Disables一个天空盒
virtual void setSkyBox(
            bool enable,
const String& materialName,
Real distance = 5000,
            bool drawFirst = true,
const Quaternion& orientation = Quaternion::IDENTITY );

Enables / Disables一个天空穹顶
        virtual void setSkyDome(
            bool enable,
const String& materialName,
Real curvature = 10,
            Real tiling = 8, Real distance = 4000, bool drawFirst = true,
            const Quaternion& orientation = Quaternion::IDENTITY);

雾相关函数
设置场景用的雾方式
        void setFog(
            FogMode mode = FOG_NONE,
ColourValue colour = ColourValue::White,
Real expDensity = 0.001,
Real linearStart = 0.0,
Real linearEnd = 1.0);

virtual FogMode getFogMode(void) const;
virtual const ColourValue& getFogColour(void) const;
virtual Real getFogStart(void) const;
virtual Real getFogEnd(void) const;
virtual Real getFogDensity(void) const;

公告板相关函数
virtual BillboardSet* createBillboardSet(const String& name, unsigned int poolSize = 20);
virtual BillboardSet* getBillboardSet(const String& name);
virtual void removeBillboardSet(BillboardSet* set);
virtual void removeBillboardSet(const String& name);

告知SceneManager是否渲染该节点
virtual void setDisplaySceneNodes(bool display);

动画相关函数
virtual Animation* createAnimation(const String& name, Real length);
virtual Animation* getAnimation(const String& name) const;
virtual void destroyAnimation(const String& name);
virtual void destroyAllAnimations(void);
virtual AnimationState* createAnimationState(const String& animName);
virtual AnimationState* getAnimationState(const String& animName);
virtual void destroyAnimationState(const String& name);
virtual void destroyAllAnimationStates(void);

手动渲染方法,高级用户使用
virtual void manualRender(
RenderOperation* rend, Material* mat, Viewport* vp,
            const Matrix4& worldMatrix, const Matrix4& viewMatrix, const Matrix4& projMatrix) ;

Overlay相关函数
virtual Overlay* createOverlay(const String& name, ushort zorder = 100);
virtual Overlay* getOverlay(const String& name);
virtual void destroyOverlay(const String& name);
virtual void destroyAllOverlays(void);

注册一个新的渲染队列监听器,当渲染队列被处理时将被更新
virtual void addRenderQueueListener(RenderQueueListener* newListener);
virtual void removeRenderQueueListener(RenderQueueListener* delListener);

场景管理器的使用举例
 这个例子演示场景的组织和管理使用方法。程序运行时你会看到2个食人魔饶一条龙转动,同时它们自己也在自转。
思路
首先通过场景管理器在场景中建立如下的场景结构:
 

 我们已经知道场景元素Entity仅仅是挂接到场景节点上,与位移、旋转和缩放有关的任务都由节点负责。那么龙和食人魔会出现在什么位置也就只与它们所在的节点有关。在OGRE系统中,根节点的缺省位置在场景中心(也就是0,0,0位置)。通过父场景节点建立子场景节点的时候,子节点相对于父节点的位移为0,那么pNodeDragon和pNodeHeadGroup节点的位置也在场景中心,为了不让龙和2个食人魔挤在一起,我们把pNodeHead1和pNodeHead2节点分别相对于pNodeHeadGroup节点在X轴上位移正负200个单位长度。这样设置的结果将是龙出现在场景中心,而2个食人魔分别在龙的两边。
 接下来是物体的运动,要使物体运动起来就必须在渲染过程中更改物体的位置和旋转量,这一点怎么办呢?不要忘了OGRE中有FrameLisener帧监听器的概念。FrameLisener类中的frameStarted方法会在每一帧渲染之前被调用。OGRE应用框架中的ExampleFrameListener已经实现了frameStarted方法,其主要工作是接受用户输入,控制摄象机。我们当然可以继承ExampleFrameListener类,扩充frameStarted方法让它控制物体的运动。
 通过让pNodeHeadGroup每一帧都旋转一定的角度,可以带动其下的节点和节点挂接物体都旋转同样的角度,这样公转就实现了。用同样的原理,每一帧分别旋转pNodeHead1和pNodeHead2节点一定角度可以它们的自转。
 

部分代码
// Sample_SceneManager.h

// myFrameListener类
class myFrameListener : public ExampleFrameListener
{
protected:
    // 对场景节点的引用,便于对运动的控制。
SceneNode *pNodeHeadGroup,*pNodeHead1,*pNodeHead2;
public:
    myFrameListener(RenderWindow* win, Camera* cam, SceneNode* pNode1,SceneNode* pNode2,SceneNode* pNode3)
        : ExampleFrameListener(win, cam)
    {
        pNodeHeadGroup = pNode1;
  pNodeHead1 = pNode2;
  pNodeHead2 = pNode3;
    }

    // 重新实现frameStarted函数,在这里实现对场景物体的运动控制
bool frameStarted(const FrameEvent& evt)
    {

        // 旋转场景节点,当然也就旋转了它们下挂接的物体
        pNodeHeadGroup->yaw(evt.timeSinceLastFrame * 30);
  pNodeHead1->pitch(evt.timeSinceLastFrame * -360);
  pNodeHead2->roll(evt.timeSinceLastFrame * 1440);

        //  不要忘了,调用基类的frameStarted函数,以实现用户输入控制(摄象机漫游控制)。
        return ExampleFrameListener::frameStarted(evt);

    }
};

// myApp类
class myApp :public ExampleApplication
{
public:
 myApp(){}
protected:
 SceneNode *pNodeDragon,*pNodeHeadGroup,*pNodeHead1,*pNodeHead2;
 // 实现createScene函数,建立场景
 void createScene(void)
 {
  // 设置环境光
        mSceneMgr->setAmbientLight(ColourValue(1, 1, 1));
  
  // 在根节点下创建pNodeDragon节点
  pNodeDragon = mSceneMgr->getRootSceneNode()->createChild();
  // 创建“龙”Entity
Entity *pEntityDragon = mSceneMgr->createEntity("Dragon", "Dragon.mesh");
        // 将龙Entity挂接到pNodeDragon节点下
pNodeDragon->attachObject(pEntityDragon);
  // “龙”的模型太大了,缩小它所在的节点,该模型在显示时也就缩小了。
pNodeDragon->setScale(0.5,0.5,0.5);

  // 创建pNodeHeadGroup场景节点
  pNodeHeadGroup = mSceneMgr->getRootSceneNode()->createChild();
        // 在pNodeHeadGroup场景节点下创建pNodeHead1节点
  pNodeHead1 = pNodeHeadGroup->createChild();
        // 使pNodeHead1节点偏离父节点
pNodeHead1->translate(200,0,0);
  // 创建“食人魔”Entity。
  Entity *pEntityHead1 = mSceneMgr->createEntity("head1", "ogrehead.mesh");
  // 挂接该“食人魔”Entity到pNodeHead1
pNodeHead1->attachObject(pEntityHead1);
  
  // 在pNodeHeadGroup节点下创建pNodeHead2节点
  pNodeHead2 = pNodeHeadGroup->createChild();
        // 使pNodeHead2节点偏离父节点
pNodeHead2->translate(-200,0,0);
  // 由前面创造的“食人魔”Entity克隆出另外一个“食人魔”Entity
  Entity *pEntityHead2 = pEntityHead1->clone("head2");
  // 将新的“食人魔”Entity挂接到pNodeHead2节点下
pNodeHead2->attachObject(pEntityHead2);
 
 }

 // 重新实现createFrameListener函数,因为基类ExampleApplication中的该函数创建的是ExampleFrameListener对象,而我们这里要使用myFrameListener对象。
    void createFrameListener(void)
    {
        mFrameListener= new myFrameListener(mWindow, mCamera, pNodeHeadGroup,pNodeHead1,pNodeHead2);
        mRoot->addFrameListener(mFrameListener);
    }
 
};

 注:这里没有演示摄象机和光挂接到场景节点中的情况。这个示例中的的摄象机是在ExampleApplication类中定义的独立于场景节点树的摄象机。摄象机与Entity不一样,Entity是完全不具有调整自身位置的能力,所以只能将自己挂接到场景节点上,通过对场景节点的位置调整来达到调整自身位置的目的,但摄象机有这个能力,所以可以独立控制。当然将摄象机挂接到场景节点上可以带来很多方便。如果要将摄象机和光挂接到场景节点上,方法与挂接Entity没什么区别,都是通过节点的attachObject函数来完成。attachObject函数的参数是MovableObject类型的,而MovableObject类是Entity、Camera和Light类的基类,所以attachObject函数可以接收Entity、Camera和Light类型的参数。

抱歉!评论已关闭.