当前位置: 首页 > news >正文

wild magic3的Scene graph结构和Geometric State更新体系

刚看了《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就可以了。

相关文章:

  • python的for语句怎么用_Python中for语句是怎么用的
  • Windows Mobile 6.5.3 DTK(Developer Tool Kit)发布
  • python牛顿法算立方根_牛顿迭代法求a的立方根的C语言程序?
  • 搭上Windows 7快车,Windows Embedded Standard 2011更名为Windows Embedded Standard 7并发布RC版...
  • sqlserver大数据量update_选择多≠随便选,数据分析软件9大软件评测教你怎么选!...
  • Windows Phone 7 Series 概述、亮点以及。。。。。。
  • c++矩阵转置_MATLAB的矩阵运算与重构
  • rabbitmq 多个消费者消费一个队列_RabbitMQ——消费端限流、TTL、死信队列
  • DX9中如何模拟DX10/11里的ConstantBuffer
  • python怎么除去列表l中所有是x的元素_Python学习教程(Python学习路线):Python3之递归函数简单示例...
  • Windows phone 7 Series发布!
  • json_extract提取复杂json_4个小窍门,让你在Python中高效使用JSON
  • 关于Windows phone 7 series开发方面的传言
  • centos找不到apt-get命令_Centos 使用 pyinstaller 打包踩坑分享
  • python cv2 imshow_Python-OpenCV 2. 图像基本操作
  • export和import的用法总结
  • iOS编译提示和导航提示
  • Swoft 源码剖析 - 代码自动更新机制
  • 老板让我十分钟上手nx-admin
  • 批量截取pdf文件
  • 区块链技术特点之去中心化特性
  • 设计模式走一遍---观察者模式
  • 我与Jetbrains的这些年
  • 一道闭包题引发的思考
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​比特币大跌的 2 个原因
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #vue3 实现前端下载excel文件模板功能
  • (04)odoo视图操作
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (利用IDEA+Maven)定制属于自己的jar包
  • (三)uboot源码分析
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .htaccess 强制https 单独排除某个目录
  • .net 获取url的方法
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .Net+SQL Server企业应用性能优化笔记4——精确查找瓶颈
  • .NET6实现破解Modbus poll点表配置文件
  • @Bean, @Component, @Configuration简析
  • @property @synthesize @dynamic 及相关属性作用探究
  • [20170705]lsnrctl status LISTENER_SCAN1
  • [2023年]-hadoop面试真题(一)
  • [AIGC] 开源流程引擎哪个好,如何选型?
  • [bzoj1038][ZJOI2008]瞭望塔
  • [C#]C#学习笔记-CIL和动态程序集
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [C++]:for循环for(int num : nums)
  • [CF226E]Noble Knight's Path
  • [HDU3710]Battle over Cities
  • [Head First设计模式]策略模式
  • [JS7] 显示从0到99的100个数字
  • [Linux]进程间通信(进程间通信介绍 | 匿名管道 | 命名管道)
  • [mysql]错误解决之Failed to start MySQL Server