wild magic3的Scene graph结构和Geometric State更新体系(转载)

来源:互联网 发布:什么叫数据库管理系统 编辑:程序博客网 时间:2024/06/04 20:42

刚看了《3D Game Engine Architecture》第3章“Scene graphs and renderers"的前两节,很精彩,暂且不拿wild magic 3和其他引擎相比较,只是作为读书笔记,记录一下书中的核心内容。

我觉得第3章是此书的核心部分,全部内容就是scene graph的更新和渲染,其中第一节描述了wild magic3中的scene graph架构,第二节重点讲解了scene grapha的Geometirc State 更新体系(updateGS)。

1)wild magic3 scene graphs

包括几个核心类: Spatial, Node, Geometry
其中Spatial是Node和Geometry的父类,Spatial包含了local transform和world transform,以及world bound(世界空间的包围体)。并且Spatial拥有上一层Spatial的指针(parent spatial)。

Node包含一组子节点(注意,子节点使用Spatial指针,而不是Node指针,因为子节点可能是Node也可能是Geometry,或者他们的子类),通过Spatial和Node组成了scene 的树形结构。

而Geometry是有mesh的几何实体,包括索引数组,模型空间的顶点数组,模型空间的法线数组,以及模型空间的包围体,还有模型级的scale值。
同时他也是继承自Spatial的,所以也可以变换,也被放置到scene grapha中,但是在wild magic3中,Geometry只能作为叶子节点(没有子节点了,只有Node有子节点)

从这儿看,Node的作用就是一个group, Node不是实体,实体只能是叶子节点,一般是Node的子节点中有Geometry。
另外,对于共享模型数据,wild magic3是在low level实现的,即Geometry的子类可以共享模型数据。

2)wild magic3中的transform
Transformation 这个类,包含一个3X3 rotation matrix, 一个 vector3f translate,和一个vector3f表达的non-uniform scale。他为了减少计算量,没有像irrlicht那样直接用一个4X4 matrix。Transformation类中的Product(const Transformation& rkA, const Transformation& rkB)方法相当于矩阵相乘,用于在scene grapha更新时从上到下更新节点的世界变换。

3)wild magic3的geometric updates

为了方便看,把相关代码列到一起

class Spatial: public Object
{
    public:
        Transformation Local, World;
        bool WorldIsCurrent;

        BoundingVolumePtr WorldBound;
        bool WorldBoundIsCurrent;

        void UpdateGS(double dAppTime=-Mathd::MAX_REAL, bool bInitiator=true)
        {
            UpdateWorldData(dAppTime);
            UpdateWorldBound();
            if(bInitiator)
            {
                PropagateBoundToRoot();
            }
        }

        void UpdateBS()
        {
            UpdateWorldBound();
            PropagateBoundToRoot();
        }

    protected:
        virtual void UpdateWorldData(double dAppTime)
        {
            UpdateControllers(dAppTime);//control可能直接修改local或world transform
            if(!WorldIsCurrent)
            {
                if(m_pkParent)
                    World.Product(m_pkParent->World, Local);
                else
                    World = Local;
            }
        }

        virtual void UpdateWorldBound() = 0;

        void PropagateBoundToRoot()
        {
            if(m_pkParent)
            {
                m_pkParent->UpdateWorldBound();
                m_pkParent->PropagateBoundToRoot();
            }
        }
};

class Geometry: public Spatial
{
    public:
        //省略其他数据,如indices, vertices, normals..
        BoundingVolumePtr ModelBound;

        void UpdateMS();
    protected:
        virtual void UpdateModelBound();
        virtual void UpdateModelNormals();
        virtual void UpdateWorldBound()
        {
            ModelBound->TransformBy(World, WorldBound);
        }
};

class Node: public Spatial
{
    protected:
        virtual void UpdateWorldData(double dAppTime)
        {
            Spatial::UpdateWorldData(dAppTimie);
            for(int i=0; i
            {
                Spatial* pkChild = m_kChild[i];
                if(pkChild)
                    pkChild->UpdateGS(dAppTime, false);
            }
        }

        virtual void UpdateWorldBound()
        {
            if(!WorldBoundIsCurrent)
            {
                bool bFoundFirstBound = false;
                for(int i=0; i
                {
                    Sptial* pkChild = m_kChild[i];
                    if(pkChild)
                    {
                        if(bFoundFirstBound)
                        {
                            //Merge current world bound with child world bound
                            WorldBound->GrowToContain(pkChild->WorldBound);
                        }
                        else
                        {
                            //set world bound to first nonull child world bound
                            bFoundFirstBound = true;
                            WorldBound->CopyFrom(pkChild->WorldBound);
                        }
                    }
                }
            }
        }
};

scene grapha的update主要做两件事情,一是从上到下更新world transform,二是从下往上更新world bound。其中包围体是要包括所有子节点的包围体的,而只有Geometry类型的节点需要计算自己的包围体(从顶点数据计算)。

wild magic3中,更新不是自动的,必须手工调用,而且要选择从哪一个节点开始更新,一般是某节点需要更新(比如local transform变了)并且他上面没有需要更新的父节点,那么就要调用该节点的UpdateGS,这样的节点有几个就调用几次UpdateGS。 UpdateGS里面通过遍历和递归,做了上面说的两件事情。UpdateGS的参数bInitiator 表明这个node是更新的起点,只有这个node的UpdateGS里面才会向上更新world bound volume直到root,而其他的node只会更新自己的world bound不会向上传播,这是为了提高效率。(因为这是在transform和world bound更新完成之后才调用的,所以只要更新的起点node向上更新包围体就足够了,下层的node没必要向上传播,否则也是被覆盖,浪费计算)

计算transform是在UpdateWorldData 里面做的,对于spatial的UpdateWorldData,主要就是把自己的local transform和parent的world transform级联起来,得到自己的world transform,但是这之前首先会使用controller进行更新,controller可能对transform系统产生影响也可能不影响,使用 controller时通过设置WorldIsCurrent来决定是否controller已经更新了world transform而不需要级联的更新方式。(比如skin controller)。而另外一些controller如普通key frame和IK,只是改变了local transform,还是需要使用级联的方式更新world transform的,他就不会设置WorldIsCurrent。另外一些controller没有影响到transform,也不会设置 WorldIsCurrent,比如morphe controller,但是他需要调用Geometry的UpdateMS来更新模型的一些数据(ModelBound).
Geometry没有override UpdateWorldData,所以和Spatial是一样的。
Node 的UpdateWorldData里面首先是直接调用了Spatial的UpdateWorldData来更新自己的world transform,然后对于他的所有子节点(Spitial,可能是Node或Geometry)遍历调用UpdateGS(bInitiator参数为false,因为子节点肯定不需要传播bound更新到root)。这就形成了UpdateGS的递归调用。这是一个深度优先的树遍历。树的每一层都是先计算好自己的transform然后让子节点去UpdateGS,所有子节点的调用都完成后才会计算自己的world bound,最终所有层次的UpdateGS都执行完毕,回到调用的起点节点那儿,接着执行那个节点的UpdateWorldBound。因为起始调用的节点的bInitiator是true,所以会执行PropagateBoundToRoot,向上更新world bound直到root。

更新UpdateWorldBound在spatial里面是个纯虚函数,Geometry的实现就是使用world transform变换model bound得到world bound,而Node里面则是计算一个包含所有子节点的world bound的总包围体。其中WorldBoundIsCurrent的作用是,当某个node的world bound可以通过其他途径得到时避开正常的计算流程,比如一个房间是固定的可以预先计算好一个world bound不再改变,不管其中的子节点怎么动怎么变都不再计算这个房间的world bound了。

整个过程就是这样的:
三个类(Spatial, Node, Geomotry)
三个主要函数(UpdateWorldData,UpdateWorldBound,以及PropagateBoundToRoot)
三个重要标志的设置(UpdateGS的bInitiator参数,Spatial的成员WorldIsCurrent和WorldBoundIsCurrent)
node/spatial的遍历,UpdateGS的递归
完成了world transform和world bound的更新。

另外UpdateBS是另外一个公开的接口,当model bound变化时,而transform没变化时,就只要调用UpdateBS就可以了。

原创粉丝点击