• 从Demo到Engine(五) Design a Material System part2


    从Demo到Engine(五) -- Design a Material System part2

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

    作者:clayman

     

    上一篇文章介绍了基本的材质系统接口,本文将继续讨论一些细节问题。

    冗余过滤

             减少状态变化是任何实时渲染系统提高性能的重要方法之一。有人认为如果每次参数更新都执行冗余检查,反而会牺牲性能,还有人觉得驱动会做冗余检查,不必多此一举。有没有必要做冗余过滤呢,答案是肯定的。首先,驱动进行的冗余检查是有限的,其次,如果能在系统的高层就找出冗余的设置,又何必一直传递到最底层再过滤呢?早期检查可以减少DX函数的调用,减少DX函数到驱动的调用,而代价往往只是一次条件判断语句,何乐而不为?

    冗余过滤可以在不同层次级别进行,render queue sort就是最高级的冗余过滤,在上一篇文章最后展示的代码中,我们见到了per material级别的过滤,再进一步,还可以做per parameter group或者per parameter级别的过滤。Parameter group是什么概念呢,实际上就是作为一个集合的一系列参数。DX 10下,render state object就是per group过滤的最好例子。DX9虽然没有state object的概念,但自己写一个类似的wrapper也并非难事,XNA 4.0就这么做了!至于Per parameter级别的过滤,应该依据参数类型决定是否需要做冗余检查。比如对基于float类型的变量就很难做冗余判断:

    if ( value != cachedFloatVale)

             updateParam();

     

             众所周知,由于浮点数本身的不精确性,这样的判断通常达不到预期效果。特别对于matrix这类包含多元素且易变的值,做冗余过滤显然得不偿失。但对纹理而言,类似的过滤则比较适合。冗余检测的目的不是要100%过滤所有重复设置,只要保证能避免大部分重复操作即可。

     

             实现冗余过滤,需要保存内部系统各变量的当前状态,包括material, shader program, state blocktexture等等。为保证状态一直性,必须让高层系统只能从一个地方修改这些参数。试想如果同时暴露了DeviceEffect,两者都会改变设备状态,那么状态追踪就会变的很麻烦。这里还要说说DX9Effect的第三个缺点:无法获得Effect保存的所有数据,比如sampler staterender state。在.fx文件里直接设置render state看起来是很便利的特性,但对引擎非常不友好,我们无法知道effect改变了哪些状态以及相应的值,相当于失去了对渲染系统的控制。.fx文件里一个错误的状态变量也许会破坏整个场景的渲染效果,而在引擎层次上根本无法找出哪里出错!DX9下,唯一可以解决这个问题的方案就是实现effect state manager。不过Effect已经够慢了,再加一个manager layer岂不是更慢,况且既然都有实现EffectStateManager的勇气了,何不更进一步抛弃Effect,直接管理Shader呢,毕竟编写2者的工作量已经差不多了。

    重新发明轮子

             为了避免Effect的种种缺陷,根本的解决方案就是编写自定义Effect/ Meta-Material。这一步其实并没有大多数人想的那么复杂,要做的只是加载/创建shader和管理参数。创建shader把原来的CompileEffect改变为CompileShader再稍做修改即可。注意,meta-material仍然需要有类似techniquepass的概念。DX SDKHLSLWithoutFX介绍的通过ConstantTable管理参数是最容易实现的方法,几乎不需要编写太多额外代码。如果你足够疯狂,可以连ConstantTable都不用,解析出ConstantTable中各变量所对应的寄存器编号和类型,直接SetShaderConstant。这里不仔细讨论如何解析constant table,但有一点需要注意,无论ConstantTable还是SetShaderConstantvs/ps所对应的变量是独立的:比如在.fx文件中声明了全局变量var,并且在vsps中都用到了这个变量,那么此变量对vs/ps来说,分别放在2个不同寄存器中。

    一些可能的优化方案

             延迟更新:所有参数,状态更新延迟到DP调用之前再进行,类似EffectCommitChange机制,Material.Apply等操作并不立即设置更新状态,而是标记,记录要进行的操作,DP之前,渲染系统检查是否有需要执行的操作并执行。

             参数打包:把需要更新的参数打包为一个或多个float[],用SetShaderConstate(float[])一次更新多个参数。这点对更新shader中的strut型变量特别有用,比如light,可编写类似SetShaderConstant( lightSemantic, light.ConverntToFloatArray) 的代码。

             参数寄存器绑定:材质/渲染系统优化很大程度上依赖于具体项目和编写shader的方式。对引擎来说,很难要求用户必须以某种特定规则编写shader,但对具体项目来说则是可行的。参数寄存器绑定指的就是所有shader都把相同的参数绑定到同一寄存器,比如color map—s0, normap map – register(s1), world matrix – register (c0),等等。这样不但可以减少冗余设置,也可以简化SetShaderParam时的代码逻辑。另外提一下,尽量不要使用effectPooleffectBlock这类东西,ms在设计整个Effect框架时虽然引入了很多不错的概念,可惜具体实现都不太好,往往得不偿失。

             从更高的层次来看,材质系统是整个渲染系统的一部分,脱离于渲染系统讨论是不完整的。仅靠material中的数据,还不足以渲染物体,worldviewprojection matrix,雾化,灯光参数也必不可少。这些数据由IRenderable或渲染系统的其他部分提供,但都必须通过material接口进行设置。此外,渲染系统还可以保存一些全局material”。物体并不局限于某一个material,在renderer中,为了实现某些全局效果,可以用renderer中定义的材质替换原来的material,比如希望做z-pre pass

    Renderer.EnableZPrePass = true;
    ................
    if (EnableZPrePass)
         render all objects with z
    -pre pass material
    else
         render all objects with normal material

             这样设计的优点是添加,修改全局效果时,只需要做最小的改动,而不必修改所有material。上一篇文章的评论中,有人提到了对Deferred Rendering的支持,也是类似的思路。无论forward还是deferred shading,材质系统并不需要做出改变,只需让shader代码支持相应的渲染方式,并在渲染器中做出调整就可以了,比如:
    deferredShading
    meta-material { technique01: normal, technique02, deferred , other technique…….};
    meta
    -material.SetTechnique( currentGlobalShadingProfile);
    …………………
    Renderer:
    if( deferredShading)
    {
      render all objects with per 
    object’s normal material(ds technique) //geometry phase
      perform screen-space lighting with renderer specific material
      perform composition with renderer specific material
      post
    -process with renderer specific material
    }
    else
    ........

             That’s All! 关于材质系统,最重要的设计理念就是抽象出一个统一的机制,可以管理任意shader,处理任何类似的参数,同时保证性能。这两篇文章并不完整,只介绍了基本思路和粗略的实现方法,multi-materialmulti-pass等概念都没有讨论。希望能对正在设计引擎的同学有所帮助,也欢迎高手指正不足之处。


    ps:不出意外的话,偶的<<Game Engine Architecture>>这周应该到了(保佑邮递员叔叔不要看错e文地址),我会参考其中的内容,继续这个系列的文章:)

     

  • 相关阅读:
    size_type、size_t、differentce_type以及ptrdiff_t
    题目1003:A+B ---c_str(),atoi()函数的使用;remove , erase函数的使用
    字符串中符号的替换---replace的用法
    A+B for Matrices 及 C++ transform的用法
    97.5%准确率的深度学习中文分词(字嵌入+Bi-LSTM+CRF)
    详细解读简单的lstm的实例
    如何使用 Pylint 来规范 Python 代码风格
    Python下Json和Msgpack序列化比较
    除了cPickle,cjson外还有没有更高效点的序列化库了
    python对象序列化或持久化的方法
  • 原文地址:https://www.cnblogs.com/clayman/p/1732211.html
Copyright © 2020-2023  润新知