不朽的SceneGraph

来源:互联网 发布:社会关系网络书籍 编辑:程序博客网 时间:2024/06/02 10:19

 不朽的SceneGraph

SceneGraph自3D技术出现伊始就有着教主的地位,而今更是像基督教一样成功传销而且深入人心。在对不朽的SceneGraph顶礼膜拜的时候,有多少人想过,它早已是一具干尸。

这里我们先不管广义上的SceneGraph,就说我们常见的狭义SceneGraph,多数常见3D引擎都有的,如Ogre, Irrlicht, OpenSG等, 可能还有你手中的引擎。一般都会有个SceneNode类,所有的场景对象都从之派生。这看起来似乎很合理,大多数教材也这么教,整个场景由树或有向图构成,层次关系清晰。这是一个理想的数据结构,是个完美的理论,但是很不幸,它和现实需求不符。

【作用变化】

最常见的SceneGraph模式是空间变换的层次树,也就是地上有栋房子,房子里有张桌子,桌子上有个茶杯。如果有人仍然按着古老的课本所教的那样,遍历这颗树来画每个节点,那他就不用在这个行业混了。SceneGraph现今的主要作用只是个场景的数据库,绘制过程是不同于树的结构的,相同属性的对象要成批绘制,半透明对象要排序绘制,地球人都知道……

【局部叛节】

结构完美性的麻烦大概是从引入骨骼动画开始的。起初我们有一个怪物的骨架,它是一个完美的带动画的SceneGraph,现在要给它蒙上一个怪物模型的皮,很不幸的是这张皮是整个骨架共有的,所以放蒙皮的最合适的位置是骨架的根。这似乎还好,但是,骨架中的节点将不绘制怪物,而是骨架的根绘制整个怪物,每个节点绘制自己的理想破灭了。更进一步,给怪物手里握一把板斧,板斧是刚性物体,是挂到手骨上的,这里又需要经典SceneGraph行为;但如果给怪物穿个草裙,这里又是一个骨骼动画行为。

诸位可能觉得有点乱了,但这只是开始,而且只是局部问题,局部问题都是可以靠封装来解决的。Ogre里用Entity类包含了一个局部的子图,Irrlicht用AnimatedMeshSceneNode封装了动画节点。

有了第一个局部封装就会有第二个——地形。地形在逻辑上和SceneGraph没冲突,但在性能上不能忍受,因为大地广阔无垠,而我们的显卡只能处理眼前看到的部分,而且还得为远处的块构造低精度网格。SceneNode的简单的子节点集合不行了,得用Octree、BSP等空间分割树了;为了不同精度的网格的边界能接上,每个块又必须知道邻接块的精度;大量的纹理和绘制批数也需要特别的优化……

【大混乱】

说到了空间分割树,问题就大了。场景中几千个对象是很正常的事,这里没有按空间搜索对象是不行了。无论是用Octree、BSP还是其他什么的,这棵树怎么和那个“One True SceneGraph”结合到一起都是个麻烦。常见的做法一:同时维护两棵树,将需要按空间查询的对象加入空间搜索树。不言自明,在维护两边同步上是很容易出错的。常见做法二:派生专门的节点支持空间搜索。会遇到不该加入空间搜索的节点的问题。

现在已经有两颗树了,两个上帝已经拜不过来了,可还有静态搜索树和动态搜索树问题。场景中的对象大致又可分为静态对象和动态对象,对他们的特点进行优化又会有不同的搜索树,如静态大场景模型用BSP能有效裁减,动态对象用松散八叉树更好处理效率更高。

当我们要对场景中的这些对象进行物理碰撞检测时,当然不是所有的对象都需要参与碰撞检测,显式上的对象的节点也不一定对应物理检测上的一个对象,这里又会有一个物理或游戏逻辑的图。实际上一个场景可能包含了更多的树或图,包括对场景中对象按光源组合、渲染目标buffer或材质排序组合的优化树,用于分块加载超大场景模型的分割树,游戏对象的逻辑关系图,窗口UI系统的子窗口树……

供奉多个神仙是很头痛的问题,所以人们都喜欢一元神论,所以我们的关键问题是核心结构是哪个,然后其他的图都与核心结构同步。在给定SceneGraph的情况下,新手一般会把逻辑全都放到SceneNode或SceneNode的Animator上,这主要是因为很多教材都这么教,这也就是以显式用的SceneGraph为核心结构。这种方法只对做固定的动画很有效,在处理对象间关系时则会有很多麻烦。

合理的架构应该是以逻辑部分为核心结构,显式用的数据结构只是核心逻辑的一个视图,有点Model-View的意思。也许有人担心重复的数据结构的问题,但其实他们的不同要远大于相同,例如想一想一颗树只是一个简单的逻辑对象,而要有多少显式对象来参与表现它。将显式和逻辑明确地模块划分开有如下好处:(1)结构清晰,互不干涉,逻辑不受显式SceneGraph的限制;(2)可以各自为各自的性能优化;(3)显式部分相对独立,易于更换显式引擎;(4)可以把逻辑独立出来,易于实现Client-Server模式。

【共享节点?】

共享节点是经典SceneGraph里的一个变态的理想主义的东西,虽然现今的多数3D引擎已经抛弃了它,但仍有相当数量的死党。为什么不好呢?一个Mesh或纹理对象的节点可以被场景中多个物体共用,确实节省了大量的内存,但它带来很恼人的问题:

一:SceneGraph不再是一颗树,而是一个有向无环图,也就是说SceneNode上没有Parent属性,你只能自顶而下地遍历。

二:如果没有经过遍历,就无法取得绘制当前节点所需的所有属性。

三:显式引擎不能缓存优化节点的绘制属性,因为无法跟踪属性变化。

那么替代的方案是什么?蝇量模式(Flyweight Pattern),通俗一点说就是常见的ResourceManager,需要共享的对象都在ResourceManager里,场景里只要简单引用。

【结论】

SceneGraph是一具不朽的干尸,它将继续存在,但已经面目全非。我们没有必要把自己限制在唯一的SceneGraph里,一把钥匙开一把锁,一种图解决一个问题,只要把模块划分开来,每个地方都可以用最合适的数据结构,甚至一个简单的vector<GameObject>。

【参考链接】
http://home.comcast.net/~tom_forsyth/blog.wiki.html#%5B%5BScene%20Graphs%20-%20just%20say%20no%5D%5D
http://www.ogre3d.org
http://www.opensg.org

原创粉丝点击