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


  • 相关阅读:
    解决xcode5升级后,Undefined symbols for architecture arm64:问题
    第8章 Foundation Kit介绍
    app 之间发送文件 ios
    iphone怎么检测屏幕是否被点亮 (用UIApplication的Delegate)
    CRM下载对象一直处于Wait状态的原因
    错误消息Customer classification does not exist when downloading
    How to resolve error message Distribution channel is not allowed for sales
    ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼
    有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中
    SAP GUI和Windows注册表
  • 原文地址:https://www.cnblogs.com/kundij/p/12712502.html
Copyright © 2020-2023  润新知