前言
加入遇到了这样的一个需求,我们有一个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,444】
第二个实例:【444+1,4442】
第三个实例:【4442+1,4443】
第四个实例:【4443+1,4444】
第五个实例:【4444+,4445】
这里的stride偏移是4*16,就是一个矩阵的大小,我的理解是,既然是实例化绘制,那么绘制的过程基本上是一致的,只是取值的变化,这里的值是矩阵和颜色,我们第一次会给矩阵的每一行赋值,直到一个矩阵的四行全部绘制完成