现在的位置: 首页 > 综合 > 正文

The Design of Model (part 1)

2013年02月28日 ⁄ 综合 ⁄ 共 2855字 ⁄ 字号 评论关闭

The Design of Model (part 1)

仅供个人学习使用,请勿转载,勿用于任何商业用途。

 

 

 

           过去一段时间都做引擎设计,小有成果,过几天陆续整理出来,今天先发个引子吧. ^_^

           第一步当然是定义模型数据,有了模型,才方便做后续开发。通俗一点说,就是要创建model类。也许你会说XNA或者说DirectX已经有内置的model,为什么还需要自己写呢?如果你稍有经验,就会发现XNA内置的model功能是非常有限的,而且是密封类,无法进行扩展。几乎所有的商业引擎都有自定义的model类(可惜的是很少看到有文章介绍如何建立一个易用的Model类)XNA和DirectX内置的model只是为了方便写一些小游戏而已。这并不是ms在偷懒,不愿意写一个更好的model,正如我们马上将看到,在编写这样一个类时,有太多选择,很难写出一个通用的模板。因此,下面设计的方案,不一定是最好,也不一定是最适合你的(当然,希望也不是最坏的),只是希望对你设计自己的Model有所启发。:)

         为了方便讨论,先介绍一些基本概念和术语。Model,指由一系列几何体组成的逻辑单元,比如一个人,一辆车。我们使用的大部分模型都将来自Maya或者3ds Max这样的建模软件,在模型师建模时,并不是一次就创建出一个完整的模型,而是把整个模型分成独立的小单元,分别建立,最后拼装到一起。比如把人物分作躯干,四肢,头部,手脚几部分来建立。这种组成模型的最小单元称为mesh part。初看起来这样的细分是没有必要的,但如果你希望每个部分能独立运动,比如车轮必须独立于车身旋转,那么就必须进行这样的细分,此外,对于不同材质的物体,也必须分开,比如车胎和内部的金属圈,就必须分为不同的mesh part。当建模软件导出模型数据时,每个mesh part都以自身的局部坐标来保存顶点的,比如,躯干以躯干中心为原点,定位所有点的坐标,手臂同样以手臂中心为原点,定位顶点坐标。这也就是很多人加载模型之后,发现模型都叠到一起的原因。所以,除了mesh part以外,还必须使用骨骼bone来表述模型的结构和mesh part之间的位置关系。简单的说,骨骼保存了相对于其父节点的位移或者旋转。显然,单一的骨骼并没有多少用,常见的是使用骨骼树,描述mesh part之间的结构关系。以人体为例,躯干为树的根节点,四个分支分别代表四肢,上臂有一个子节点,连接着前臂,前臂又连接着手掌,手指。显然,要获得手掌的坐标,就必须从躯干开始,依次加上上臂和前臂的坐标偏移。当然,对于某些特别简单的模型,比如一个盒子,一个mesh part就能代表一个model,自然也就不需要骨骼了。

          在建立你的模型时,至少有以下问题是需要考虑的:
1. 以什么方式保存模型数据,顶点数组,vertex buffer还是vertex buffer + index buffer,绘图用DrawIndexPrimitive,还是DrawPrimitive
2. 以什么方式组织数据,triangle list, triangle strip还是???
3. 组成同一model的mesh part分别拥有各自的vertex buffer,还是把所有mesh part数据打包为一个大的vertex buffer,通过索引区别每个mesh part?
4 同一模型的不同实例,共享顶点数据还是分开?
5. 是否支持LOD,如果支持,以什么方式获得LOD数据,运行时计算还是预处理?如何实现LOD过渡,如何保存LOD数据:把同一model的不同LOD作为独立的model,还是封装为一个?又如何控制LOD?
6. 模型将支持什么样的动画:骨骼动画,关键帧动画,morphing?动画在CPU还是GPU计算?
7. 模型是否需要骨骼,是否可以形变。
8. 如何处理材质信息。
9. 如何处理包围盒。
10. 是否支持instancing,支持哪种类型的instancing?
..............................

           当你真正动手编写一个引擎时,还有更多问题需要考虑,比如未来如何集成到scene graph里,如何裁剪,如何渲染等等,如果忽略了这些重要的元素,那么将来做后续开发时必然会出现很多问题。当然,并不是说你从现在开始就要周全的考虑好scene management,renderer等系统的设计,而是对将来高级系统如何使用低级类有大概了解,这也正是OOP里的依赖倒置原则。前段的所有问题都是实现细节,而这里,考虑的则是抽象。
          这里是一个理想的渲染器例子: 

foreach renderableObject in scene
    if(renderableObject is visible)
          Renderer.AddObjectToRenderQueue(renderableObject)

Renderer.SortAllOpaqueObjectByDistance();
Renderer.SortAllOpaqueObjectByMaterial();
Renderer.SortAllTransparentObjectByDistance();
Render.SortAllAllTransparentObjectByMaterial();

foreach material in Render.materalQueue
     material.Begin();
     foreach opaqueRenderableObject in Render.renderQueue
             renderableObject.Draw();
    material.End();

foreach material in Render.materalQueue
      material.Begin();
      foreach transparentRenderableObject in Render.renderQueue
               renderableObject.Draw();
       material.End();

       这几乎是最简单的渲染流程了,为了方便讨论,我省略了大部分细节。首先,我们从裁剪系统得到需要渲染的物体。接下来,为了尽量利用early-z cull的好处,把实心物体按照从前到后的顺序排序,为了尽量减少渲染状态改变,再把物体安材质排序。对于透明物体来说,为了获得正确的渲染效果,则需要按从后到前的顺序排序。

       这段代码虽然简单,也显示了一些核心的概念,首先,所有可渲染的物体都必须有一个同一的接口Draw,其次,应该把材质封装成独立的material对象。
        (to be continue..............)

呃....不行了,瞌睡,瞌睡,天快亮了,先睡去+_+ Zzzz......

 

抱歉!评论已关闭.