WebGL2系列之实例数组(Instanced Arrays)

实例化数组

实例化是一种只挪用一次衬着函数却能绘制出许多物体的手艺,它节约衬着一个物体时从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,终究绘制的结果以下:

《WebGL2系列之实例数组(Instanced Arrays)》

可以看出,一次绘制挪用,绘制出了100个对象;
假如经由过程WebGL1的体式格局须要遍历100次绘制。因而可以看出削减了绘制的遍历。
固然假如只是绘制100个四边形,遍历要领也没什么不好,实例化的威力重要体现在,当数据量变到很大的时刻,比方在笔者电脑上,把count值改成4000,那末会绘制4000 * 4000 = 一千六百万个四边形,以下:

《WebGL2系列之实例数组(Instanced Arrays)》
可以看出,照样可以很好的绘制出来(虽然因为对象太多,已看不清楚界线)
而采纳WebGL1 轮回遍历的体式格局,预计最多也就可以到达万级别的绘制轮回数目,万万级别的数目几乎不可设想。
固然这个数目 也是有限定的,比方在笔者的机械上,把count改成5000,也就是5000 * 5000 = 两千五百万的时刻,机械就奔溃了。

《WebGL2系列之实例数组(Instanced Arrays)》

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彪叔
《WebGL2系列之实例数组(Instanced Arrays)》

    原文作者:netcy
    原文地址: https://segmentfault.com/a/1190000017048578
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞