webgl扩展系列之二--------------------ANGLE_instanced_arrays(实例化绘制)

一.介绍

ANGLE_instanced_arrays 扩展是一种用于实现WebGL中实例化渲染的扩展。实例化渲染允许您在单个绘制调用中多次使用相同的几何体,并对每个实例应用不同的属性(如颜色、位置、大小等)。这对于在图形渲染中实现重复元素、粒子系统、草地等效果非常有用,因为它可以显著提高渲染性能。

通常,在非实例化渲染中,您需要为每个几何体的每个顶点指定属性(如颜色、位置等)。然而,在实例化渲染中,您只需指定实例之间的属性,并且相同的几何体可以多次重用,以绘制多个实例。

ANGLE_instanced_arrays 扩展引入了以下两个函数:

  1. drawArraysInstancedANGLE(mode, first, count, primcount): 在单个绘制调用中绘制多个实例。它类似于 drawArrays 函数,但允许您指定要绘制的实例数量。

  2. vertexAttribDivisorANGLE(index, divisor): 用于设置通用顶点属性的分频器(divisor)。分频器确定属性在多个实例之间的更新频率。

  3. drawElementsInstancedANGLE(mode,type,count,offset,primcount):与 drawArraysInstancedANGLE 类似,这个函数允许您在单个绘制调用中绘制多个实例,但是它使用的是索引缓冲来定义绘制的图元。

    • mode: 表示要绘制的基本图元类型的枚举值,例如 gl.TRIANGLES 表示绘制三角形。其他可能的值包括 gl.POINTSgl.LINES 等。
    • count: 表示要绘制的索引数量。
    • type: 表示索引数组的数据类型,例如 gl.UNSIGNED_BYTEgl.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);
相关推荐
世俗ˊ24 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92125 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_29 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人38 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript