码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-12-05 16:12:49      阅读:241      评论:0      收藏:0      [点我收藏+]

标签:多个   矩阵   资源   遍历   9.png   这一   png   10个   分数   

实例化数组

实例化是一种只调用一次渲染函数却能绘制出很多物体的技术,它节省渲染一个物体时从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

然后定义每个对象的偏移量数据的数组:
``` var offsetArray = [];
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定义了count*count个数据(注意每个数据有3个分量,所以数据个数不等于offsetArray数组长度),因此绘制的示例个数不能超过count * count 个,但是可以少于。
## 案例效果说明
如果把count 指定为10,最终绘制的效果如下:

![绘制10*10个示例](http://upload-images.jianshu.io/upload_images/6271001-70e41e9924b21c8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看出,一次绘制调用,绘制出了100个对象;
如果通过WebGL1的方式需要遍历100次绘制。因此可以看出减少了绘制的遍历。
当然如果只是绘制100个四边形,遍历方法也没什么不好,实例化的威力主要体现在,当数据量变到很大的时候,比如在笔者电脑上,把count值改为4000,那么会绘制4000 * 4000 = 一千六百万个四边形,如下:

![九百万个四边形](http://upload-images.jianshu.io/upload_images/6271001-fc5ee472f58590c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 
可以看出,还是可以很好的绘制出来(虽然由于对象太多,已经看不清楚界限)
而采用WebGL1 循环遍历的方式,估计最多也就能够达到万级别的绘制循环数量,千万级别的数量简直不可想象。  
当然这个数量 也是有限制的,比如在笔者的机器上,把count改成5000,也就是5000 * 5000 = 两千五百万的时候,机器就奔溃了。

![奔溃了](http://upload-images.jianshu.io/upload_images/6271001-f60f0a01f4055d99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 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彪叔
![ITman彪叔公众号](https://upload-images.jianshu.io/upload_images/6271001-71b1902df91ba0cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/256)

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

标签:多个   矩阵   资源   遍历   9.png   这一   png   10个   分数   

原文地址:http://blog.51cto.com/13842424/2326422

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!