声明一下,为了省事,文章的部分内容是从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; 灯光列表
关于更新渲染状态的函数主要是UpdateRS函数
// 如果参数akGStack为空,先分配空间,再将默认的全局状态压入到栈里面去,然后向上传递直到根节点,将每个节点的状态压入到栈里面,如果不为空,则将本节点的状态压入到堆栈里,然后更新本节点的状态(通过把栈里的状态取出放到本节点里)然后把加到栈里面的状态弹出
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);
if (bInitiator) //如果本节点是渲染状态开始更新的节点,则需要为参数中两个指针分配内存空间了
{
// stack initialized to contain the default global states
akGStack = WM3_NEW TStack<GlobalState*>[GlobalState::MAX_STATE];
for (int i = 0; i < GlobalState::MAX_STATE; i++)
{
akGStack[i].Push(GlobalState::Default[i]);
}
// stack has no lights initially
pkLStack = WM3_NEW TStack<Light*>;
// traverse to root and push states from root to this node
PropagateStateFromRoot(akGStack,pkLStack);
}
else
{
// push states at this node
PushState(akGStack,pkLStack);
}
// propagate the new state to the subtree rooted here
UpdateState(akGStack,pkLStack);
if (bInitiator)
{
WM3_DELETE[] akGStack;
WM3_DELETE pkLStack;
}
else
{
// pop states at this node
PopState(akGStack,pkLStack);
}
}
//----------------------------------------------------------------------------
void Spatial::PushState (TStack<GlobalState*>* akGStack,
TStack<Light*>* pkLStack)
{
TList<GlobalStatePtr>* pkGList;
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());
}
}
//----------------------------------------------------------------------------
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);
}
}