摄象机接口的设计
最近在调整 3d engine 的接口。前段时间把 GC 模块加到底层后,很多部分都需要修改实现了(可以简化许多实现代码)。既然重构代码,不如就再次审查一遍接口的设计,结合最近的一些想法,重新弄一下。
嗯,那么 3d 引擎是什么?跟 3d api (Direct3D 或 openGL)有什么区别?固然,engine 如果只是做 3d api 的一层薄薄的封装,抹平各套 3d api 的差异。那么,就过于底层,显得小了。
如果为特定形式的游戏写死代码,让开发者写一些 MOD 插件就可以形成不同的游戏,那么又显得太高。在这种高层次上,游戏类型会限制于 engine 的实现。比如魔兽争霸 3 就直接用户写 MOD ,并的确有人以此发展出许多玩法。但你永远不可能在 魔兽争霸 3 的基础上写一个 MOD 实现第一人称射击游戏。
所以我指的 3d engine ,是处于 3d 游戏软件结构中间地位的东西。
那么,我们的 3d engine 到底要解决的是什么问题?做 engine 绝对不是以我能在 3d api 的基础上扩展出什么东西为设计向导。因为,对于完成一个软件,是一个从机器实现域映射到问题域的过程。这两个领域的模型是不同的。3d api 完成的是实现域的扩展,engine 则应该完全从实现域到问题域的一个变换,让开发者可以用最接近问题域的语言来表达问题。
跑跑题说的别的。
再提提最近我这里反复提到的老话题,为什么引擎中要有 gc 模块?gc 的机制是远离机器实现域的。计算机的冯诺模型上没有 gc 的概念。C 语言是贴近机器模型的,所以也没有天然的 gc 设施。但是对于问题域的表达,有了 gc 后更为容易,因为资源的生命期管理不属于解决问题的直接部分。
再有一个例子是近几年重新流行的函数式编程语言。函数式编程语言同样是不同于计算机固有模型的。但是,这样的语言却以改变计算机固有模型为代价,提供一个新的域来描述问题,许多问题的解决变得更加方便。
爱因斯坦建立广义相对论,有赖于黎曼几何体系。虽然黎曼几何空间和欧式几何空间可以做等价变换。但是若是没有黎曼几何,直接从欧式几何空间中推导出广义相对论,我想那将是不可完成的任务。
其实,给软件加一个中间层,最大的作用即是变换原有的实现域,提供出新的语言来描述问题。而中间的许多变换由于被集中解决,无论从性能还是稳定性上都会得到提高。
Raymond 在 TAOUP 中提到一句话(4.5 Unix 和面向对象语言,中文版 P103):“如果你知道自己在做什么,三层就足够了;但如果你不知道自己在做什么,十七层也没用。”为什么不是四层或是更多?云风认为:第一层我们完成了对底层的封装和抽象,排除了不必要的细节。第二层完成了一次转换,提供出最适合的语言来描述问题。第三层我们则用这种语言解决问题。多余的中间层次是无意义的,因为对于问题明确的应用,我们不需要二次转换。而如果我们足够理解系统底层,则不需要在第一个层次上重复做无谓的封装。至于第三个层次,如果你需要做多余的层次划分,那么就证明你的中间层搭建的不好,没能提供最佳的问题描述语言。
我们知道两点间直线最短。但如果要把中国和美国的每个城市都连通为一个交通网。将城市两两相连绝对不是最优的方案。通常我们会修出几条主航线连接两个国家,再在每个国家内部建立局部交通网落。这不仅仅是成本的问题。即使不计成本,让中国的每个城市和美国的每个城市直航,调度消耗的几何级数的增加同样会大大的降低整体的交通效率。
回到正题。那么作为 3d engine ,我们设计者就应该严格的考虑每个功能到底应该提供怎样的接口给人使用。可以最佳的满足问题解决的需要。作为 3d engine 的实现者,我们满脑子可以是矩阵变换、空间矢量、线性代数这些东西。但一旦反应到使用人的一边,这些就不再应该是他们描述问题的语言。engine 需要做的就是隐藏这些细节,换一种方式提供出来。又不能失去使用上的灵活性(比如只能做 RTS 而不能用于 fps)。
下面谈谈最近在做的摄像机的接口。尚未定型,暂作记录。
对于 3d api 来说,摄象机就是绝对空间中一个矢量,有绝对空间坐标和朝向。再加上焦距等一系列属性,便可以描述出要渲染的视野。
但对于要解决的问题(一个可以玩的游戏)来说,这不是合适的描述语言。我们关注的摄象机其实可以是一个实体,它在存在于虚拟世界空间中,它自身的位置往往相对于虚拟场景中的另一个实体,而非以世界绝对坐标的形式提供。比如是 fps 游戏,摄象机可能在主角的身后;对于 RTS ,它在地面的上方;对于赛车,它在驾驶位;对于 日式 RPG 它可能固定在场景中由编辑器预先设定好的位置上,等等。
所以,摄象机只需要绑定在虚拟场景中的一个物件上即可。或者它本身就是一个(不可被渲染的)虚拟物件。
而摄象机的朝向呢?
设置它的朝向不应该让使用者提供一个矩阵,这会让人不知所措。在逻辑表达中,大多数情况下,我们只需要让摄象机指向一个物体即可(对于引擎,还要进一步跟踪这个物体,而不需要每个渲染帧都要求使用者不停的刷新摄象机的状态)。这个物体可以在虚拟世界中真实存在,也可以是一个不被渲染的虚拟物体。比如 FPS 游戏中,我们可以让摄象机指向主角面前 10 米远的地方的虚拟物体。
少数时候,我们需要让摄象机指向一个坐标值(往往在写 demo 的时候用)。当然,我们可以排除后一种情况,当需要让摄象机指向一个特定坐标的时候,我们可以为在这个坐标点上创建一个虚拟物体(如同上一段中提示的 fps 游戏的摄象机设计)。但,我们只需要的是物体的坐标不是?而不是需要物体的全部。
这是一个典型的,可以用面向对象方法解决的问题。我们需要的是:具有获取坐标能力的东西,不管它是什么。
所以,我们可以给虚拟世界中的物件都提供一个方法,这个方法可以取出一个接口,一个获取特定坐标的接口。注意,“取出一个接口”在 C++ 中可以让对象继承这个接口的方式提供出来,但不一定必须如此(如果有 gc 的机制,临时创建一个仅含有这个接口的对象更加方便)。如果在 COM 中,那么就是调用 QueryInterface 得到。实现手法并不重要。
这样一个接口背后的实现需要做的事情是,获取自己相对一个指定坐标系的相对位置。它有可能得到一个非法的相对位置。这是因为,对象本身和目标可能并不在一个世界中。
如果我们需要指定一个特定坐标,可以做一个特别的实现封装一组矢量:比如,将一个位置信息绑定在主角面前 10 米处。
对于实现,没太多好谈的。
由于 3d engine 中虚拟物件通常以树方式组织。那么首先是求出位置对象和目标对象的最近公共祖先(LCA),如果没有公共祖先则返回出错值。如果有,根据公共祖先计算出相对坐标。
需要留意的是,LCA 问题在教科书上提到了许多算法来解决。但我们不要读死书。在 3d engine 的渲染树上,层级通常不多,但在某些层级上节点特别多。层次不深的树即使用最苯的策略寻找公共祖先也不会太慢。
Comments
Posted by: szcuipeng | (36) April 4, 2014 05:48 PM
Posted by: felix | (35) November 4, 2008 05:23 PM
Posted by: Anonymous | (34) July 9, 2008 11:35 PM
Posted by: Kute | (33) July 6, 2008 11:10 PM
Posted by: seek | (32) July 6, 2008 11:45 AM
Posted by: liangliang | (31) July 5, 2008 10:55 AM
Posted by: 小顺 | (30) July 2, 2008 10:37 PM
Posted by: 小顺 | (29) July 2, 2008 10:26 PM
Posted by: coolwind | (28) July 1, 2008 12:19 AM
Posted by: Yock.W | (27) June 30, 2008 04:28 AM
Posted by: 逍遥剑客 | (26) June 26, 2008 06:54 PM
Posted by: yisa | (25) June 26, 2008 12:39 AM
Posted by: joe wulf | (24) June 25, 2008 07:27 PM
Posted by: sjinny | (23) June 25, 2008 06:00 PM
Posted by: Cloud | (22) June 25, 2008 05:22 PM
Posted by: kevinlynx | (21) June 25, 2008 03:52 PM
Posted by: Cloud | (20) June 25, 2008 02:34 PM
Posted by: rockcarry | (19) June 25, 2008 02:01 PM
Posted by: black | (18) June 25, 2008 01:57 PM
Posted by: kypck | (17) June 25, 2008 11:18 AM
Posted by: JohnK | (16) June 25, 2008 10:57 AM
Posted by: rockcarry | (15) June 25, 2008 10:11 AM
Posted by: rockcarry | (14) June 25, 2008 10:07 AM
Posted by: 阿土仔 | (13) June 25, 2008 09:51 AM
Posted by: sjinny | (12) June 25, 2008 09:34 AM
Posted by: goldou | (11) June 25, 2008 09:17 AM
Posted by: nothanks | (10) June 25, 2008 09:00 AM
Posted by: Jim | (9) June 25, 2008 08:53 AM
Posted by: joe wulf | (8) June 25, 2008 08:50 AM
Posted by: joe wulf | (7) June 25, 2008 08:29 AM
Posted by: JohnK | (6) June 25, 2008 07:56 AM
Posted by: JohnK | (5) June 25, 2008 07:05 AM
Posted by: sjinny | (4) June 25, 2008 01:10 AM
Posted by: sjinny | (3) June 25, 2008 01:02 AM
Posted by: sboy | (2) June 25, 2008 12:50 AM
Posted by: sboy | (1) June 25, 2008 12:49 AM