实例化数组
实例化是一种只挪用一次衬着函数却能绘制出许多物体的手艺,它节约衬着一个物体时从CPU到GPU的通讯时刻。
实例数组是如许的一个对象,运用它,可以把本来的的uniform变量转换成attribute变量,而且这个attribute变量对应的缓冲区可以被多个对象运用;如许在绘制的时刻,可以削减webgl的挪用次数。
背景
假定如许的一个场景:你须要绘制许多个外形雷同的物体,然则每一个物体的色彩、位置却不一样,一般的做法是如许的:
for(var i = 0; i < amount_of_models_to_draw; i++)
{
doSomePreparations(); // bind VAO, bind Textures, set uniforms etc.
gl.drawArrays(gl.TRIANGLES, 0, amount_of_vertices);
}
然则这类做法的一个瑕玷是:当绘制的对象的数目庞大以后,实行的效力就会变的很慢了;这是因为每一次绘制的时刻,都须要挪用许多webgl 的许多要领,比方绑定VAO对象,绑定贴图,设置uniform变量,通知GPU从哪一个缓冲戋戋读取极点数据,以及从那里找到极点属性,一切这些都会是CPU和GPU的资本斲丧过量。
实例化
假如可以讲数据一次性发送给GPU,然后通知WebGL运用一个绘制函数,绘制多个物体,就会更轻易。这类手艺,就是实例化手艺。这类手艺的完成思绪,就是把底本的uniform变量,比方变更矩阵,变成attribute变量,然后把多个对象的矩阵数据,写在一同,然后建立一切矩阵的VBO对象(极点缓存区); 建立好缓冲区后,把一切对象的矩阵数据经由过程bufferData 上传到缓冲区中,这和一般的attribute变量的缓冲区没什么差别。
接下来,就是和一般的VBO差别的部份:该缓冲区可以在多个对象之间同享。每一个对象 取该缓冲区的一部份数据,作为attribute变量的值,要领以下:
gl.vertexAttribDivisor(index, divisor)
经由过程gl.vertexAttribDivisor要领指定缓冲区中的每一个值,用于多少个对象,比方divisor = 1,示意每一个值用于一个对象;假如divisor=2,示意一个值用于两个对象。 index示意的attribute变量的地点。
然后,经由过程挪用以下要领举行绘制:
gl.drawArraysInstanced(mode, first, count, instanceCount);
gl.drawElementsInstanced(mode, count, type, offset, instanceCount);
这两个要领和 gl.drawArrays与gl.drawElements相似,差别的是多了第四个参数 instanceCount,示意一次绘制多少个对象。
经由过程这个要领,便能完成一次挪用绘制多个对象的目的。
案例申明
代码展现
本案例 将一次绘制多个四边形,代码以下:
var count = 3000;
var positions = new Float32Array([
-1/count, 1/count, 0.0,
-1/count, -1/count, 0.0,
1/count, 1/count, 0.0,
1/count, -1/count, 0.0,
]);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
var colors = new Float32Array([
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 1.0,
]);
var colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(1);
var indices = new Uint8Array([
0,1,2,
2,1,3
]);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW); //给缓冲区添补数据
var offsetArray = [];
for(var i = 0;i < count;i ++){
for(var j = 0; j < count; j ++){
var x = ((i + 1) - count/2) / count * 4;
var y = ((j + 1) - count/2) / count * 4;
var z = 0;
offsetArray.push(x,y,z);
}
}
var offsets = new Float32Array(offsetArray)
var offsetBuffer = gl.createBuffer();
var aOffsetLocation = 2;
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
gl.enableVertexAttribArray(aOffsetLocation);
gl.vertexAttribPointer(aOffsetLocation, 3, gl.FLOAT, false, 12, 0);
gl.vertexAttribDivisor(aOffsetLocation, 1);
// ////////////////
// // DRAW
// ////////////////
gl.clear(gl.COLOR_BUFFER_BIT);// 清空色彩缓冲区
// // 绘制第一个三角形
gl.bindVertexArray(triangleArray);
gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);
定义四边形VBO、IBO数据
起首定义一个变量count,绘制四边形的个数为 count * count,也就是count 列 count行个四边形。 然后一下代码定义四边形的极点坐标、色彩和索引相干数据,这在WebGL1中屡次运用,不在赘述:
var positions = new Float32Array([
-1/count, 1/count, 0.0,
-1/count, -1/count, 0.0,
1/count, 1/count, 0.0,
1/count, -1/count, 0.0,
]);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
var colors = new Float32Array([
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 1.0,
]);
var colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(1);
var indices = new Uint8Array([
0,1,2,
2,1,3
]);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW); //给缓冲区添补数据
uniform变量改成attribute变量
接下来,为了把每一个四边形离开,我们给每一个四边形定义一个偏移量(此处的偏移量可以相当于变更矩阵),在WebGL1中,这个偏移量会以uniform变量的体式格局定义,然则在实例化的手艺下,该偏移量定义为attribute变量, layout(location=2) in vec4 offset:
var vsSource = `#version 300 es
......
layout(location=2) in vec4 offset;
......
void main() {
vColor = color;
gl_Position = position + offset;
}
`;
定义偏移量的数据及VBO
然后定义每一个对象的偏移量数据的数组:
for(var i = 0;i < count;i ++){
for(var j = 0; j < count; j ++){
var x = ((i + 1) - count/2) / count * 4 - 2/count;
var y = ((j + 1) - count/2) / count * 4 - 2/count;
var z = 0;
offsetArray.push(x,y,z);
}
}
这个偏移量,将会使一切的四边形,根据count 行 count 列分列。
定义了偏移量数组以后,建立响应的缓冲区和开启attribute变量:
var offsetBuffer = gl.createBuffer();
var aOffsetLocation = 2; // 偏移量attribute变量地点
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
gl.enableVertexAttribArray(aOffsetLocation); // 启用偏移量attribute变量从缓冲区取数据
gl.vertexAttribPointer(aOffsetLocation, 3, gl.FLOAT, false, 12, 0); // 定义每一个数据的长度为3个重量,长度为12 = 3 * 4(浮点数长度)。
gl.vertexAttribDivisor(aOffsetLocation, 1);
gl.vertexAttribDivisor
注重 gl.vertexAttribDivisor(aOffsetLocation, 1); 这一行,1示意指定每一个数据(定义每一个数据的长度为3个重量,长度为12 = 3 * 4(浮点数长度)) 被一个四边形所用,而每一个四边形的绘制时期,attribute变量offset坚持稳定,这个uniform变量相似。
gl.drawElementsInstanced 绘制多个实例
接下来,挪用要领绘制多个实例,
// ////////////////
// // DRAW
// ////////////////
gl.clear(gl.COLOR_BUFFER_BIT);// 清空色彩缓冲区
// // 绘制第一个三角形
gl.bindVertexArray(triangleArray);
gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);
gl.drawElementsInstanced 将会绘制count count个四边形的实例,须要注重的是,绘制实例的个数,不能多于attribute变量offset变量的对应的缓冲区的数据个数,前面代码offsetArray定义了countcount个数据(注重每一个数据有3个重量,所以数据个数不等于offsetArray数组长度),因而绘制的示例个数不能超过count * count 个,然则可以少于。
案例结果申明
假如把count 指定为10,终究绘制的结果以下:
可以看出,一次绘制挪用,绘制出了100个对象;
假如经由过程WebGL1的体式格局须要遍历100次绘制。因而可以看出削减了绘制的遍历。
固然假如只是绘制100个四边形,遍历要领也没什么不好,实例化的威力重要体现在,当数据量变到很大的时刻,比方在笔者电脑上,把count值改成4000,那末会绘制4000 * 4000 = 一千六百万个四边形,以下:
可以看出,照样可以很好的绘制出来(虽然因为对象太多,已看不清楚界线)
而采纳WebGL1 轮回遍历的体式格局,预计最多也就可以到达万级别的绘制轮回数目,万万级别的数目几乎不可设想。
固然这个数目 也是有限定的,比方在笔者的机械上,把count改成5000,也就是5000 * 5000 = 两千五百万的时刻,机械就奔溃了。
WebGL1 扩大
在WebGL1中,可以经由过程扩大来ANGLE_instanced_arrays来完成,相干函数以下:
var ext = gl.getExtension('ANGLE_instanced_arrays');
ext.vertexAttribDivisorANGLE(index, divisor);
ext.drawArraysInstancedANGLE(mode, first, count, primcount);
ext.drawElementsInstancedANGLE(mode, count, type, offset, primcount);
更多精彩内容,请关注民众号:ITman彪叔