在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,
_renderData
和InputAssembler
都是数据容器,_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._models
中extractDrawItem
,从model 提取数据转化为drawitem, 再根据view上的stages,遍历每个drawItem
, 从drawItem.effect.getTechnique(stage)
中得到tech, 如果存在tech就把drawitem转化为具体stage的 stageitem
。最后调用_opaqueStage。
Base._draw
函数中,执行真正的渲染。Base._draw
根据Effect
,Technique
,Pass
的数据,得到shader
,并为shader
设置好webgl
状态和各个Uniform
变量,最后调用device.draw
完成一个渲染流程.// 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); };