Assembler自定义顶点数量

简单概述:
自定义assembler,通过修改传递到着色器的顶点数量,达成类似TiledSprite的平铺效果。
如图:

image.png

Assembler的作用是记录顶点数据,对于普通Sprite组件,一般是如下的顶点位置、顶点纹理坐标、顶点颜色

image.png

在渲染流中,Assembler主要参与了3个环节,分别是更新颜色、更新渲染数据、填充缓冲数据:


image.png

其中updateColor是由Sprite等渲染组件间接调用:


image.png

我们今天关心的就是其中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下


image.png

但是我们现在有9个顶点,4个矩形,8个三角形,所以要重新划分


image.png

如上图,现在我们的三角形,从左到右,从下到上,应该是
{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;
       

我们要做的是一样的事情,只不过顶点多了一点而已


image.png

仍然是从左到右,从下到上,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个顶点
然后插值出当前顶点的本地坐标,然后模拟一下矩阵乘法,得出世界坐标。

计算纹理坐标时,遍历顺序要保持一致。

纹理有个要注意的点,因为有些顶点是两个或者三个矩形公用的,所以顶点纹理坐标要注意不能紊乱,对于同一个顶点,不能在一个矩形里预期采样纹理右上角,却在隔壁矩形预期采样纹理左上角。
应该如下如:


image.png

应该是左右对称、上下对称的。
可以找到规律,最下面第一行,从左往右依次是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

以上基本就搞定了。
设置不同的行列:


image.png

实际价值比较有限,主要是学习用,加深我对顶点的理解。后面有空考虑做一个拼图小游戏,通过改变顶点数量可以实现图片分割成不同的多边形碎片。

下面是完整类:

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实现的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容