A cover drawn as two triangles repeats two of its four corners. Indexed drawing stores each vertex once and lists triangles as indices in an ELEMENT_ARRAY_BUFFER; drawElements(mode, count, type, byteOffset) walks them. type is UNSIGNED_BYTE, UNSIGNED_SHORT or, in WebGL 2 (WebGL 1 with OES_element_index_uint), UNSIGNED_INT. The fifth part of webgl-helper.js builds the cover wall this way:
// Section 3.5: the covers of Section 3.1.8 as 4 vertices (x, y, u, v, book) + 6 indices each
GLH.coverWall = function () {
const vertices = [], indices = [];
GLH.covers.forEach((hex, i) => {
for (const [u, v] of [[0, 0], [1, 0], [1, 1], [0, 1]]) {
vertices.push(-0.94 + i * 0.32 + u * 0.26, -0.82 + v * 1.64, u, 1 - v, i);
}
indices.push(i * 4, i * 4 + 1, i * 4 + 2, i * 4, i * 4 + 2, i * 4 + 3);
});
return { vertices: new Float32Array(vertices), indices: new Uint16Array(indices) };
};The wall needs 24 vertices plus 36 two-byte indices, 552 bytes, against 720 bytes for 36 unindexed vertices. In smooth meshes a vertex is shared by about six triangles, so savings grow, and the GPU can reuse a vertex it has just shaded. Uint16Array indices cap a draw at 65,536 vertices.
<!doctype html>
<style>
body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
p { max-width: 600px; margin: 4px 0; }
</style>
<canvas id="wall" width="1200" height="340"></canvas>
<p id="info"></p>
<script>
const covers = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
const rgb = (hex) => [1, 3, 5].map((k) => parseInt(hex.slice(k, k + 2), 16) / 255);
// The covers as 4 vertices (x, y, u, v, book) + 6 indices each.
function coverWall() {
const vertices = [], indices = [];
covers.forEach((hex, i) => {
for (const [u, v] of [[0, 0], [1, 0], [1, 1], [0, 1]]) {
vertices.push(-0.94 + i * 0.32 + u * 0.26, -0.82 + v * 1.64, u, 1 - v, i);
}
indices.push(i * 4, i * 4 + 1, i * 4 + 2, i * 4, i * 4 + 2, i * 4 + 3);
});
return { vertices: new Float32Array(vertices), indices: new Uint16Array(indices) };
}
const gl = document.getElementById('wall').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec2 aPosition; layout(location = 1) in vec2 aUV; layout(location = 2) in float aBook;
uniform vec3 uColors[6]; uniform bool uWire;
out vec2 vUV; out vec3 vColor;
void main() { vUV = aUV; vColor = uWire ? vec3(1.0) : uColors[int(aBook)];
gl_Position = vec4(aPosition, 0.0, 1.0); gl_PointSize = 12.0; }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; in vec2 vUV; in vec3 vColor; uniform bool uWire; out vec4 outColor;
void main() {
outColor = uWire ? vec4(0.15, 0.15, 0.15, 1.0)
: vec4(vColor * mix(0.55, 1.0, smoothstep(0.07, 0.10, vUV.x)) * mix(1.2, 0.7, vUV.y), 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);
gl.uniform3fv(gl.getUniformLocation(program, 'uColors'), covers.flatMap(rgb));
const uWire = gl.getUniformLocation(program, 'uWire');
const { vertices, indices } = coverWall();
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 20, 0);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 20, 8);
gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 20, 16);
[0, 1, 2].forEach(i => gl.enableVertexAttribArray(i));
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
gl.clearColor(0.93, 0.91, 0.87, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1i(uWire, 0);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); // 36 indices -> 12 triangles
// Show the structure: each triangle's edges (LINE_LOOP per triangle) and the 24 shared vertices.
gl.uniform1i(uWire, 1);
for (let t = 0; t < indices.length; t += 3) gl.drawElements(gl.LINE_LOOP, 3, gl.UNSIGNED_SHORT, t * 2);
gl.drawArrays(gl.POINTS, 0, vertices.length / 5);
const indexed = vertices.byteLength + indices.byteLength, flat = 36 * 5 * 4;
document.getElementById('info').textContent =
`${vertices.length / 5} vertices + ${indices.length} Uint16 indices = ${indexed} bytes, against ${flat} bytes for 36 unindexed vertices. The diagonal edge shows each quad reusing two of its corners.`;
</script>