• 从Demo到Engine(一) IRenderable


    从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设计的文章,可惜一直没想好怎么写,今天有空,索性想到什么写什么吧..........

  • 相关阅读:
    沿着河边走,环城60公里
    与客家土楼的约会(叶剑英故居福建)
    与客家土楼的约会(增城河源)
    与客家土楼的约会(东源龙川)
    C#几个经常犯错误
    C# 获取当前路径方法
    C# Stopwatch与TimeSpan详解
    话说程序员成熟的标志
    C#开源项目介绍
    C#各种加密算法的研究
  • 原文地址:https://www.cnblogs.com/clayman/p/1633704.html
Copyright © 2020-2023  润新知