一.介绍
ANGLE_instanced_arrays
扩展是一种用于实现WebGL中实例化渲染的扩展。实例化渲染允许您在单个绘制调用中多次使用相同的几何体,并对每个实例应用不同的属性(如颜色、位置、大小等)。这对于在图形渲染中实现重复元素、粒子系统、草地等效果非常有用,因为它可以显著提高渲染性能。
通常,在非实例化渲染中,您需要为每个几何体的每个顶点指定属性(如颜色、位置等)。然而,在实例化渲染中,您只需指定实例之间的属性,并且相同的几何体可以多次重用,以绘制多个实例。
ANGLE_instanced_arrays
扩展引入了以下两个函数:
-
drawArraysInstancedANGLE(mode, first, count, primcount)
: 在单个绘制调用中绘制多个实例。它类似于drawArrays
函数,但允许您指定要绘制的实例数量。 -
vertexAttribDivisorANGLE(index, divisor)
: 用于设置通用顶点属性的分频器(divisor)。分频器确定属性在多个实例之间的更新频率。 -
drawElementsInstancedANGLE(mode,type,count,offset,primcount)
:与drawArraysInstancedANGLE
类似,这个函数允许您在单个绘制调用中绘制多个实例,但是它使用的是索引缓冲来定义绘制的图元。mode
: 表示要绘制的基本图元类型的枚举值,例如gl.TRIANGLES
表示绘制三角形。其他可能的值包括gl.POINTS
、gl.LINES
等。count
: 表示要绘制的索引数量。type
: 表示索引数组的数据类型,例如gl.UNSIGNED_BYTE
、gl.UNSIGNED_SHORT
等。offset
: 表示索引数组中的偏移量。primcount
: 表示要绘制的实例数量。
以下是一个使用 ANGLE_instanced_arrays
扩展的示例:
javascript
// 获取扩展
var ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
alert("ANGLE_instanced_arrays extension not supported.");
}
// 设置分频器
ext.vertexAttribDivisorANGLE(positionAttributeLocation, 0); // 非实例化属性
ext.vertexAttribDivisorANGLE(colorAttributeLocation, 1); // 实例化属性
// 绘制多个实例
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, vertexCount, instanceCount);
请注意,使用 ANGLE_instanced_arrays
扩展需要确保您的浏览器和设备支持该扩展。它在许多现代的 WebGL 实现中都有良好的支持,但在使用之前最好检查目标平台的兼容性信息。
二.绘制多个四边形
2.1 编写shader代码
js
var vShader =
"attribute vec3 a_position;" +
"uniform mat4 u_modelMatrix;" +
"void main(){" +
"gl_Position = u_modelMatrix*vec4(a_position, 1.0);" +
"v_color=a_color;" +
"}"
var fShader =
"precision mediump float;" +
"uniform vec3 u_color;"+
"void main() {" +
" gl_FragColor =vec4(u_color,1.0);" +
" }"
2.2 初始化缓冲区
js
function initVertexBuffer(gl) {
//创建位置缓冲区
var positions = new Float32Array( [
1, 0, 0,
0, 1, -1,
-1, 0, -1,
0, -1, 0,
])
const fSize=positions.BYTES_PER_ELEMENT
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
//创建索引缓冲区
const typedindex=new Uint8Array([
0,1,2,
2,3,0
])
const indexBuffer=gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,typedindex,gl.STATIC_DRAW)
return {
positionBuffer,
// colorBuffer,
indexBuffer,
fSize
}
}
const objBuffer=initVertexBuffer(gl)
const colorList= [
[0, 0, 1],
[1, 0, 0],
[0, 1, 0],
]
2.3 绘制并更新颜色和模型矩阵
js
//绑定一个对象的顶点和索引缓冲区
function bindObjBuffer(gl,objBuffer){
const fSize=objBuffer.fSize
gl.bindBuffer(gl.ARRAY_BUFFER,objBuffer.positionBuffer)
var a_position = gl.getAttribLocation(gl.program, 'a_position');
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false,0, 0)
gl.enableVertexAttribArray(a_position)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,objBuffer.indexBuffer)
}
var modelMatrix = new Matrix4()
function drawAsUsually(gl,objBuffer,colorList,currentAngle,ext){
var u_modelMatrixLocation = gl.getUniformLocation(gl.program, 'u_modelMatrix')
var u_colorLocation = gl.getUniformLocation(gl.program, 'u_color')
bindObjBuffer(gl,objBuffer)
colorList.forEach((colorItem,index)=>{
//设置矩阵
modelMatrix.setRotate(currentAngle+index*30, 0, 0, 1)
gl.uniformMatrix4fv(u_modelMatrixLocation, false, modelMatrix.elements)
//设置颜色
gl.uniform3fv(u_colorLocation, colorList[index])
gl.drawElements(gl.TRIANGLES,6,gl.UNSIGNED_BYTE,0)
})
}
三. 使用扩展绘制
3.1 修改shader,将u_color
使用a_color
代替,u_modelMatrix
使用a_modelMatrix
代替
js
var vShader =
"attribute vec3 a_position;" +
"attribute vec3 a_color;" +
"attribute mat4 a_modelMatrix;" +
"varying vec3 v_color;" +
"void main(){" +
"gl_Position = a_modelMatrix*vec4(a_position, 1.0);" +
"v_color=a_color;" +
"}"
var fShader =
"precision mediump float;" +
"varying vec3 v_color;" +
"void main() {" +
" gl_FragColor =vec4(v_color,1.0);" +
" }"
3.2 创建颜色缓冲区并在绘制时绑定
js
//为颜色创建Buffer
const colorBuffer=gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer)
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(colorList.flat()),gl.STATIC_DRAW)
function drawAsInstance(gl,objBuffer,colorBuffer,matrixBuffer,matrixData,matrices,currentAngle,ext){
//绑定颜色缓冲区
var a_color = gl.getAttribLocation(gl.program, 'a_color');
gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer)
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false,0, 0)
gl.enableVertexAttribArray(a_color)
//设置颜色偏移
ext.vertexAttribDivisorANGLE(a_color,1);
//绑定位置和索引缓冲区
bindObjBuffer(gl,objBuffer,ext)
ext.drawElementsInstancedANGLE(
gl.TRIANGLES,
6,
gl.UNSIGNED_BYTE,
0,
3,
);
}
3.3 为矩阵创建缓冲区
js
//为矩阵创建buffer
const numInstances=colorList.length
const matrixData=new Float32Array(numInstances * 16)
const matrixBuffer=gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,matrixBuffer)
gl.bufferData(gl.ARRAY_BUFFER,matrixData.byteLength, gl.DYNAMIC_DRAW)
const matrices =[]
for(let i=0;i<numInstances;i++){
const byteOffsetToMatrix = i * 16 * 4;
const numFloatsForView = 16;
const matrix=new Matrix4()
matrix.elements=new Float32Array(matrixData.buffer,byteOffsetToMatrix,numFloatsForView)
matrices.push(matrix)
}
这儿有三点要注意
gl.bufferData(gl.ARRAY_BUFFER,matrixData.byteLength, gl.DYNAMIC_DRAW)
由于矩阵要在每一帧动态更新,所以这儿先创建了一个 空的定型数组,第二个参数只给了长度,第三个参数是gl.DYNAMIC_DRAW
,而不是gl.STATIC_DRAW
- 创建矩阵时,让矩阵的元素指向matrixData的buffer,这样后续矩阵更新时matrixData即可更新
3.4 在绘制时动态更新矩阵缓冲区
js
function drawAsInstance(gl,objBuffer,colorBuffer,matrixBuffer,matrixData,matrices,currentAngle,ext){
//绑定颜色缓冲区
var a_color = gl.getAttribLocation(gl.program, 'a_color');
gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer)
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false,0, 0)
gl.enableVertexAttribArray(a_color)
//设置颜色偏移
ext.vertexAttribDivisorANGLE(a_color,1);
//绑定位置和索引缓冲区
bindObjBuffer(gl,objBuffer,ext)
const matrixLoc=gl.getAttribLocation(gl.program, 'a_modelMatrix');
//更新矩阵
matrices.forEach((matrix,index)=>{
matrix.setRotate(currentAngle+index*30, 0, 0, 1)
})
updateMatrixBuffer(gl,matrixBuffer,matrixData,matrixLoc,ext)
ext.drawElementsInstancedANGLE(
gl.TRIANGLES,
6,
gl.UNSIGNED_BYTE,
0,
3,
);
}
function updateMatrixBuffer(gl,matrixBuffer,matrixData,matrixLoc,ext){
//更新矩阵缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
//填充数据
gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData);
const bytesPerMatrix = 4 * 16;
//更新矩阵每一列
for (let i = 0; i < 4; ++i) {
const loc = matrixLoc + i;
gl.enableVertexAttribArray(loc);
// 注意stride和offset
const offset = i * 16; // 一行有4个单精度浮点数,1个就占用4字节
gl.vertexAttribPointer(
loc, // location
4, // size (num values to pull from buffer per iteration)
gl.FLOAT, // type of data in buffer
false, // normalize
bytesPerMatrix, // stride, num bytes to advance to get to next set of values
offset, // offset in buffer
);
// 这行说的是attribute只对下一个实例才进行迭代
ext.vertexAttribDivisorANGLE(loc, 1);
}
}

四.webgl2.0中实例化
webgl2.0标准实现了实例化
js
gl.vertexAttribDivisor(index, divisor)
js
gl.drawArraysInstanced(mode, first, count, instanceCount);
gl.drawElementsInstanced(mode, count, type, offset, instanceCount);