Clayman's Graphics Corner

DirectX,Shader & Game Engine Programming

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

从Demo到Engine(一) -- IRenderable

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

 

         从普通demoengine(为了方便讨论,本文的engine仅仅指render engine),最重要的一部就是抽象和批处理。这样的说法可能不太容易理解,看以下代码:

object1.Draw();
object2.Draw();
object3.Draw(); 

       

        这是我们写demo时最常出现的情况,从OO的角度来说,这是非常好的设计方案,每个对象都知道如何绘制自己,把所有渲染相关的数据方法都封装在内部,与设计模式中shape,circle,rectangle的例子不谋而合。但你应该已经猜到,我之所以写这篇文章,那么肯定这种方法是不好的,让我们来看看这段代码有什么问题。

         1. 性能。编写图形程序时,性能永远是第一位的考虑。对于object.Draw来说,为了保证正确渲染对象,Draw内部必须正确设置所有状态(render statevertex declarationstream sourceetc.., 如果前后渲染的物体有很多状态都相同,显然,冗余的状态设置会影响性能。其次,众所周知,GPU更善于一次渲染大量三角形而不是渲染很多次少量三角形,因此常常需要把小几何体打包为一个整体渲染。

         2. 可维护性。这段代码非常难于扩展,每次添加新的ojbect,就必须编写Draw。此外,更重要的是底层函数会遍布所有代码,一旦底层发生变化,几乎所有代码都要重写。举例来说,每个object都需要保存了Device引用,并直接调用DrawPrimitive绘图,当程序从DX9升级到DX10,或者从DX迁移到OpengGL,所有Draw函数中与渲染相关的代码都要重写。

         如果你已经意识到了这些问题,恭喜,你已经有初步的引擎思想了。回到文章开头,解决以上问题的关键就是把所有object抽象为一种统一的对象,之后在renderer中以批的方式组织,渲染,比如下面这样:

 

renderer.AddRenderable(obj1);
renderer.AddRenderable(obj2);
renderer.AddRenderable(obj3);
renderer.DrawScene();

 

         这里,把所有可渲染对象抽象为Renderable对象。乍一看,把所有可渲染对象都统一为一个类/接口似乎不是那么简单的事,但仔细分析,其实并不复杂。渲染一个物体的最少必要元素有哪些呢?1. 几何体信息,包括vertex/index buffer, vertex declaration2,材质信息,包括shaderrender statetexture3,变换信息,通常就是matrix。还有吗?没了,对就是那么简单,已经可以立即写出renderable的原型了:

interface IRenderable
{
      GetGeometryPackage();
      GetMaterial();
      GetMatrix();

}

 

        Renderer责设置geometryPackage, material,matrixIRenderable不再依赖于底层API了。再进一步,GeometryPackage应该如何设计呢?为了方便讨论,假设我们的几何数据只会用到一个stream,可以写出类似的代码:

abstract class GeometryPackage
{
         packageType;
         vertexDecl;
}

class BufferedPackage:GeometryPackage
{
         vertexBuffer;
}

class IndexedBufferedPackage: BufferedPackage
{
       indexBuffer;
}

class Renderer
{
         
switch(packageType)
         
case: buffered : drawPrimitive;
         
case: indexedBufferd: drawIndexPrimitive;
}

 

                 GeometryPackage是一个抽象类,根据不同的几何数据,派生出不同子类。你大概会说,这不是和object3.Draw需要为每个类实现Draw一样了吗?非也,这里的关键在于GeometryPackage只可能有有限的几个子类,由于硬件在很长一段时间内都不会发生变化,这些有限的子类相对是比较稳定的。但GeometryPackage用到底层类型(vertexBuffer)了,也有可能变化啊。对,可更重要的是我们已经把可能遍布所有object中的顶点数据,集中到GeometryPackage中几个有限的子类中了,即使底层函数变化,修改起来也非常快,并且所有接口仍然可以运行。Renderer需要根据packageType判断调用哪个函数,重构告诉我们switchbad smell?如前所述,这里的packageType基本是稳定有限的,因此也不会有太多副作用,反而整个程序只会在Renderer才会出现drawPrimitive等函数了,以后维护起来将非常方便。

         关于GetMatrix,没有太多可说的,但上面代码中隐藏了一个比较重要的地方,Matrix[] GetMatrix()才是完整的写法,这样,无论对简单还是带骨骼动画的物体都适用。

         至于Material,就比较复杂了,咱下回再说吧,呵呵J

 

========================================================================================

老早就想写一些关于renderer,material设计的文章,可惜一直没想好怎么写,今天有空,索性想到什么写什么吧..........

 

posted on 2009-12-28 01:58  clayman  阅读(2954)  评论(6编辑  收藏  举报