webgl1实现实例化绘制多个物体

前言

加入遇到了这样的一个需求,我们有一个2d的物体,我们的需求是在屏幕上去显示若干个这样的物体,只是每个物体的除了顶点一样之外,这些2d物体还会进行旋转,缩放,平移,还有就是颜色的变化
解决这个问题:
只发送一次绘制来渲染所有物体
由于物体的顶点都是相同的,所以我们将顶点发往GPU的显存中存着等待调用,关于旋转,缩放,平移,只不过是空间坐标系在变化,颜色我们另外传值就好

开始代码讲解

shader代码如下:

var vertexshader3d =
    'attribute vec4 a_position;' +
    'attribute vec4 color;' +
    //下面这个变量是我们的重点
    'attribute mat4 matrix;' +
    
    'uniform mat4 u_MVMatrix;' +
    'uniform mat4 u_PMatrix;' +

    'varying vec4 v_color;' +
    'void main() {' +
    'gl_Position =u_PMatrix*u_MVMatrix* matrix * a_position;' +
    'v_color = color;' +
    '}'
var fragmentshader3d =
    'precision mediump float;' +
    'varying vec4 v_color;' +
    'void main() {' +
    'gl_FragColor = v_color;' +
    '}'

获取webgl1.0实例化绘制的上下文

const ext = gl.getExtension('ANGLE_instanced_arrays');

在shader中获取属性的位置

  const colorLoc = this._shader.getCustomAttributeLocation("color");
  const matrixLoc = this._shader.getCustomAttributeLocation('matrix');

为matrix分配好在显存中存储数据的字节数组

 // setup matrixes, one per instance
        this.numInstances = 10;
        // make a typed array with one view per matrix
        //下面这句话的意思是创建一个指定大小的数组
        //每一个矩阵是4行4列,也就是由16个数据,每一个数据的大小是4个字节
        //每一个实例就有一个矩阵,所以是实例的数目乘以16
        this.matrixData = new Float32Array(this.numInstances * 16);
        this.matrices = [];
        //下面这个函数的意思就是说,matrices会有四个元素
        /*
        0:[0-16*4*1]--->其实就是从1到第16个顶点,只是每个顶点是4个字节而已
        1:[16*4*1+1-16*4*2]--->其实就是从第17个顶点到第32个顶点,只是每个顶点是4个字节而已
        2:[16*4*2+1-16*4*3]--->其实就是从第33个顶点到第48个顶点,只是每个顶点是4个字节而已
        3:[16*4*3+1-16*4*4]--->其实就是从第49个顶点到第64个顶点,只是每个顶点是4个字节而已
        */
        for (let i = 0; i < this.numInstances; ++i) {
            const byteOffsetToMatrix = i * 16 * 4;
            const numFloatsForView = 16;
            this.matrices.push(new Float32Array(
                this.matrixData.buffer,
                byteOffsetToMatrix,
                numFloatsForView));
        }
        this.matrixBuffer = gl.createBuffer();
        GLapi.bindBuffer(gl.ARRAY_BUFFER, this.matrixBuffer);
        // just allocate the buffer
        GLapi.bufferDataLength(gl.ARRAY_BUFFER, this.matrixData.byteLength, gl.DYNAMIC_DRAW);

设置颜色数据

 // setup colors, one per instance
        this.colorBuffer = gl.createBuffer();
        GLapi.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
        var colorData = [];
        for(var j=0;j<this.numInstances;j++)
        {
            var res = this.getRandowColor();
            colorData.push(res[0]);
            colorData.push(res[1]);
            colorData.push(res[2]);
            colorData.push(res[3]);
        }
        GLapi.bufferData(gl.ARRAY_BUFFER,
            new Float32Array(colorData),
            gl.STATIC_DRAW);

更新空间坐标系

 // update all the matrices
        this.matrices.forEach((mat, ndx) => {
            this._glMatrix.mat4.identity(mat);
            if(Math.random()>0.5)
            this._glMatrix.mat4.translate(mat, mat, [-0.5 + ndx * 0.025, 0, 0]);
            else
            this._glMatrix.mat4.translate(mat, mat, [-0.5,-0.5 + ndx * 0.025, 0]);

            this._glMatrix.mat4.rotateZ(mat, mat, time * (0.1 + 0.1 * ndx));
        });

绑定矩阵数据并且告诉GPU每个实例该如何读取数据

// upload the new matrix data
        GLapi.bindBuffer(gl.ARRAY_BUFFER, this.matrixBuffer);
        GLapi.bufferSubData(gl.ARRAY_BUFFER, 0, this.matrixData);
        // set all 4 attributes for matrix
        const bytesPerMatrix = 4 * 16;
        for (let i = 0; i < 4; ++i) {
            const loc = matrixLoc + i;
            gl.enableVertexAttribArray(loc);
            // note the stride and offset
            const offset = i * 16;  // 4 floats per row, 4 bytes per float
            GLapi.vertexAttribPointer(
                loc,              // location
                4,                // size (num values to pull from buffer per iteration)
                gl.FLOAT,         // type of data in buffer
                false,            // normalize
                bytesPerMatrix,   // stride, num bytes to advance to get to next set of values
                offset,           // offset in buffer
            );
            // this line says this attribute only changes for each 1 instance
            ext.vertexAttribDivisorANGLE(loc, 1);
        }

绑定颜色缓冲并且告诉GPU每个实例该如何读取颜色数据

 // set attribute for color
        gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
        gl.enableVertexAttribArray(colorLoc);
        gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
        // this line says this attribute only changes for each 1 instance
        ext.vertexAttribDivisorANGLE(colorLoc, 1);

开始绘制

 ext.drawArraysInstancedANGLE(
            gl.TRIANGLES,
            0,             // offset
            numVertices,   // num vertices per instance
            this.numInstances,  // num instances
        );

模型

假设我们的模型是用下面的顶点造的,经过我的测试发现,如果要绘制10000个这样的模型,使用实例化绘制,帧数依然可以维持在60帧,如果不使用,页面有时会直接奔溃了,帧数在30以下

 var positions = [-0.1, 0.4, z,
        -0.1, -0.4, z,
            0.1, -0.4, z,
            0.1, -0.4, z,
        -0.1, 0.4, z,
            0.1, 0.4, z,
            0.4, -0.1, z,
        -0.4, -0.1, z,
        -0.4, 0.1, z,
        -0.4, 0.1, z,
            0.4, -0.1, z,
            0.4, 0.1, z,
        ]

总结:

1:关于attribute属性中,如果声明变量的类型的mat4,那么变量的地址将是

var pos = this._shader.getCustomAttributeLocation('matrix');
pos+0,pos+1,pos+2,pos+3

2:务必了解gl.vertexAttribPointer(index, size, type, normalized, stride, offset)这个函数
对于顶点属性的变量用法:
上传数据阶段:绑定缓冲,发送数据到显存
使用数据阶段:绑定缓冲,激活属性变量的位置,然后再告诉GPU如何数据给读取属性变量,这个如何读取数据就是vertexAttribPointer它干的事情了
参数列表:
index:shader中变量的位置,
size:从当前指定的bufferdata中读取多少个数据赋值给每个变量的,注意它的单位不是字节,只是数据的数量,一般不会超过4,一维数组,二维数组,三维数组,四维数组
type:单个数据的类型,上边的gl.FLOAT表示用四个字节来表示的浮点数
normalized:是否进行归一化,就是将坐标尺寸转换到【-1,1】的空间里
stride:对于当前位置的bufferdata里,偏移多少是下一组顶点属性的值,相邻两个顶点之间的偏移
offset:表示从当前指定的bufferdata的什么位置开始读数据
3:假设绘制的实例数量是5:
那么构建的数组的大小就是544,数组元素占的字节数是4
我们将它传到GPU显存,当使用它的时候
总的字节数【0,5444】
第一个实例:【0,4
44】
第二个实例:【4
44+1,4442】
第三个实例:【4442+1,4443】
第四个实例:【4443+1,4444】
第五个实例:【4444+,4445】
这里的stride偏移是4*16,就是一个矩阵的大小,我的理解是,既然是实例化绘制,那么绘制的过程基本上是一致的,只是取值的变化,这里的值是矩阵和颜色,我们第一次会给矩阵的每一行赋值,直到一个矩阵的四行全部绘制完成

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值