实例化是一种只调用一次渲染函数却能绘制出不少物体的技术,它节省渲染一个物体时从CPU到GPU的通讯时间。
实例数组是这样的一个对象,使用它,能够把原来的的uniform变量转换成attribute变量,并且这个attribute变量对应的缓冲区能够被多个对象使用;这样在绘制的时候,能够减小webgl的调用次数。web
假设这样的一个场景:你须要绘制不少个形状相同的物体,可是每一个物体的颜色、位置却不同,一般的作法是这样的:数组
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变量的地址。webgl
而后,经过调用以下方法进行绘制:spa
gl.drawArraysInstanced(mode, first, count, instanceCount); gl.drawElementsInstanced(mode, count, type, offset, instanceCount);
这两个方法和 gl.drawArrays与gl.drawElements相似,不一样的是多了第四个参数 instanceCount,表示一次绘制多少个对象。
经过这个方法,便能实现一次调用绘制多个对象的目标。code
本案例 将一次绘制多个四边形,代码以下:orm
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);
首先定义一个变量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); //给缓冲区填充数据
接下来,为了把每一个四边形分开,咱们给每一个四边形定义一个偏移量(此处的偏移量能够至关于变换矩阵),在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; } `;
而后定义每一个对象的偏移量数据的数组:
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(aOffsetLocation, 1); 这一行,1表示指定每一个数据(定义每一个数据的长度为3个份量,长度为12 = 3 * 4(浮点数长度)) 被一个四边形所用,而每个四边形的绘制期间,attribute变量offset保持不变,这个uniform变量相似。
接下来,调用方法绘制多个实例,
// //////////////// // // 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中,能够经过扩展来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彪叔