原文链接: https://forum.cocos.org/t/demo/95087
作者: GT
排版整理: 白玉无冰
背景
自定义渲染可以实现很多酷炫的 shader 特效,目前常用的有两种方法:
创建自定义材质,给材质增加参数。这个参数会作为 uniform 变量传入 shader 由于渲染合批要求材质参数保持一致,所以如果大量对象使用自定义材质时,并且材质参数各不相同,是无法进行合批渲染的,一个对象占一个 draw call
创建自定义 assembler,在顶点数据输入渲染管道前修改它的值 这种方式比较灵活,如果需要输入更多自定义参数,标准的顶点格式就不够用了
本文介绍另一种方法,即能让shader获得自定义参数,又能让自定义材质合批渲染。这种方法就是自定义顶点格式 。
Assembler详解
Assembler
是实现本文相关功能的核心类,先简单回顾一下官方文档里介绍的内容 https://docs.cocos.com/creator/manual/zh/advanced-topics/custom-render.html
![](https://i-blog.csdnimg.cn/blog_migrate/136571624176fe822310cddaba1fffa9.png)
Assembler
中必须要定义updateRenderData
及fillBuffers
方法 前者需要更新准备顶点数据,后者则是将准备好的顶点数据填充进VetexBuffer
和IndiceBuffer
中
2D渲染中,Assember2D
类是一个重要的基础类,最常用的cc.Sprite
的各种模式(Simple,平铺,九宫格)在内部都对应了不同的Assembler
派生类。同样是一个四边形的节点,不同的Assembler
可以将其转化成不同数量的顶点实现不同的渲染效果
Simple模式下是常规的四边形,有4个顶点。(利用这个可以实现渐变色效果)
平铺模式下Assembler根据纹理的重复次数对节点进行“拆碎”,相当于每重复一次就产生1个四边形。(利用这个可以实现顶点动画之水纹旗子)
九宫格模式下Assembler将节点拆分为9个四边形,每个四边形对应纹理上的一个“格子”
fillBuffers源码解读
先看看Assembler2D
是如何实现 fillBuffers
的 源码位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/assembler-2d.js
fillBuffers (comp, renderer) {
// 如果节点的世界坐标发生变化,重新从当前节点的世界坐标计算一次顶点数据
if (renderer.worldMatDirty) {
this.updateWorldVerts(comp);
}
// 获取准备好的顶点数据
// vData包含pos、uv、color数据
// iData包含三角剖分后的顶点索引数据
let renderData = this._renderData;
let vData = renderData.vDatas[0];
let iData = renderData.iDatas[0];
// 获取顶点缓存
// getBuffer()方法后面会被我们重载,以便获得支持自定义顶点格式的缓存
let buffer = this.getBuffer(renderer);
// 获取当前节点的顶点数据对应最终buffer的偏移量
// 可以简单理解为当前节点和其他同格式节点的数据,都将按顺序追加到这个大buffer里
let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);
// fill vertices
let vertexOffset = offsetInfo.byteOffset >> 2,
vbuf = buffer._vData;
// 将准备好的vData拷贝到VetexBuffer里。这里会判断如果buffer装不下了,vData会被截断一部分
// 通常是因为节点数量太多导致的,从下个节点开始会使用新的buffer,也就是重新开一个合批
// 当前节点的数据被截断后,则只能被渲染一部分(推测)
if (vData.length + vertexOffset > vbuf.length) {
vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
} el