cocos creator2.x渲染过程源码解析

  在cocos creator2.x引擎中,所有的渲染组件都是继承自 cc.RenderComponent,例如cc.Sprite,cc.Label 等。组件的 Assembler 主要负责组件数据的更新处理及填充,由于不同的渲染组件在数据内容及填充上也都不相同,所以每一个渲染组件都会对应拥有自己的 Assembler 对象,而所有的 Assembler 对象都是继承自 cc.Assembler。Material 作为资源,主要记录渲染组件的渲染状态,使用的纹理及 Shader。

一、首先分析Assembler,主要代码如下:

  

Assembler.register(Label, {
    getConstructor(label) {
        let ctor = TTF;
        
        if (label.font instanceof cc.BitmapFont) {
            ctor = Bmfont;
        } else if (label.cacheMode === Label.CacheMode.CHAR) {
            cc.warn('sorry, canvas mode does not support CHAR mode currently!');
        }

        return ctor;
    },

    TTF,
    Bmfont
});
export default class Assembler {
    constructor () {
        this._extendNative && this._extendNative();
    }
    // 负责初始化渲染数据及一些局部参数
    init (renderComp) {
        this._renderComp = renderComp;
    }
    // 负责在渲染组件的顶点数据有变化时进行更新修改
    updateRenderData (comp) {
    }
    // 负责在渲染时进行顶点数据的 Buffer 填充
    // 将顶点坐标数据、uv数据和颜色数据添加到buffer后。核心点!此处是_assembler、_renderData和InputAssembler三者发生关系的地方
    fillBuffers (comp, renderer) {
    }
    
    getVfmt () {
        return vfmtPosUvColor;
    }
}
// renderCompCtor 渲染组件类
//assembler  组件装配类

Assembler.register = function (renderCompCtor, assembler) {
    renderCompCtor.__assembler__ = assembler;// 注册组件装配类到渲染组件类
};

Assembler.init = function (renderComp) {
    let renderCompCtor = renderComp.constructor;
    let assemblerCtor =  renderCompCtor.__assembler__;//取得组件装配类
    while (!assemblerCtor) {
        renderCompCtor = renderCompCtor.$super;
        if (!renderCompCtor) {
            cc.warn(`Can not find assembler for render component : [${cc.js.getClassName(renderComp)}]`);
            return;
        }
        assemblerCtor =  renderCompCtor.__assembler__;
    }
    if (assemblerCtor.getConstructor) {
        assemblerCtor = assemblerCtor.getConstructor(renderComp);
    }
    
    if (!renderComp._assembler || renderComp._assembler.constructor !== assemblerCtor) {
        let assembler = assemblerPool.get(assemblerCtor);
        assembler.init(renderComp);// 调用具体组件装配实例对组件实例进行初始化
        renderComp._assembler = assembler;
    }
};

  渲染组件通过 Assembler.register注册到引擎中,比如图形渲染组件的注册代码为 Assembler.register(cc.Graphics, GraphicsAssembler),cc.Graphics为图形类,GraphicsAssembler继承自Assembler类,渲染组件持有_assembler,_assembler持有_renderData,_renderDataInputAssembler都是数据容器,_assembler是数据操作,_assembler可以创建和updateRenderData,更新verts,InputAssembler是在渲染时用到的,用于组织传入GPU的数据.

  

  在 v2.x 中,RenderFlow 会根据渲染过程中调用的频繁度划分出多个渲染状态,比如 Transform,Render,Children 等,而每个渲染状态都对应了一个函数。在 RenderFlow 的初始化过程中,会预先根据这些状态创建好对应的渲染分支,这些分支会把对应的状态依次链接在一起。例如如果一个节点在当前帧需要更新矩阵,以及需要渲染自己,那么这个节点会更新他的 flag 为node._renderFlag = RenderFlow.FLAG_TRANSFORM | RenderFlow.FLAG_RENDER。RenderFlow 在渲染这个节点的时候就会根据节点的 node._renderFlag 状态进入到 transform => render 分支,而不需要再进行多余的状态判断.

  cc.renderer对象,是一个全局对象,提供基础渲染接口的渲染器对象. 里面存放了一些渲染有关的类定义以及一些全局属性如device,InputAssembler,Pass等等, 核心的是两个属性,一个是_froward一个是_flow._flow是一个cc.RenderFlow类(在h5上是一个model-batcher实例).在初始化的过程中,会创建RenderFlow或model-batcher的实例,并传入_flow.init方法中.

initWebGL (canvas, opts) {
        require('./webgl/assemblers');
        const ModelBatcher = require('./webgl/model-batcher');

        this.Texture2D = gfx.Texture2D;
        this.canvas = canvas;
        this._flow = cc.RenderFlow;
        
        if (CC_JSB && CC_NATIVERENDERER) {
            // native codes will create an instance of Device, so just use the global instance.
            this.device = gfx.Device.getInstance();
            this.scene = new renderer.Scene();
            let builtins = _initBuiltins(this.device);
            this._forward = new renderer.ForwardRenderer(this.device, builtins);
            let nativeFlow = new renderer.RenderFlow(this.device, this.scene, this._forward);
            this._flow.init(nativeFlow);
        }
        else {
            let Scene = require('../../renderer/scene/scene');
            let ForwardRenderer = require('../../renderer/renderers/forward-renderer');
            this.device = new gfx.Device(canvas, opts);
            this.scene = new Scene();
            let builtins = _initBuiltins(this.device);
            this._forward = new ForwardRenderer(this.device, builtins);
            this._handle = new ModelBatcher(this.device, this.scene);
            this._flow.init(this._handle, this._forward);
        }
        config.addStage('shadowcast');
        config.addStage('opaque');
        config.addStage('transparent');
    }
render (ecScene, dt) {
        this.device.resetDrawCalls();
        if (ecScene) {
            // walk entity component scene to generate models
            this._flow.render(ecScene, dt);
            this.drawCalls = this.device.getDrawCalls();
        }
    },

// 初始化创建各种渲染流分支
// batcher 为model-batcher实例
//
forwardRenderer 为forward-renderer实例
RenderFlow.init = function (batcher, forwardRenderer) {
    _batcher = batcher;
    _forward = forwardRenderer;

    flows[0] = EMPTY_FLOW;
    for (let i = 1; i < FINAL; i++) {
        flows[i] = new RenderFlow();
    }
};


RenderFlow.render = function (scene, dt) {
    _batcher.reset();
    _batcher.walking = true;

    RenderFlow.visitRootNode(scene);

    _batcher.terminate();
    _batcher.walking = false;

    _forward.render(_batcher._renderScene, dt);
};

看一最终渲染调用栈:


渲染入口函数为ForwardRenderer.prototype.render,遍历所有RendererCamera,获取每个摄像机所关联的view放入viewpool, 然后 对viewpool按优先级排序。然后跳入Base._render,清除设备,从scene._modelsextractDrawItem,从model 提取数据转化为drawitem, 再根据view上的stages,遍历每个drawItemdrawItem.effect.getTechnique(stage)中得到tech, 如果存在tech就把drawitem转化为具体stage的 stageitem。最后调用_opaqueStage。

_opaqueStage设定下矩阵,又回到Base._draw函数中,执行真正的渲染。
Base._draw根据Effect,Technique,Pass的数据,得到shader,并为shader设置好webgl状态和各个Uniform变量,最后调用device.draw完成一个渲染流程.
 _stageInfos 代表每个stage的具体内容。
// render stages
    for (let i = 0; i < _stageInfos.length; ++i) {
      let info = _stageInfos.data[i];
      let fn = this._stage2fn[info.stage];

      fn(view, info.items);
    }

一共有三种stage:

this._registerStage('shadowcast', this._shadowStage.bind(this));
this._registerStage('opaque', this._opaqueStage.bind(this));
  this._registerStage('transparent',this._transparentStage.bind(this));
_registerStage(name, fn) {
    this._stage2fn[name] = fn;
  }

 比如 _opaqueStage函数内容:

_opaqueStage (view, items) {
    view.getPosition(_camPos);

    // update uniforms
    this._device.setUniform('cc_matView', mat4.array(_a16_view, view._matView));
    this._device.setUniform('cc_matpProj', mat4.array(_a16_proj, view._matProj));
    this._device.setUniform('cc_matViewProj', mat4.array(_a16_viewProj, view._matViewProj));
    this._device.setUniform('cc_cameraPos', vec4.array(_a4_camPos, _camPos));

    // update rendering
    this._submitLightsUniforms();
    this._submitOtherStagesUniforms();

    this._drawItems(view, items);
  }

二、RenderFlow是cocos 组织渲染数据的核心,这里再做进一步的源码分析补充:

RenderFlow是cocos实际渲染之前更新节点渲染数据的核心,它的作用是遍历所有场景节点,根据节点的_renderFlag来确定是否需要相关的操作,比如如果节点移动了,它就会设置this._renderFlag |= RenderFlow.FLAG_WORLD_TRANSFORM当遍历到该节点就会更新坐标矩阵,从而在后续的渲染中改变它在屏幕中显示的位置。

RenderFlow 定义如下:

function RenderFlow () {
    this._func = init;
    this._next = null;//指向下一次RenderFlow
}

可见RenderFlow是一个链表结构,按顺序对节点应用具体的func功能,更新节点相关的渲染数据,包括了本地坐标更新,世界坐标更新,opacity,color,更新渲染数据(updateRenderData),提交数据(_render),渲染子节点和渲染完子节点的一些后续操作。每个RenderFlow默认的_func是 函数init, 也是作用于任何一个节点第一个_func,它的主要作用是为节点创建一个正确的RenderFlow链,  函数init的定义如下:

function init (node) {
    let flag = node._renderFlag;// 取得节点的renderFlag
    let r = flows[flag] = getFlow(flag);//通过renderFlag创建具体的RenderFlag链
    r._func(node);//调用具体RenderFlow功能函数更新节点的渲染数据
}

getFlow 中通过node._renderFlag逐个与操作预定义好的flag,如果值为1,调用createFlow创建具体的RenderFlow,并形成一条RenderFlow链 ,定义如下:

function getFlow (flag) {
    let flow = null;
    let tFlag = FINAL;
    while (tFlag > 0) {
        if (tFlag & flag)//如果flag标识匹配,则添加新的渲染流
            flow = createFlow(tFlag, flow);// 需要把上一步创建flow传入,作为下一个流
        tFlag = tFlag >> 1;// 标志右移一位 匹配下一个flag
    }
    return flow;
}
function createFlow (flag, next) {
    let flow = new RenderFlow();
    flow._next = next || EMPTY_FLOW;// 将本次创建的flow加入链表首部
        // 根据不同的flag设置不同的处理方法
        switch (flag) {
        case DONOTHING:  // 1 << 0
            flow._func = flow._doNothing;
            break;    
        case BREAK_FLOW: // 1 << 1
            flow._func = flow._doNothing;
            break;
        case LOCAL_TRANSFORM: // 1 << 2 更新本地坐标转换矩阵
            flow._func = flow._localTransform;
            break;
        case WORLD_TRANSFORM: // 1 << 3 更新世界坐标转换矩阵
            flow._func = flow._worldTransform;
            break;
                case UPDATE_RENDER_DATA:// 1 << 4 更新渲染数据
            flow._func = flow._updateRenderData;
            break;
        case OPACITY: //1 << 5 更新透明度
            flow._func = flow._opacity;
            break;
        case COLOR://1 << 6 更新颜色
            flow._func = flow._color;
            break;
        case RENDER: //1 <<7 检测合批 填充数据
            flow._func = flow._render;
            break;
        case CHILDREN: //1 << 8 遍历子节点渲染流程
            flow._func = flow._children;
            break;
        case POST_RENDER: //1 << 9  RENDER后处理
            flow._func = flow._postRender;
            break;
    }
        return flow;
}

然后r._func(node)就是对node节点的渲染数据进行更新,比如本地坐标的变换功能_localTransform代码如下:

let _proto = RenderFlow.prototype;
//EMPTY_FLOW的_func就是_doNothing
_proto._doNothing = function () {
};

_proto._localTransform = function (node) {
    node._updateLocalMatrix();
    node._renderFlag &= ~LOCAL_TRANSFORM;
    this._next._func(node);
};
 


原文地址:https://www.cnblogs.com/kundij/p/12712502.html