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定义了count*count个数据(注意每个数据有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彪叔
WebGL2系列之实例数组(Instanced Arrays)的更多相关文章
- WebGL2系列之顶点数组对象
使用了顶点缓冲技术后,绘制效率有了较大的提升.但是还有一点不尽如人意,那就是顶点的位置坐标.法向量.纹理坐标等不同方面的数据每次使用时需要单独指定,重复了一些不必要的工作.WebGL2提供了一种专门用 ...
- Java-Runoob-高级教程-实例-数组:16. Java 实例 - 数组并集
ylbtech-Java-Runoob-高级教程-实例-数组:16. Java 实例 - 数组并集 1.返回顶部 1. Java 实例 - 数组并集 Java 实例 以下实例演示了如何使用 unio ...
- ylbtech-Java-Runoob-高级教程-实例-数组:15. Java 实例 – 判断数组是否相等
ylbtech-Java-Runoob-高级教程-实例-数组:15. Java 实例 – 判断数组是否相等 1.返回顶部 1. Java 实例 - 判断数组是否相等 Java 实例 以下实例演示了如 ...
- Java-Runoob-高级教程-实例-数组:08. Java 实例 – 数组填充
ylbtech-Java-Runoob-高级教程-实例-数组:08. Java 实例 – 数组填充 1.返回顶部 1. Java 实例 - 数组填充 Java 实例 以下实例我们通过 Java Ut ...
- Java-Runoob-高级教程-实例-数组:07. Java 实例 – 数组合并
ylbtech-Java-Runoob-高级教程-实例-数组:07. Java 实例 – 数组合并 1.返回顶部 1. Java 实例 - 数组合并 Java 实例 以下实例演示了如何通过 List ...
- Java-Runoob-高级教程-实例-数组:06. Java 实例 – 数组获取最大和最小值
ylbtech-Java-Runoob-高级教程-实例-数组:06. Java 实例 – 数组获取最大和最小值 1.返回顶部 1. Java 实例 - 数组获取最大和最小值 Java 实例 以下实例 ...
- Java-Runoob-高级教程-实例-数组:02. Java 实例 – 数组添加元素
ylbtech-Java-Runoob-高级教程-实例-数组:02. Java 实例 – 数组添加元素 1.返回顶部 1. Java 实例 - 数组添加元素 Java 实例 以下实例演示了如何使用s ...
- Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找
ylbtech-Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找 1.返回顶部 1. Java 实例 - 数组排序及元素查找 Java 实例 以下实例演示 ...
- Java数组声明创建和使用以及多维数组、Arrays类、稀疏数组
目录 数组概述 数组声明创建 内存分析 java内存分析 堆 栈 方法区 三种初始化 静态初始化 动态初始化 数组的默认初始化 数组的四个基本特点 数组边界 小结: 数组使用 数组基础使用 For E ...
随机推荐
- Javascript (ECMAScript5) 的细节和违反直觉的地方
记录在学习Javascript (ECMAScript5) 中的一些与其他语言的不同之处,本文会不断更新. 里面的知识可能并不太适合有一定经验的Javascript程序员,仅仅但不限于给初学者阅读. ...
- Hive学习之路 (十八)Hive的Shell操作
一.Hive的命令行 1.Hive支持的一些命令 Command Description quit Use quit or exit to leave the interactive shell. s ...
- 20145203盖泽双 《Java程序设计》第6周学习总结
20145203盖泽双 <Java程序设计>第6周学习总结 教材学习内容总结 1.如果要将数据从来源中取出,可以使用输入串流,若将数据写入目地, 可以使用输出串流.在java中,输入串流代 ...
- React 入门学习笔记2
摘自阮一峰:React入门实例教程,转载请注明出处. 一.获取真实的DOM节点 组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM).只有当它 ...
- virtualbox+vagrant学习-2(command cli)-12-vagrant Provision命令
Provision 格式: vagrant provision [vm-name] [--provision-with x,y,z] 针对正在运行的vagrant托管计算机运行任何配置预配置程序. u ...
- Sequelize-nodejs-9-Scopes
Scopes作用域 Scoping allows you to define commonly used queries that you can easily use later. Scopes c ...
- Android Studio更改工程名异常解决方案 :can't rename root module
在修改Android Studio 中 project的名字时 ,提示 “can’t rename root module”. 这是因为Android Studio只能修改根目录内的所有文件,要修改p ...
- mybatis提取<where><if>共用代码
mybatis项目dao层中很多sql语句都会拥有某些相同的查询条件,以<where><if test=""></if></where&g ...
- springboot项目打war包pom设置
<build> <finalName>PayManager</finalName><!--打包后的名字PayManager.war--> <plu ...
- centos7 支持中文显示(转)
centos7 支持中文显示 - kingleoric - 博客园https://www.cnblogs.com/kingleoric/p/7517753.html http://www.linuxi ...