五 ModelBatcher 数据合批
ModelBatcher : 用以管理渲染数据model,渲染批次合并,从而减少drawcall,提升性能。
var ModelBatcher = function (device, renderScene) { this._renderScene = renderScene; this._device = device; ... ... // buffers this._quadBuffer = this.getBuffer('quad', vfmtPosUvColor); this._meshBuffer = this.getBuffer('mesh', vfmtPosUvColor); this._quadBuffer3D = this.getBuffer('quad', vfmt3D); this._meshBuffer3D = this.getBuffer('mesh', vfmt3D); ... ... };
/
在 ModelBatcher 的初始化中,会持有仅包含渲染相关内容的场景数据:_renderScene,以及4个渲染数据 Buffer。2D图片渲染用到的就是其中的 _meshBuffer 。
5.1 ModelBatcher 的 _renderScene
class Scene { constructor(app) { this._lights = new FixedArray(16); this._models = new FixedArray(16); this._cameras = new FixedArray(16); this._debugCamera = null; this._app = app; this._views = []; } ... ... }
ModelBatcher 中的 _renderScene 的类型是定义于 cocos2d\renderer\scene\scene.js 的Scene类。所有渲染批次数据都会保存到 _models 数组内,_lights 是所有灯光,_cameras 是所有的摄像机,这些暂时不具体了解。
RenderScene 中的数据会在 ForwardRender 中,将数据渲染到屏幕的过程中使用,具体内容在 ForwardRender 的详解中细讲。
5.2 ModelBatcher 与 Model
// CCRenderComponent 中的 _checkBatch 检测合批方法
_checkBacth (renderer, cullingMask) { let material = this._materials[0]; if ((material && material.getHash() !== renderer.material.getHash()) || renderer.cullingMask !== cullingMask) { renderer._flush(); renderer.node = material.getDefine('CC_USE_MODEL') ? this.node : renderer._dummyNode; renderer.material = material; renderer.cullingMask = cullingMask; } } <p data-line="185" class="sync-line" style="margin:0;"></p>
在渲染流程中,节点的遍历方式为深度优先遍历。在 CCRenderComponent 的 _checkBatch 检测合批方法中,如果发现2个使用的材质的hash值不同,会调用_flush方法创建一个新的渲染批次数据 Model.
// ModelBatcher 的 _flush 方法
_flush () { ... ... // 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(); },
ModelBatcher 调用 _flush 方法,生成一个新的 Model 数据,并且加入了_renderScene 中。一个Model 数据就是一个渲染批次,所以Model 数据越多,drawcall 越多,这也就导致背包等有多个item的应用场景下,导致卡顿。
Model 的定义如下代码片段,其中需要重点了解的就是 _inputAssembler 变量。在 _flash 方法中看到,model.setInputAssembler(ia) 方法设置了 _inputAssembler 。ia 就是InpputAssembler类型的变量,且所有参数也在上面初始化了。需要注意到:
ia._vertexBuffer 是 this._meshBuffer._vb 的引用。
ia._indexBuffer 是 this._meshBuffer._ib 的引用。
ia._start是顶点索引的开始位置。
ia._count是顶点索引的个数。
// Model 数据的结构 export default class Model { constructor() { this._type = 'default'; this._poolID = -1; // 对象池id this._node = null; // 节点 this._inputAssembler = null; // InputAssembler 包含渲染数据 this._effect = null; // Effect shader this._viewID = -1; this._cameraID = -1; this._userKey = -1; this._castShadow = false; this._boundingShape = null; } ...... } <p data-line="235" class="sync-line" style="margin:0;"></p>
在 RenderFlow 的 _render 方法中, _checkBacth 合批完成后,会调用 fillBuffers 填充数据,将Assembler._renderData 中的数据填充到 buffer 中。以Assembler2D 的 fillBuffers 为例,代码如下。
fillBuffers (comp, renderer) { if (renderer.worldMatDirty) { this.updateWorldVerts(comp); } let renderData = this._renderData; let vData = renderData.vDatas[0]; let iData = renderData.iDatas[0]; let buffer = this.getBuffer(renderer); let offsetInfo = buffer.request(this.verticesCount, this.indicesCount); // fill vertices let vertexOffset = offsetInfo.byteOffset >> 2, vbuf = buffer._vData; if (vData.length + vertexOffset > vbuf.length) { vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset); } else { vbuf.set(vData, vertexOffset); } // fill indices let ibuf = buffer._iData, indiceOffset = offsetInfo.indiceOffset, vertexId = offsetInfo.vertexOffset; for (let i = 0, l = iData.length; i < l; i++) { ibuf[indiceOffset++] = vertexId + iData[i]; } }
在下面的 getBuffer 代码中发现,其实所有的 Assembler2D 的数据都会填充到 ModelBatcher 里的 MeshBuffer 中。所以其实MeshBuffer 中保存着所有的顶点数据,包括不同的纹理。Model中保存对 MeshBuffer 的引用和该渲染批次用的顶点数据的下标。
getBuffer () { return cc.renderer._handle._meshBuffer; }
七.ForwardRender
ForwardRender 继承于 Base, 是与底层渲染最靠近的类型,当上面的流程处理完毕后,会在ForwardRender 的 render() 中处理当前场景的渲染状态,材质,光照,通道,着色器,更新着色器的统一变量。并在 _draw() 中调用 device.draw()方法,进行绘制。
7.1 ForwardRender 的成员变量
部分重要的继承于 Base 的成员变量:
_device:根据运行平台对应的绘制图形对象 gfx.Device 的实例,用于绘制图形到屏幕,类型定义于 cocos2d\renderer\gfx\index.js。
_programLib :管理 shader 定义,获取,检查等相关的变量。类型定义于 cocos2d\renderer\core\program-lib.js。
_stage2fn:保存有不同渲染通道的名称与其对应的不同渲染方法。ForwardRender 中设置有 shadowcast, opaque, transparent 三种渲染通道。
_viewPools:单个相机的描述数据类(View) 的对象池。一个View对应一个相机。
_drawItemsPools:渲染数据类的对象池,保存有每个渲染批次需要的model,effect 等数据。
_stageItemsPools:单个渲染通道需要渲染的数据的对象池,本质是对 _drawItemsPools 中的数据按照不同通道进行了分类。
ForwardRender 中定义的成员变量:
_lights:保存所有灯光数据。
_shadowLights:保存所有阴影灯光数据。
7.2 渲染通道
类名 ForwardRender 翻译为前向渲染,泛指传统上只有 Opaque 和 Transparent 两个通道的渲染技术。cocos有三个渲染通道,渲染通道方法定义在 _stage2fn 中。
shadowcast:阴影渲染。
opaque:不透明,从前往后按顺序渲染。
transparent :透明,从后往前按顺序渲染。
渲染管线具体详解请参考unity官方文档(对的,真要学cocos还得看unity的文档):
内置渲染管线中的渲染路径:
https://docs.unity.cn/cn/2019.4/Manual/RenderingPaths.html