Switching meshes means resetting every attribute's pointer and enable flag. A vertex array object (VAO) records that state: while it is bound, each vertexAttribPointer(), enableVertexAttribArray() and ELEMENT_ARRAY_BUFFER binding is stored in it, and binding it later restores them all (the pointers remember their buffers; the ARRAY_BUFFER binding itself is not stored). The sixth helper part wraps this:
// Section 3.5: record an interleaved float buffer's layout (and optional indices) in a VAO
GLH.vao = function (gl, attributes, data, layout, indices) { // layout: [[name, size], ...]
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
GLH.buffer(gl, data);
const stride = layout.reduce((sum, [, size]) => sum + size * 4, 0);
let offset = 0;
for (const [name, size] of layout) {
if (name in attributes) { // skip attributes the linker removed
gl.vertexAttribPointer(attributes[name], size, gl.FLOAT, false, stride, offset);
gl.enableVertexAttribArray(attributes[name]);
}
offset += size * 4;
}
if (indices) GLH.buffer(gl, indices, gl.ELEMENT_ARRAY_BUFFER); // stored in the VAO
gl.bindVertexArray(null);
return vao;
};With it, a mesh's setup is one GLH.vao() call and drawing it is bindVertexArray() plus drawElements(), as Split-Screen shows. Programs sharing a VAO need matching attribute locations (layout(location = n)), and a VAO should be unbound before you set up other buffers, or you will silently rewrite its element binding.
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; }
canvas { display: block; width: 100%; max-width: 600px; }
</style>
<canvas id="view" width="600" height="260"></canvas>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec2 aPosition; // shared locations let one VAO serve many programs
layout(location = 1) in vec3 aColor;
uniform vec2 uOffset; out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPosition + uOffset, 0.0, 1.0); }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; in vec3 vColor; out vec4 c; void main() { c = vec4(vColor, 1.0); }`]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
// Record an interleaved float buffer's layout (and optional indices) in a VAO.
function makeVAO(data, layout, indices) { // layout: [[location, size], ...]
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
const stride = layout.reduce((sum, [, size]) => sum + size * 4, 0);
let offset = 0;
for (const [location, size] of layout) {
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset); // stored in the VAO
gl.enableVertexAttribArray(location); // stored in the VAO
offset += size * 4;
}
if (indices) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); // stored in the VAO
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
}
gl.bindVertexArray(null); // unbind before touching other buffers
return vao;
}
// Mesh 1: a book (indexed quad, position + colour interleaved).
const book = makeVAO([-0.15, -0.85, 0.12, 0.37, 0.55, 0.15, -0.85, 0.12, 0.37, 0.55,
0.15, 0.35, 0.3, 0.6, 0.8, -0.15, 0.35, 0.3, 0.6, 0.8],
[[0, 2], [1, 3]], [0, 1, 2, 0, 2, 3]);
// Mesh 2: a star as a triangle fan with a different vertex count and no indices.
const star = [0, 0, 1, 0.9, 0.5];
for (let i = 0; i <= 10; i++) {
const a = Math.PI / 2 + i * Math.PI / 5, r = i % 2 ? 0.12 : 0.3;
star.push(Math.cos(a) * r * 0.45, Math.sin(a) * r, 0.88, 0.6, 0.06);
}
const starVAO = makeVAO(star, [[0, 2], [1, 3]]);
const uOffset = gl.getUniformLocation(program, 'uOffset');
function frame(ms) {
const t = ms * 0.001;
gl.clearColor(0.97, 0.96, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
for (let i = 0; i < 4; i++) {
gl.bindVertexArray(book); // one call restores pointers, enables and indices
gl.uniform2f(uOffset, -0.75 + i * 0.5, 0);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
gl.bindVertexArray(starVAO); // switch mesh: nothing else to reset
gl.uniform2f(uOffset, -0.75 + i * 0.5, 0.62 + 0.05 * Math.sin(t * 3 + i));
gl.drawArrays(gl.TRIANGLE_FAN, 0, 12);
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>