• 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);
    };
     


  • 相关阅读:
    使用GetLogicalProcessorInformation获取逻辑处理器的详细信息(NUMA节点数、物理CPU数、CPU核心数、逻辑CPU数、各级Cache)
    Android学习-应用程序管理
    用户过2亿获取每个用户不到6分钱,闪传是怎么做到?(最大的成本是决策成本,否则全是无用功)
    Delphi应用程序的调试(十)调试器选项(在IDE中不要使用异常)
    无标题窗体拖动(三种方法)
    关于Qt在子线程中使用QMessageBox的折衷方法
    Qt自定义事件的实现(军队真正干活,但要增加监军,大平台通知事件,事件内容自定义)
    java对数据库的操作
    ddd
    伟大是熬出来的
  • 原文地址:https://www.cnblogs.com/kundij/p/12712502.html
Copyright © 2020-2023  润新知