cocos 源码阅读(二:RenderFlow 渲染流)

 一: Render-flow基本构成

从CCDirector.js中的mainLoop我们了解到 renderer.render是实际参与渲染的方法:顺着线索我们找到了RenderFlow这个类:下面是我整理的部分关键的方法和属性:

首先看一下initWebGL方法:

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);
            // 前向渲染对象负责将view视图通过device渲染都屏幕上
            this._forward = new ForwardRenderer(this.device, builtins);
            // 渲染前批处理,优化性能,降低drawcall
            this._handle = new ModelBatcher(this.device, this.scene);
            // 初始化渲染流对象依赖 前向渲染对象和批处理
            this._flow.init(this._handle, this._forward);

            console.log(`scene is `,this.scene,' and device is ',this.device,' and _flow is ',this._flow,' and _handle is ',this._handle);
        }
    },

再来看看主角方法render(scene,dt):

render (ecScene, dt) {
        /** 重置drawcall */
        this.device.resetDrawCalls();
        if (ecScene) {
            // walk entity component scene to generate models
            /** 调用渲染静态函数 */
            this._flow.render(ecScene, dt);
            this.drawCalls = this.device.getDrawCalls();
        }
    },

 继续跟进看看render: 下一节着重看看这个前向渲染 _forward

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

    /** 递归遍历根节点 */
    RenderFlow.visitRootNode(rootNode);
    // 从根节点深度遍历完毕,开始执行批处理
    _batcher.terminate();
    _batcher.walking = false;
    // 将batcher中的渲染数据渲染到屏幕 _forward渲染数据需要用到合批的渲染数据两个类有相互依赖的关系
    _forward.render(_batcher._renderScene, dt);
};

重点看看批处理的东西:model-batcher.js

terminate() {
        if (cc.dynamicAtlasManager && cc.dynamicAtlasManager.enabled) {
            cc.dynamicAtlasManager.update();
        }

        // flush current rest Model
        this._flush();

        for (let key in _buffers) {
            _buffers[key].uploadData();
        }

        this.walking = false;
    },

跟踪到此查看_flush()

/***
     * 
     * 强制刷新缓冲区数据
     * 
     */
    _flush() {
        let material = this.material,
            buffer = this._buffer,
            indiceCount = buffer.indiceOffset - buffer.indiceStart;
        if (!this.walking || !material || indiceCount <= 0) {
            return;
        }

        let effect = material.effect;
        if (!effect) return;

        // Generate ia
        let ia = this._iaPool.add();
        // 顶点数据缓冲区
        ia._vertexBuffer = buffer._vb;
        // 顶点索引缓冲区
        ia._indexBuffer = buffer._ib;
        // 顶点索引开始索引
        ia._start = buffer.indiceStart;
        // 顶点索引个数
        ia._count = indiceCount;

        // Generate model
        let model = this._modelPool.add();
        this._batchedModels.push(model);
        model.sortKey = this._sortKey++;
        model._cullingMask = this.cullingMask;
        model.setNode(this.node);
        model.setEffect(effect);
        model.setInputAssembler(ia);

        this._renderScene.addModel(model);
        buffer.forwardIndiceStartToOffset();
    },

关于vb和ib想必大家都知道什么意思,下面看一下一个顶点数据包含哪些数据:

 

var vfmtPosUvColor = new gfx.VertexFormat([
    { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
]);

 ​​​​​​​

 

 

看看渲染流的init静态方法:

RenderFlow.init = function (batcher, forwardRenderer) {
    _batcher = batcher;
    _forward = forwardRenderer;
    
    // 这里flows是一个包含了1025个RenderFlow对象,而每一个对象又是一个渲染链表对象
    flows[0] = EMPTY_FLOW;
    for (let i = 1; i < FINAL; i++) {
        flows[i] = new RenderFlow();
    }
};

 看看init方法:渲染流真正执行_func的时候会调用它,是根据节点的渲染标识进行创建的

function init (node) {
    // 拿到节点的渲染标识
    let flag = node._renderFlag;
    // 根据节点身上的渲染标识进行创建渲染流
    let r = flows[flag] = getFlow(flag);
    // 执行对应的渲染流函数
    r._func(node);
}

看看getFlow函数,就是在这里创建了渲染流链表:

function getFlow (flag) {
    let flow = null;
    let tFlag = FINAL;
    while (tFlag > 0) {
        if (tFlag & flag)
            // 创建渲染流 将上一个flow传入构成一个链
            flow = createFlow(tFlag, flow);
        tFlag = tFlag >> 1;
    }
    return flow;
}

每一次createFlow都会将当前的flow置为头节点

function createFlow (flag, next) {
    let flow = new RenderFlow();
    // 将当前创建的渲染流置于链表的头部
    flow._next = next || EMPTY_FLOW;

    switch (flag) {
        case DONOTHING: 
            flow._func = flow._doNothing;
            break;
        case BREAK_FLOW:
            flow._func = flow._doNothing;
            break;
        case LOCAL_TRANSFORM: 
            flow._func = flow._localTransform;
            break;
        case WORLD_TRANSFORM: 
            flow._func = flow._worldTransform;
            break;
        case OPACITY:
            flow._func = flow._opacity;
            break;
        case COLOR:
            flow._func = flow._color;
            break;
        case UPDATE_RENDER_DATA:
            flow._func = flow._updateRenderData;
            break;
        case RENDER: 
            flow._func = flow._render;
            break;
        case CHILDREN: 
            flow._func = flow._children;
            break;
        case POST_RENDER: 
            flow._func = flow._postRender;
            break;
    }

    return flow;
}

二:节点渲染顺序是怎样的

节点在渲染的时候的渲染顺序在CCNode.js文件里面 源码如下:

_onBatchCreated(dontSyncChildPrefab) {
        this._initProperties();

        // Fixed a bug where children and parent node groups were forced to synchronize, instead of only synchronizing `_cullingMask` value
        this._cullingMask = 1 << _getActualGroupIndex(this);
        if (CC_JSB && CC_NATIVERENDERER) {
            this._proxy && this._proxy.updateCullingMask();
        }

        if (!this._activeInHierarchy) {
            if (CC_EDITOR ? cc.director.getActionManager() : ActionManagerExist) {
                // deactivate ActionManager and EventManager by default
                cc.director.getActionManager().pauseTarget(this);
            }
            eventManager.pauseTarget(this);
        }

        let children = this._children;
        // 采用深度遍历的方式遍历子节点最后才是根节点
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i];
            if (!dontSyncChildPrefab) {
                // sync child prefab
                let prefabInfo = child._prefab;
                if (prefabInfo && prefabInfo.sync && prefabInfo.root === child) {
                    PrefabHelper.syncWithPrefab(child);
                }
                child._updateOrderOfArrival();
            }
            child._onBatchCreated(dontSyncChildPrefab);
        }

        if (children.length > 0) {
            // 如果该节点有子节点就加一个渲染标记
            this._renderFlag |= RenderFlow.FLAG_CHILDREN;
        }

        if (CC_JSB && CC_NATIVERENDERER) {
            this._proxy.initNative();
        }
    },

我们可以清楚的看到节点遍历方式是深度遍历的方式。 

这时候你应该会问这个方法在哪里调用呢:主要是两个地方调用:CCScene.js,base-node.js

CCScene.js:  这个是在编辑器环境下生效

_load: function () {
        if (!this._inited) {
            if (CC_TEST) {
                cc.assert(!this._activeInHierarchy, 'Should deactivate ActionManager and EventManager by default');
            }
            this._onBatchCreated(CC_EDITOR && this._prefabSyncedInLiveReload);
            this._inited = true;
        }
    },

base-node.js: 

_instantiate (cloned, isSyncedNode) {
        if (!cloned) {
            cloned = cc.instantiate._clone(this, this);
        }

        var newPrefabInfo = cloned._prefab;
        if (CC_EDITOR && newPrefabInfo) {
            if (cloned === newPrefabInfo.root) {
                newPrefabInfo.fileId = '';
            }
            else {
                var PrefabUtils = Editor.require('scene://utils/prefab');
                PrefabUtils.unlinkPrefab(cloned);
            }
        }
        if (CC_EDITOR && cc.engine._isPlaying) {
            let syncing = newPrefabInfo && cloned === newPrefabInfo.root && newPrefabInfo.sync;
            if (!syncing) {
                cloned._name += ' (Clone)';
            }
        }

        // reset and init
        cloned._parent = null;
        cloned._onBatchCreated(isSyncedNode);

        return cloned;
    },

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值