【WebGL】attribute方式实例化绘制
背景
一般有attribute和uniform两种方式进行实例化绘制
attribute方式实例化
这里需要注意
- bufferData和bufferSubData方式的用法顺序和参数
-
gl.bufferData(target, sizeOrData, usage);
- sizeOrData(
实例化配合bufferSubData 更新数据一般使用这种先
)- 传入数字(size)指定要分配的缓冲区大小,单位是字节。此时,缓冲区会被分配相应大小的内存,但不会被填充具体的数据。当你后续会使用
bufferSubData
方法来逐步填充缓冲区数据,或者不确定具体数据但先需要分配内存时,会采用这种方式 - 传入类型化数组,直接将类型化数组中的数据写入到缓冲区中,同时根据数组的大小为缓冲区分配相应的内存。常见的类型化数组有 Float32Array、Uint16Array 等。当你已经有了完整的顶点数据或索引数据,并且想一次性将它们写入缓冲区时,使用这种方式很方便。
- 传入数字(size)指定要分配的缓冲区大小,单位是字节。此时,缓冲区会被分配相应大小的内存,但不会被填充具体的数据。当你后续会使用
- sizeOrData(
-
bufferSubData (target, offset, data)
方法用于更新已经绑定的缓冲区对象中的一部分数据。它允许你在不重新创建整个缓冲区的情况下,修改缓冲区中的特定数据区域,这在动态更新数据时非常有用,比如动画中的顶点位置变化。
-
- vertexAttribDivisor(index, divisor)
- index:指定要设置的顶点属性的索引,这个索引通常是通过 gl.getAttribLocation 方法获取的。
- divisor:指定属性更新的频率,是一个无符号整数。具体含义如下:
- divisor 为 0 时,表示该属性在每个顶点都更新,这是传统的绘制方式。
- divisor 为 1 时,表示该属性在每个实例更新一次,这是实例化绘制中最常用的设置。
- divisor 大于 1 时,表示该属性每 divisor 个实例更新一次
- drawArraysInstanced
- mode:指定绘制的图元类型,是一个枚举值。常见的取值有:
- gl.POINTS:绘制一系列点。
- gl.LINES:绘制一系列独立的线段。
- gl.TRIANGLES:绘制一系列独立的三角形。
- first:指定从顶点数组的第几个元素开始绘制,是一个无符号整数。
- count:指定要绘制的顶点数量,是一个无符号整数。
- primcount:指定要绘制的实例数量,是一个无符号整数。
- mode:指定绘制的图元类型,是一个枚举值。常见的取值有:
attribute方式案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL 2 Instanced Rendering with mat4 Attribute using bufferSubData</title>
<style>
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="glCanvas" width="640" height="480"></canvas>
<script>
function main() {
// 获取 canvas 元素和 WebGL 2 上下文
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('Unable to initialize WebGL 2. Your browser or machine may not support it.');
return;
}
// 设置视口大小
gl.viewport(0, 0, canvas.width, canvas.height);
// 禁用深度测试
gl.disable(gl.DEPTH_TEST);
// 禁用混合
gl.disable(gl.BLEND);
// 顶点着色器代码
const vertexShaderSource = `#version 300 es
layout (location = 0) in vec2 a_position;
layout (location = 1) in mat4 a_instanceMatrix;
void main() {
// 使用矩阵变换顶点位置
vec4 pos = a_instanceMatrix * vec4(a_position, 0.0, 1.0);
gl_Position = pos;
}
`;
// 片段着色器代码
const fragmentShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// 创建着色器程序
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success) {
console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) {
console.error('Program linking error:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
if (!vertexShader || !fragmentShader) {
return;
}
const program = createProgram(gl, vertexShader, fragmentShader);
if (!program) {
return;
}
// 顶点数据
const positions = [
-0.1, -0.1,
0.1, -0.1,
0.0, 0.1
];
// 预先分配足够的空间给矩阵数据
const numInstances = 2;
const matrixDataSize = numInstances * 4 * 4 * 4; // 每个 mat4 是 4x4 矩阵,每个元素是 float(4 字节)
const matrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixDataSize, gl.DYNAMIC_DRAW);
// 初始实例化矩阵数据
let mat4Data = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-0.5, 0, 0, 1,
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0.5, 0, 0, 1
]);
// 使用 bufferSubData 更新矩阵数据
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, mat4Data);
// 创建顶点缓冲区
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取属性位置
const positionAttributeLocation = 0//gl.getAttribLocation(program, 'a_position');
const matrixAttributeLocation = 1//gl.getAttribLocation(program, 'a_instanceMatrix');
if (positionAttributeLocation === -1 || matrixAttributeLocation === -1) {
console.error('Failed to get attribute location');
return;
}
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 为矩阵的每个 vec4 分量设置 attribute
const bytesPerMatrix = 4 * 4 * 4; // 4 个 vec4,每个 vec4 4 个浮点数,每个浮点数 4 字节
for (let i = 0; i < 4; i++) {
const loc = matrixAttributeLocation + i;
gl.enableVertexAttribArray(loc);
const offset = i * 4 * 4; // 每个 vec4 偏移 16 字节
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.vertexAttribPointer(loc, 4, gl.FLOAT, false, bytesPerMatrix, offset);
// 设置每个实例更新一次
gl.vertexAttribDivisor(loc, 1);
}
// 渲染循环
let time = 0;
function render() {
// 更新矩阵数据
time += 0.01;
mat4Data[12] = -0.5 + Math.sin(time) * 0.2; // 修改第一个矩阵的平移分量
mat4Data[28] = 0.5 + Math.cos(time) * 0.2; // 修改第二个矩阵的平移分量
// 使用 bufferSubData 更新缓冲区数据
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, mat4Data);
// 使用着色器程序
gl.useProgram(program);
// 清除画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制实例
const instanceCount = numInstances;
gl.drawArraysInstanced(gl.TRIANGLES, 0, positions.length / 2, instanceCount);
// 请求下一帧渲染
requestAnimationFrame(render);
}
// 开始渲染循环
render();
}
main();
</script>
</body>
</html>