Wildmagic4札记
不想吐槽了,这****博客还真是坑,代码出现各种问题,没办法,只有重新写一遍了
声明一下,为了省事,文章的部分内容是从n5的博客里面直接摘抄过来的
1:关于只能指针的,我仿照他写的:
class obj { public: obj() { count=0; cout<<"obj is constructed"<<endl; } ~obj() { cout<<"obj is destructed" << endl; } void IncreaseRenfer() { count++; } void DecreaseRenfer() { if(--count==0) delete this; } private: int count; }; template <class T> class smart { public: smart(T *p) { ptr=p; if(ptr) ptr->IncreaseRenfer(); } ~smart () { if(ptr) ptr->DecreaseRenfer(); } smart (const smart &p) { ptr=p.ptr; if(ptr) ptr->IncreaseRenfer(); } smart& operator = (T *p) { T *temp=p; ptr=p; ptr->IncreaseRenfer(); if(temp) temp->DecreaseRenfer(); return *this; } public: T *ptr; };
关于运行时的类型识别:
//每一其他类都有一个这个类的对象来维护类的继承标志 class Rtti { public: Rtti (const char* acName, const Rtti* pkBaseType); ~Rtti (); const char* GetName () const; bool IsExactly (const Rtti& rkType) const; //查看rktype类是否和本类是一个类 bool Rtti::IsDerived (const Rtti& rkType) const //查看rktype类是否是从本类继承来的 { const Rtti* pkSearch = this; while (pkSearch) { if (pkSearch == &rkType) { return true; } pkSearch = pkSearch->m_pkBaseType; } return false; } private: const char* m_acName; //这个类的基类的名字,比如一个类从Foo类派生出来,这个类的这个变量的名字就是Wm3.Foo const Rtti* m_pkBaseType; //这个类的父类 };
由于这个引擎的所有类都是从object类派生出来的,所以object类有一个Rtti类的成员变量来检测这个类的一些情况
class WM3_ITEM Object { public: virtual ~Object (); protected: Object (); public: static const Rtti TYPE; inline const Rtti& GetType () const { return TYPE; } bool IsExactly (const Rtti& rkType) const; //是否和tkType是同一个类 bool IsDerived (const Rtti& rkType) const; bool IsExactlyTypeOf (const Object* pkObject) const; //两个类是否是同一个类 bool IsDerivedTypeOf (const Object* pkObject) const; //本类是否是从参数pkObject类派生出来的 }
关于强制转换类型这里指提供了四种:静态强制转换,动态强制转换,前面两种的常量转换形式:
动态强制转换主要就是判别一下当前的类是否是从要转换的类派生而来的,如果是就强制转换,不是就转换为NULL
T* StaticCast (Object* pkObj) 静态强制转换 { return (T*)pkObj; } const T* StaticCast (const Object* pkObj) { return (const T*)pkObj; } T* DynamicCast (Object* pkObj) 动态强制转换 { return pkObj && pkObj->IsDerived(T::TYPE) ? (T*)pkObj : NULL; } const T* DynamicCast (const Object* pkObj) { return pkObj && pkObj->IsDerived(T::TYPE) ? (const T*)pkObj : NULL; }
在介绍场景管理之前介绍一下坐标变换吧,这个引擎把坐标变换封装起来了,类名叫Transformation
// 如果一个矩阵M只有他的对角线元素不全为0,其他都为0,那么就称M是对角矩阵。
// The transformation is Y = M*X+T, where M is a 3-by-3 matrix and T is
// a translation vector. In most cases, M = R, a rotation matrix, or
// M = R*S, where R is a rotation matrix and S is a diagonal matrix(对角矩阵)
// whose diagonal entries are positive scales. To support modeling
// packages that allow reflections and nonuniform scales, I now allow
// the general transformation Y = M*X+T. The vector X is transformed in
// the "forward" direction to Y. The "inverse" direction transforms Y
// to X, namely X = M^{-1}*(Y-T) in the general case. In the special
// case of M = R*S, the inverse direction is X = S^{-1}*R^t*(Y-T).
该类包含这几个成员变量:
Matrix3f m_kMatrix;//其逆矩阵就是其转置矩阵,由于当这个矩阵只是表示旋转矩阵时期逆矩阵和本身矩阵时一样的,因为旋转矩阵的转置矩阵就是本身
Vector3f m_kTranslate; 平移
Vector3f m_kScale; 缩放
bool m_bIsIdentity, m_bIsRSMatrix, m_bIsUniformScale;
//是否为单位矩阵,是否为M = R*S缩放矩阵和对角矩阵的乘积,对角矩阵S的对角线元素为xyz方向上的缩放,其他元素为0,是否为标准同大小缩放
//m_bIsRSMatrix为true表示这个矩阵m_kMatrix只包含旋转,关于缩放矩阵用另外一个矩阵S表示,S是对角矩阵,否则表示这个矩阵m_kMatrix同时包含旋转,缩放和平移
//当m_bIsRSMatrix为true的时候,平移 缩放 旋转不在一个矩阵里面,m_kMatrix只包含旋转,缩放储存在m_kScale里面,平移储存在m_kTranslate里面,
// 当m_bIsRSMatrix为false的时候,m_kMatrix同时包含缩放和旋转矩阵,注意这个矩阵永远不会包含平移
这个类实现了几个方法:
// Compute X = M^{-1}*(Y-T) where Y is the input and X is the output. //根据Y求X Vector3f ApplyInverse (const Vector3f& rkInput) const; void ApplyInverse (int iQuantity, const Vector3f* akInput, Vector3f* akOutput) const; // Compute Y = M*X+T where X is the input and Y is the output. //根据输入点计算经过本变换矩阵变换后的点 Vector3f Transformation::ApplyForward (const Vector3f& rkInput) const { if (m_bIsIdentity) 如果本变换矩阵时单位矩阵,就相当于没有变换 { // Y = X return rkInput; } if (m_bIsRSMatrix) 如果本变换矩阵把旋转矩阵和缩放平移矩阵分开了,则分开计算旋转和缩放 { // Y = R*S*X + T Vector3f kOutput(m_kScale.X()*rkInput.X(),m_kScale.Y()*rkInput.Y(), m_kScale.Z()*rkInput.Z()); kOutput = m_kMatrix*kOutput + m_kTranslate; return kOutput; } // Y = M*X + T Vector3f kOutput = m_kMatrix*rkInput + m_kTranslate; //如果本变换矩阵同时包含了缩放和旋转,直接根据 // Y = M*X + T 公式计算即可 return kOutput; } //这个方法是计算n个向量都经过本变换后的结果,相当于上一个方法加一个for循环而已 void Transformation::ApplyForward (int iQuantity, const Vector3f* akInput, Vector3f* akOutput) const
包括几个核心类: Spatial, Node, Geometry
其 中Spatial是Node和Geometry的父类(spatial是个接口类),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有子节点)
这里的场景也是用树来管理的,但是这棵树不太一样,只有叶子节点Geometry里面才有几何实体,为了区分叶子节点和非叶子节点才有了Geometry和Node这两个类
为了搞的比较明确一点,贴出部分代码
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); //这个函数主要用来更新变换,这个是用来向下更新,这个函数递归的调用UpdateGS函数 UpdateWorldBound(); //更新世界空间的包围盒包围盒(对于更新包围盒:1:叶子节点:直接从顶点中计算 2:非叶子节点:把所有子节点的包围盒合并) if(bInitiator) //如果是从本节点开始更新的,就向上更新包围盒 { PropagateBoundToRoot(); //这个函数的功能就是向上更新包围盒一直到根节点 } } void UpdateBS() //这个函数只是更新本节点包围盒,还向上更新包围盒 { UpdateWorldBound(); PropagateBoundToRoot(); } protected: virtual void UpdateWorldData(double dAppTime) //这个主要用来更新变换,更新变换的方法是把父节点的world * 自己的local得到自己world,原因见上面的图 { UpdateControllers(dAppTime);//control可能直接修改local或world transform if(!WorldIsCurrent) { if(m_pkParent) World.Product(m_pkParent->World, Local); else World = Local; } } virtual void UpdateWorldBound() = 0; //这里把更新包围盒的函数声明为纯虚函数,Node类和Geometry类需要重载这个函数 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函数, //然后调用个个子节点的UpdateGs函数,向下更新变换,在向下更新变换的过程中,子节点就不需要向上更新包围盒了 { Spatial::UpdateWorldData(dAppTimie); for(int i=0; i { Spatial* pkChild = m_kChild[i]; if(pkChild) pkChild->UpdateGS(dAppTime, false); } } //其中WorldBoundIsCurrent的作用是,当某个node的world bound可以通过其他途径得到时避开正常的计算流程, 比如一个房间是固定的可以预先计算好一个world bound不再改变,不管其中的子节点怎么动怎么变都不再计算这个房间的world bound了。 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是在UpdateWorldData 里面做的,对于spatial的UpdateWorldData,主要就是把自己的local transform和parent的world transform级联起来,得到自己的world transform。
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就可以了。
关于渲染状态:
关于状态这个引擎专门封装了一个类:GlobalState从Object类派生而来的
- alpha: 是否启用混合,srcblend, dstblend; 是否启用alpha test, test方法,test ref value
- cull: 是否启用culling, front face ccw or cw, cull face back or front
- dither:是否开启抖动
- fog: 是否启用fog, fog mode(linear,exp, exp_sqr), start,end, density, color, per_vertex/per_pixel
- material: 即光照材质,emissive, ambient, diffuse, specular和shininess
- shade:flat or smooth(Gouraud shading)高洛德着色模式 还是平滑着色模式
- wireframe: 是否启用线框模式
- zbuffer: 是否使用zbuffer, zwrite, z compare func
Spatial节点类里有几个个成员变量
TList<GlobalStatePtr>* m_pkGlobalList; 状态列表 ,这个列表里存储着一个节点所需要的各种状态,包括alpha等状态 TList本身是个链表
TList<Pointer<Light> >* m_pkLightList; 灯光列表
叶子节点Geometry里面有一个数组
GlobalStatePtr States[GlobalState::MAX_STATE];,这个数组里存储的是叶子节点绘制之前的渲染状态,前面的两个链表和下面的一个UpdateRS函数都是为了更新这个数组而产生的
关于更新渲染状态的函数主要是UpdateRS函数
virtual void UpdateRS (TStack<GlobalState*>* akGStack = 0, TStack<Light*>* pkLStack = 0); TStack本身是个栈
void Spatial::UpdateRS (TStack<GlobalState*>* akGStack, //参数akGStack中实际是个数组,数组里每个元素是个栈,栈中存储的是所有节点的状态 TStack<Light*>* pkLStack) { bool bInitiator = (akGStack == 0); //<1>如果本节点是渲染状态开始更新的节点,则需要为参数中两个数组分配内存空间了,并且给数组分配默认的状态值,并且从本节点开始向根节点遍历,把根节点一直到本节点的状态依次存储到akGstack里面,也就是说遍历完成之后栈顶元素里是本节点的状态;<2>如果本节点不是开始更新的节点,直接把本节点全局状态链表中的状态压到栈中,不管本节点是不是开始更新的节点,该UpdateRS函数到达哪个节点,akGStack栈中的栈顶元素永远是到达的那个节点的状态;<3>如果本节点是叶子节点,调用UpdateState函数,则将参数aKGStack数组中每一个栈的栈顶元素更新到叶子节点的States数组内(最后进行绘制叶子节点中的物体的时候的渲染状态就是States中的状态),这个也就是说子节点中的状态会屏蔽父节点中的状态(原因是之前往上遍历的时候是先把父节点的状态压入到栈里面,然后把子节点里的状态压入到栈里面的),<4>如果本节点不是叶子节点,调用UpdateState函数向下递归调用UpdateRS函数(特别注意:只有增加一个新的状态例如ZBuffer到一个节点中,或者删除一个状态的时候才会调用UpdateRS函数,例如增加一个状态ZBuffer到本节点(假如本节点不是叶子节点)中的时候,那么这个节点的m_pkGlobalList链表增加了一个链,那么就要调用这个函数UpdateRS,最终新增加的这个状态会被放到akGstack里面,例如被放到akGStack[5]里面,当然akGStack[5]这个栈里面只有一个元素,因为这个状态刚刚加到这个节点里面,然后向下递归调用UpdateRS函数,由于这个节点的所有子节点都没有这种状态,所有akGStack[5]栈里面都会只有一个元素,当递归到叶子节点的时候,会调用UpdateState函数把所有的状态都更新到States数组里面,然后那个被增加新状态的节点的所有的子节点(叶子节点)的States数组里面都会有ZBuffer这种状态),将本节点中的状态向下更新,UpdateState函数后,需要把本节点的状态从栈中删除,如果本节点是开始更新的节点的话,还需要把之前参数中的两个数组的空间释放 if (bInitiator) { akGStack = WM3_NEW TStack<GlobalState*>[GlobalState::MAX_STATE]; for (int i = 0; i < GlobalState::MAX_STATE; i++) { akGStack[i].Push(GlobalState::Default[i]); } pkLStack = WM3_NEW TStack<Light*>; PropagateStateFromRoot(akGStack,pkLStack); } else { PushState(akGStack,pkLStack); } UpdateState(akGStack,pkLStack); //这个函数分两种情况 //1:如果本节点是叶子节点,则将参数aKGStack数组中每一个栈的栈顶元素更新到叶子节点的States数组内 //2:如果本节点不是叶子节点,就向下递归调用UpdateRS函数,将本节点中的状态向下更新 if (bInitiator) { WM3_DELETE[] akGStack; WM3_DELETE pkLStack; } else { PopState(akGStack,pkLStack); } } //把本节点的全局状态列表中的状态压到 参数akGstack栈中 void Spatial::PushState (TStack<GlobalState*>* akGStack, TStack<Light*>* pkLStack) { TList<GlobalStatePtr>* pkGList; //这里遍历本节点全局状态列表中的所有状态,找出这些状态对应的类型,根据类型(是alpha还是cull等)分别压到对应的栈中 for (pkGList = m_pkGlobalList; pkGList; pkGList = pkGList->Next()) { int eType = pkGList->Item()->GetGlobalStateType(); akGStack[eType].Push(pkGList->Item()); } TList<LightPtr>* pkLList; for (pkLList = m_pkLightList; pkLList; pkLList = pkLList->Next()) { pkLStack->Push(pkLList->Item()); } } //把本节点全局状态列表中的所有状态从akGstack中移除 void Spatial::PopState (TStack<GlobalState*>* akGStack, TStack<Light*>* pkLStack) { TList<GlobalStatePtr>* pkGList; for (pkGList = m_pkGlobalList; pkGList; pkGList = pkGList->Next()) { int eType = pkGList->Item()->GetGlobalStateType(); GlobalState* pkDummy; akGStack[eType].Pop(pkDummy); } TList<LightPtr>* pkLList; for (pkLList = m_pkLightList; pkLList; pkLList = pkLList->Next()) { Light* pkDummy; pkLStack->Pop(pkDummy); } } void Spatial::PropagateStateFromRoot (TStack<GlobalState*>* akGStack, TStack<Light*>* pkLStack) //从根节点开始,把所有的渲染状态压到akGStack栈数组里面 { if (m_pkParent) { m_pkParent->PropagateStateFromRoot(akGStack,pkLStack); } PushState(akGStack,pkLStack); } void Geometry::UpdateState (TStack<GlobalState*>* akGStack, TStack<Light*>* pkLStack) //更新节点状态,这个是也叶子节点的更新状态函数,主要是用来更新States数组的 { int i; for (i = 0; i < GlobalState::MAX_STATE; i++) { GlobalState* pkGState = 0; akGStack[i].GetTop(pkGState); assert( pkGState ); States[i] = pkGState; } Light* const* akLight = pkLStack->GetData(); int iQuantity = pkLStack->GetQuantity(); Lights.RemoveAll(); for (i = 0; i < iQuantity; i++) { Lights.Append(akLight[i]); } } void Node::UpdateState (TStack<GlobalState*>* akGStack,TStack<Light*>* pkLStack) //这个是非叶子节点的更新状态函数,主要用来把本节点的状态向下更新 { for (int i = 0; i < m_kChild.GetQuantity(); i++) { Spatial* pkChild = m_kChild[i]; if (pkChild) { pkChild->UpdateRS(akGStack,pkLStack); } } }