简单概述:
自定义assembler,通过修改传递到着色器的顶点数量,达成类似TiledSprite的平铺效果。
如图:
Assembler的作用是记录顶点数据,对于普通Sprite组件,一般是如下的顶点位置、顶点纹理坐标、顶点颜色
在渲染流中,Assembler主要参与了3个环节,分别是更新颜色、更新渲染数据、填充缓冲数据:
其中updateColor是由Sprite等渲染组件间接调用:
我们今天关心的就是其中updateRenderData这一环节。
updateRenderData,更新渲染数据,其实就是更新四个顶点的位置、纹理坐标。
最上面的笑脸图中,一个精灵变成对称平铺的2x2个精灵,4个顶点变9个顶点,2个三角形变8个三角形,我们首先需要更新顶点数量和顶点索引数量,以便创建足够大的缓冲区
row:number = 2;
col = 2;
/**把一张图变成平铺的row*col张 */
setGird(row:number,col:number){
this.row = row;
this.col = col;
this.verticesCount = (row+1)*(col+1);
this.indicesCount = 6*row*col;
}
然后我们处理顶点数据,首先是顶点索引,默认只有四个顶点的时候,分割成两个三角形,索引分别是{0,1,2},{1,2,3},这一部分在core/render/webgl/render-data.js下
但是我们现在有9个顶点,4个矩形,8个三角形,所以要重新划分
如上图,现在我们的三角形,从左到右,从下到上,应该是
{0,1,3}{1,3,4},{1,2,4},{2,4,5},{3,4,6},{4,6,7},{4,5,7},{5,7,8}
初始化创建顶点数据后,重新更新一下索引缓冲
/**更新顶点索引 */
_updateIndices () {
let indices = this._renderData.iDatas[0];
let indexOffset = 0;
let _col = this.col;
let _row = this.row;
for (let j = 0; j < _row; ++j) {
for (let i = 0; i < _col; ++i) {
let start = j * (_col+1) + i;
indices[indexOffset++] = start;
indices[indexOffset++] = start + 1;
indices[indexOffset++] = start + _col + 1;
indices[indexOffset++] = start + 1;
indices[indexOffset++] = start + _col + 1;
indices[indexOffset++] = start + 1 + _col + 1;
}
}
}
然后是更新顶点坐标,在assembler-2d.js中,是先计算出四个顶点的本地坐标,存到_local中,然后与世界矩阵相乘(native下没有这一步,矩阵变换好像是在c++端),得到四个顶点的世界坐标:
updateWorldVerts (comp) {
let local = this._local;
let verts = this._renderData.vDatas[0];
////省略若干行
//矩形四个角的本地坐标变换成世界坐标
// left bottom
verts[vertexOffset] = al + cb + tx;
verts[vertexOffset + 1] = bl + db + ty;
vertexOffset += floatsPerVert;
// right bottom
verts[vertexOffset] = ar + cb + tx;
verts[vertexOffset + 1] = br + db + ty;
vertexOffset += floatsPerVert;
// left top
verts[vertexOffset] = al + ct + tx;
verts[vertexOffset + 1] = bl + dt + ty;
vertexOffset += floatsPerVert;
// right top
verts[vertexOffset] = ar + ct + tx;
verts[vertexOffset + 1] = br + dt + ty;
我们要做的是一样的事情,只不过顶点多了一点而已
仍然是从左到右,从下到上,9个顶点的本地坐标为:
{-50,-50},{0,-50},{50,-50},{-50,0},{0,0},{50,0},{-50,50},{0,50},{50,50},
let vertexOffset = 0;
let _col = this.col;
let _row = this.row;
for(let j=0;j<=_row;j++){
let _y = vb + j/_row*(vt-vb);
for(let i=0;i<=_col;i++){
let _x = vl + i/_col*(vr-vl);
verts[vertexOffset] = a*_x + c*_y + tx;
verts[vertexOffset + 1] = b*_x + d*_y + ty;
vertexOffset += floatsPerVert;
}
}
以上代码的意义就是按照从左到右,从下到上的顺序遍历9个顶点
然后插值出当前顶点的本地坐标,然后模拟一下矩阵乘法,得出世界坐标。
计算纹理坐标时,遍历顺序要保持一致。
纹理有个要注意的点,因为有些顶点是两个或者三个矩形公用的,所以顶点纹理坐标要注意不能紊乱,对于同一个顶点,不能在一个矩形里预期采样纹理右上角,却在隔壁矩形预期采样纹理左上角。
应该如下如:
应该是左右对称、上下对称的。
可以找到规律,最下面第一行,从左往右依次是0,1,0,1,0……,第二行从左往右依次是2,3,2,3……
updateUVs (sprite) {
let uv = sprite._spriteFrame.uv;
let uvOffset = this.uvOffset;
let floatsPerVert = this.floatsPerVert;
let verts = this._renderData.vDatas[0];
let _col = this.col;
let _row = this.row;
let index = uvOffset;
for(let j=0;j<=_row;j++){
let arr = j%2==0?[0,1]:[2,3];
for(let i=0;i<=_col;i++){
let k = arr[i%2]
let srcOffset = k * 2;
verts[index] = uv[srcOffset];
verts[index + 1] = uv[srcOffset + 1];
index+=floatsPerVert;
}
}
}
按照同样的顺序遍历所有顶点,然后找出当前顶点使用原纹理哪个角的纹理,然后依次赋值u、v
以上基本就搞定了。
设置不同的行列:
实际价值比较有限,主要是学习用,加深我对顶点的理解。后面有空考虑做一个拼图小游戏,通过改变顶点数量可以实现图片分割成不同的多边形碎片。
下面是完整类:
import HYZSimpleSpriteAssembler from "./HYZSimpleSpriteAssembler";
export default class HYZAssemblerGird extends HYZSimpleSpriteAssembler {
verticesCount = 4;
indicesCount = 6;
init(comp) {
this.setGird(5,5)
super.init(comp)
this._updateIndices();
}
row:number = 2;
col = 2;
/**把一张图变成平铺的row*col张 */
setGird(row:number,col:number){
this.row = row;
this.col = col;
this.verticesCount = (row+1)*(col+1);
this.indicesCount = 6*row*col;
}
/**更新顶点索引 */
_updateIndices () {
let indices = this._renderData.iDatas[0];
let indexOffset = 0;
let _col = this.col;
let _row = this.row;
for (let j = 0; j < _row; ++j) {
for (let i = 0; i < _col; ++i) {
let start = j * (_col+1) + i;
indices[indexOffset++] = start;
indices[indexOffset++] = start + 1;
indices[indexOffset++] = start + _col + 1;
indices[indexOffset++] = start + 1;
indices[indexOffset++] = start + _col + 1;
indices[indexOffset++] = start + 1 + _col + 1;
}
}
}
updateWorldVertsWebGL(comp) {
let local = this._local;
let verts = this._renderData.vDatas[0];
let matrix = comp.node._worldMatrix;
let matrixm = matrix.m,
a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],
tx = matrixm[12], ty = matrixm[13];
let vl = local[0], vr = local[2],
vb = local[1], vt = local[3];
let floatsPerVert = this.floatsPerVert;
let vertexOffset = 0;
let _col = this.col;
let _row = this.row;
for(let j=0;j<=_row;j++){
let _y = vb + j/_row*(vt-vb);
for(let i=0;i<=_col;i++){
let _x = vl + i/_col*(vr-vl);
verts[vertexOffset] = a*_x + c*_y + tx;
verts[vertexOffset + 1] = b*_x + d*_y + ty;
vertexOffset += floatsPerVert;
}
}
}
updateWorldVertsNative(comp) {
let local = this._local;
let verts = this._renderData.vDatas[0];
let floatsPerVert = this.floatsPerVert;
let vl = local[0],
vr = local[2],
vb = local[1],
vt = local[3];
let index = 0;
let _col = this.col;
let _row = this.row;
for(let j=0;j<=_row;j++){
let _y = vb + j/_row*(vt-vb);
for(let i=0;i<=_col;i++){
let _x = vl + i/_col*(vr-vl);
verts[index] = _x;
verts[index+1] = _y;
index += floatsPerVert;
}
}
}
updateUVs (sprite) {
let uv = sprite._spriteFrame.uv;
let uvOffset = this.uvOffset;
let floatsPerVert = this.floatsPerVert;
let verts = this._renderData.vDatas[0];
let _col = this.col;
let _row = this.row;
let index = uvOffset;
for(let j=0;j<=_row;j++){
let arr = j%2==0?[0,1]:[2,3];
for(let i=0;i<=_col;i++){
let k = arr[i%2]
let srcOffset = k * 2;
verts[index] = uv[srcOffset];
verts[index + 1] = uv[srcOffset + 1];
index+=floatsPerVert;
}
}
}
}
其中HYZSimpleSpriteAssembler 是完全模仿core\renderer\webgl\assemblers\sprite\2d\simple.js实现的