层次结构和状态继承
在 blog 上,我写过好几篇关于场景管理模块的树结构的文章。这些也是我这两年在做游戏引擎中对象管理的思考历程。
通常游戏引擎中会把可渲染对象以树结构储存,这是场景管理模块最常见的作法。顺便说一句,GUI 界面也是用类似的方式。但是,我始终认为,从 gameplay 的层面上来看,游戏逻辑需要关注的对象并不需要用层次结构的方式管理。因为,空间结构上的层次很可能发生变化,从而引起关注的对象的层次路径变化。我们最终关注的那些东西不变,但它们在空间中的位置却会经常改变。
我一直在思考的问题是:为什么一定要用树结构组织可渲染对象?树结构到底带来了什么好处?
最直接的好处是,减少矩阵运算的次数。因为,渲染层最终需要对象在整个世界中的位置,而每个被渲染的部件本身却是逐级组合起来的(为了减少数据重复,我们不能因为一个部件换了个位置,就复制一次),部件只会记录相对整体的一个局部空间变换。如果我们平坦的保存没有可渲染部件,势必在计算它最终被渲染到屏幕时的世界矩阵的时候,需要连乘一长串局部矩阵。而组织成树结构,以一定的次序计算,可以大大减少最终矩阵乘法的数量。
但这一点好处,我认为还没有触及本质。表达空间位置的矩阵,仅仅是可渲染对象的一个属性而已。
层次结构的本质是让属性可以用继承的方式优化储存,并方便批量修改。对于每种属性,会定义一种对应的继承方法。
对于空间矩阵,如果一个对象没有自己的局部矩阵,那么它就继承了父亲的矩阵,如果有,继承的方式就是做 一次矩阵的乘法。修改根节点的空间矩阵,等价于修改了连同它的所有子孙的在空间中的位置。
其实,还有很多属性也需要继承。继承可以避免把相同的属性值复制到相关节点上,也方便了一组对象一起修改。
例如材质,当我们由于种种原因,将一个网格拆分成多个时(可能是因为网格顶点数量过多,也可能是因为它们的贴图不同),多个子网格的材质其实是基本一致的。
还有可使距离,用来控制摄象机距离多远的时候,就不再渲染该组对象。同一个物件,放在室内的时候可视距离比较近,放在室外的时候可视距离较远。
还有一类属性,可能简单到只是一个布尔量,但需要方便的成组改变。我能举出的例子有很多,下面只列出常见的几个:
- 可见标记:我们有时需要临时关闭一组对象的显示,但又不希望删掉这些对象。
- 投射阴影:让标记了的对象投射在阴影图上。
- 接收阴影:在渲染的时候,考虑阴影图的影响。
- 点选标记:被聚焦的时候,采用一种特殊的着色器高亮显示。
- 水中倒影:需要绘制在水面的倒影中。
我觉得在实现的时候可以合并这类布尔量的标记,一起处理比较好。
用一个 64bit 整数保存 32 组状态。高 32bit 表示该状态是自己设置(1) 还是从父亲继承下来(0) 。低 32bit 表示每个状态(0/1) 。
这样,读取第 n 个状态可以简单的用 (state >> n) & 1
。
当我们需要从父亲继承状态时,可以使用:
local mask = state >> 32 state = ( parent.state & (mask~0xffffffff) | (state & mask) | (mask << 32)
Comments
Posted by: Songs | (12) July 6, 2020 05:51 PM
Posted by: xxx | (11) June 29, 2020 06:31 PM
Posted by: walkfish | (10) June 14, 2020 11:47 AM
Posted by: YuqiaoZhang | (9) June 11, 2020 09:45 PM
Posted by: lizhi | (8) June 10, 2020 03:05 PM
Posted by: Cloud | (7) June 10, 2020 01:52 PM
Posted by: lizhi | (6) June 10, 2020 01:35 PM
Posted by: Drimoon | (5) June 10, 2020 11:20 AM
Posted by: 林冲 | (4) June 8, 2020 09:53 AM
Posted by: yongxinchang | (3) June 8, 2020 08:51 AM
Posted by: Cloud | (2) June 6, 2020 08:16 PM
Posted by: dwing | (1) June 6, 2020 11:33 AM