CocosCreator2.1.0渲染流程與shader

CocosCreator2.1.0版本正式支持導入3D模型

對於2.5D遊戲的開發來說有着重要意義

自己此前在寫捕魚遊戲時瞭解過自定義shader

並實現了4種不同的水波效果

但經過CocosCreator版本的不斷升級

尤其是1.10和2.0兩個版本

舊的渲染器被拋棄了

因此老的shader特效也全都不能用了

直到最近正好有時間,花了幾天把原先寫的特效升級到了最新的2.1.0版本

下面記錄一下自定義shader實現方法的改變

以及新的渲染器的理解

 

過往自定義shader的實現依賴

cc.gl

cc.GLProgram

cc.GLProgramState

CCSprite._sgNode

CCTexture2D.setTexParameters

這些統統都不能用了!

取而代之的是新的渲染結構

顯然又是多層封裝咯

最上層的material關聯到sprite組件

最底層的pass關聯到具體的vert和frag着色器代碼,也就是Shader層

其實Shader層才應該是最底層的

從底向上一層層來看

 

Shader

系統默認的shader是通過以下方法保存在lib對象中的

ProgramLib.prototype.define = function define (name, vert, frag, defines) {

而lib對象所在的位置比較奇怪,可能往後的版本會變更

cc.renderer._forward._programLib

着色器代碼也需要稍作修改

以往的CC_Texture0等固定變量都不存在了

 

Pass

Pass的構造函數傳入的name就是與着色器同名的name

所以Pass是直接關聯shader的

另外Pass還包含了混合參數、深度測試參數、模板測試參數等等

複製代碼

Base.prototype._draw = function _draw (item) {
    var this$1 = this;

  var device = this._device;
  var programLib = this._programLib;
  var node = item.node;
    var ia = item.ia;
    var effect = item.effect;
    var technique = item.technique;
    var defines = item.defines;

  // reset the pool
  // NOTE: we can use drawCounter optimize this
  // TODO: should be configurable
  _float2_pool.reset();
  _float3_pool.reset();
  _float4_pool.reset();
  _float9_pool.reset();
  _float16_pool.reset();
  _float64_pool.reset();
  _int2_pool.reset();
  _int3_pool.reset();
  _int4_pool.reset();
  _int64_pool.reset();

  // set common uniforms
  // TODO: try commit this depends on effect
  // {
  node.getWorldMatrix(_m4_tmp$2);
  device.setUniform('model', mat4.array(_float16_pool.add(), _m4_tmp$2));

  var inverse = mat3.invert(_m3_tmp$1, mat3.fromMat4(_m3_tmp$1, _m4_tmp$2));
  if (inverse) {
    mat3.transpose(_m3_tmp$1, inverse);
    device.setUniform('normalMatrix', mat3.array(_float9_pool.add(), _m3_tmp$1));
  }
  // }

  // set technique uniforms
  for (var i = 0; i < technique._parameters.length; ++i) {
    // 這裏遍歷technique._parameters
    // 再從effect找到參數的值
    // 因此參數必須在technique中聲明並同時在effect中定義
    // 若不在technique中聲明,則不會遍歷不會走到device.setUniform這一步
    
    var prop = technique._parameters[i];
    var param = effect.getProperty(prop.name);
    // 若未在effect中賦值,則從technique中找默認
    if (param === undefined) {
      param = prop.val;
    }
    // 默認也找不到,就給個該類型的default值
    if (param === undefined) {
      param = this$1._type2defaultValue[prop.type];
    }

    if (param === undefined) {
      console.warn(("Failed to set technique property " + (prop.name) + ", value not found."));
      continue;
    }

    if (
      prop.type === enums.PARAM_TEXTURE_2D ||
      prop.type === enums.PARAM_TEXTURE_CUBE
    ) {
      if (prop.size !== undefined) {
        if (prop.size !== param.length) {
          console.error(("The length of texture array (" + (param.length) + ") is not corrent(expect " + (prop.size) + ")."));
          continue;
        }
        var slots = _int64_pool.add();
        for (var index = 0; index < param.length; ++index) {
          slots[index] = this$1._allocTextuerUnit();
        }
        device.setTextureArray(prop.name, param, slots);
      } else {
        device.setTexture(prop.name, param, this$1._allocTextuerUnit());
      }
    } else {
      var convertedValue = (void 0);
      if (param instanceof Float32Array || param instanceof Int32Array) {
        convertedValue = param;
      }
      else if (prop.size !== undefined) {
        var convertArray = _type2uniformArrayValue[prop.type];
        if (convertArray.func === undefined) {
          console.error('Uniform array of color3/int3/float3/mat3 can not be supportted!');
          continue;
        }
        if (prop.size * convertArray.size > 64) {
          console.error('Uniform array is too long!');
          continue;
        }
        convertedValue = convertArray.func(param);
      } else {
        var convertFn = _type2uniformValue[prop.type];
        convertedValue = convertFn(param);
      }
      device.setUniform(prop.name, convertedValue);
    }
  }

  // for each pass
  for (var i$1 = 0; i$1 < technique._passes.length; ++i$1) {
    var pass = technique._passes[i$1];
    var count = ia.getPrimitiveCount();

    // set vertex buffer
    device.setVertexBuffer(0, ia._vertexBuffer);

    // set index buffer
    if (ia._indexBuffer) {
      device.setIndexBuffer(ia._indexBuffer);
    }

    // set primitive type
    device.setPrimitiveType(ia._primitiveType);

    // set program (通過pass裏保存的program名字找到着色器program!)
    var program = programLib.getProgram(pass._programName, defines);
    device.setProgram(program);

    // cull mode
    device.setCullMode(pass._cullMode);

    // blend
    if (pass._blend) {
      device.enableBlend();
      device.setBlendFuncSep(
        pass._blendSrc,
        pass._blendDst,
        pass._blendSrcAlpha,
        pass._blendDstAlpha
      );
      device.setBlendEqSep(
        pass._blendEq,
        pass._blendAlphaEq
      );
      device.setBlendColor32(pass._blendColor);
    }

    // depth test & write
    if (pass._depthTest) {
      device.enableDepthTest();
      device.setDepthFunc(pass._depthFunc);
    }
    if (pass._depthWrite) {
      device.enableDepthWrite();
    }

    // stencil
    if (pass._stencilTest) {
      device.enableStencilTest();

      // front
      device.setStencilFuncFront(
        pass._stencilFuncFront,
        pass._stencilRefFront,
        pass._stencilMaskFront
      );
      device.setStencilOpFront(
        pass._stencilFailOpFront,
        pass._stencilZFailOpFront,
        pass._stencilZPassOpFront,
        pass._stencilWriteMaskFront
      );

      // back
      device.setStencilFuncBack(
        pass._stencilFuncBack,
        pass._stencilRefBack,
        pass._stencilMaskBack
      );
      device.setStencilOpBack(
        pass._stencilFailOpBack,
        pass._stencilZFailOpBack,
        pass._stencilZPassOpBack,
        pass._stencilWriteMaskBack
      );
    }

    // draw pass
    device.draw(ia._start, count);

    this$1._resetTextuerUnit();
  }
};

複製代碼

 

Technique

Technique的構造函數如下

var Technique = function Technique(stages, parameters, passes, layer) {

stages不太瞭解

parameters聲明瞭注入shader代碼中的參數名和類型

未聲明的參數即使寫在shader裏面也是無法使用的

passes可以指定多個,是否意味着多次渲染

以下是默認的SpriteMaterial中的Technique

複製代碼

var mainTech = new renderer.Technique(
    ['transparent'],
    [
        { name: 'texture', type: renderer.PARAM_TEXTURE_2D },
        { name: 'color', type: renderer.PARAM_COLOR4 } ],
    [
        pass
    ]
);

複製代碼

可以看到只設置了兩個參數

因此在着色器中可以使用texture紋理採樣

同時使用節點顏色color

 

Effect

Effect的構造函數如下

var Effect = function Effect(techniques, properties, defines) {

以下是默認的SpriteMaterial中的Effect

複製代碼

this._effect = new renderer.Effect(
    [
        mainTech ],
    {
        'color': this._color
    },
    [
        { name: 'useTexture', value: true },
        { name: 'useModel', value: false },
        { name: 'alphaTest', value: false },
        { name: 'useColor', value: true } ]
);

複製代碼

在自定義材質中properties直接傳空對象{}即可

如果是不變的uniform參數可以在technique中賦值默認val

如果是變化的uniform參數,如time、衰減因子、點擊位置等等

通過以下方法來更新變量的值即可

Effect.prototype.setProperty = function setProperty (name, value) {

 

Material

自定義材質可以繼承自默認材質

也可以類比SpriteMaterial來寫

但我覺得那樣太麻煩了,直接繼承Material把幾個有用的參數填進去就行了

而材質與sprite的綁定也簡化爲兩行代碼

原本CCSprite._activateMaterial統統省去

當紋理和頂點信息不改變的情況下

我認爲以下兩句是可以省略的

this.markForUpdateRenderData(true);

this.markForRender(true);

並且在h5、微信、安卓原生平臺均驗證有效

複製代碼

class CustomMaterial extends cc.renderer.renderEngine.Material{
    constructor(name , vert , frag , uniforms = [] , defines = []){
        super(false);
        this.name = name
        let lib = cc.renderer._forward._programLib;
        !lib._templates[name] && lib.define(name, vert, frag, defines);
        this.init(name , uniforms);
    }
    use(sprite){
        // cc.dynamicAtlasManager.enabled = false;
        // 設置基本紋理和顏色
        let texture = sprite.spriteFrame.getTexture();
        let color = sprite.node.color
        this.setTexture(texture);
        this.setUniform('color' , { r: color.r / 255, g: color.g / 255, b: color.b / 255, a: sprite.node.opacity / 255 })
        this.updateHash();
        // 指定sprite的材質
        sprite._material = this;
        sprite._renderData._material = this;
        sprite._state = cc.Sprite.State.CUSTOM;
    }
    init(name , uniforms) {
        let renderer = cc.renderer.renderEngine.renderer;
        let gfx = cc.renderer.renderEngine.gfx;

        let pass = new renderer.Pass(name);
        pass.setDepth(false, false);
        pass.setCullMode(gfx.CULL_NONE);
        pass.setBlend(
            gfx.BLEND_FUNC_ADD,
            gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA,
            gfx.BLEND_FUNC_ADD,
            gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA
        );
        
        let mainTech = new renderer.Technique(
            ['transparent'],
            [
                ...uniforms,
                { name: 'texture', type: renderer.PARAM_TEXTURE_2D /*, val : '默認值'*/},
                { name: 'color', type: renderer.PARAM_COLOR4 /*, val : '默認值'*/},
            ],
            [pass]
        );

        this._texture = null;
        this._effect = this.effect = new renderer.Effect([mainTech], {}, []);
        this._mainTech = mainTech;
    }
};

複製代碼

 

Render

可渲染節點如包含CCSprite組件的node

渲染組件CCSprite繼承自RenderComponent

渲染組件onEnable時會爲node賦值渲染組件的索引

this.node._renderComponent = this;

CCDirector.mainLoop中發起渲染命令

RenderComponentWalker.visit遍歷場景節點

RenderFlow._children方法中會過濾點!active和全透明的節點

if (!c._activeInHierarchy || c._opacity === 0) continue;

RenderComponentWalker._commitComp中比較material的hash值

這也是updateHash的意義所在(若不調用updateHash很可能會報錯,比如當節點是首個渲染節點時)

若hash值相同會使用上一個材質(流水線操作)

複製代碼

    _commitComp (comp, assembler, cullingMask) {
        if (this.material._hash !== comp._material._hash || 
            this.cullingMask !== cullingMask) {
            this._flush();
    
            this.node = assembler.useModel ? comp.node : this._dummyNode;
            this.material = comp._material;
            this.cullingMask = cullingMask;
        }
    
        assembler.fillBuffers(comp, this);
    },

複製代碼

RenderComponentWalker._flush

Scene.prototype.addModel添加至渲染模型數組

Base.prototype._render會遍歷模型數組

顯然model中是包含material等全部渲染信息的

再由Base.prototype._draw渲染每一個顯示模型

最後由Device.prototype.draw調用opengl命令完成繪製~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章