twgl.js 3,005 (MIT, 7.0.0, github.com/greggman/twgl.js (https://github.com/greggman/twgl.js 3,005 ), about 3,000 stars), by the author of WebGL2Fundamentals, is what webgl-helper.js became by accident: helpers that link programs, collect locations, build buffers from arrays, load textures and set uniforms by name, while you still call useProgram() and draw. Its math module appeared in Math Library Comparison; here it draws the six real covers:
<canvas id="c" width="1200" height="300" style="width:600px;height:150px"></canvas>
<script src="https://cdn.jsdelivr.net/npm/twgl.js@7.0.0/dist/7.x/twgl-full.min.js"></script>
<script>
const gl = document.getElementById('c').getContext('webgl2');
const programInfo = twgl.createProgramInfo(gl, [`#version 300 es
in vec2 position; in vec2 texcoord; uniform vec2 uOffset; out vec2 vUV;
void main() { vUV = texcoord;
gl_Position = vec4(position * vec2(0.12, 0.72) + uOffset, 0, 1); }`,
`#version 300 es
precision mediump float; uniform sampler2D uCover; in vec2 vUV; out vec4 c;
void main() { c = texture(uCover, vUV); }`]); // compiled, linked, reflected
const quad = twgl.primitives.createXYQuadBufferInfo(gl); // position, normal, texcoord
const sources = [1, 2, 3, 4, 5, 6].map((n) => ({ src: `booknest-cover-${n}.png`, flipY: 1 }));
const covers = twgl.createTextures(gl, { ...sources }, () => { // runs once all have loaded
gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quad);
Object.values(covers).forEach((texture, i) => {
twgl.setUniforms(programInfo, { uCover: texture, uOffset: [-0.8 + i * 0.32, 0] });
twgl.drawBufferInfo(gl, quad);
});
window.__done = true;
});
</script>
There is no getAttribLocation(), vertexAttribPointer() or texParameteri() call: twgl matched the buffer names position and texcoord to the shader's inputs. It also wraps VAOs, framebuffers and transform feedback.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
p { max-width: 600px; margin: 4px 6px; font: 11px monospace; }
</style>
<canvas id="c" width="1200" height="300"></canvas>
<p id="out"></p>
<script src="https://cdn.jsdelivr.net/npm/twgl.js@7.0.0/dist/7.x/twgl-full.min.js"></script>
<script>
// Covers painted on 2D canvases stand in for the book's PNG files.
const books = [['The Quiet Harbor', '#1e5f8c'], ['Small Steps to Big Summits', '#5b3f99'], ["The Clockmaker's Paradox", '#e09a10'],
['Salt and Saffron', '#3f7d3a'], ['The Quiet Harbor', '#b5452e'], ['Salt and Saffron', '#2a9d8f']];
function paintCover([title, color]) {
const c = document.createElement('canvas'); c.width = 128; c.height = 192;
const g = c.getContext('2d');
g.fillStyle = color; g.fillRect(0, 0, 128, 192);
g.fillStyle = 'rgba(0,0,0,0.25)'; g.fillRect(0, 0, 10, 192);
g.fillStyle = '#f7f4ee'; g.fillRect(18, 120, 100, 2);
g.font = 'bold 15px Georgia'; g.textAlign = 'center';
title.split(' ').reduce((lines, word) => { // naive word wrap
const last = lines[lines.length - 1];
if ((last + ' ' + word).length > 11 && last) lines.push(word); else lines[lines.length - 1] = (last + ' ' + word).trim();
return lines;
}, ['']).forEach((line, i) => g.fillText(line, 68, 40 + i * 20));
g.font = '10px system-ui'; g.fillText('BookNest', 68, 176);
return c;
}
const gl = document.getElementById('c').getContext('webgl2');
const programInfo = twgl.createProgramInfo(gl, [`#version 300 es
in vec2 position; in vec2 texcoord; uniform vec2 uOffset; out vec2 vUV;
void main() { vUV = texcoord; gl_Position = vec4(position * vec2(0.12, 0.72) + uOffset, 0, 1); }`,
`#version 300 es
precision mediump float; uniform sampler2D uCover; in vec2 vUV; out vec4 c;
void main() { c = texture(uCover, vUV); }`]); // compiled, linked, reflected
const quad = twgl.primitives.createXYQuadBufferInfo(gl); // position, normal, texcoord
const sources = books.map((book) => ({ src: paintCover(book), flipY: 1, min: gl.LINEAR }));
const covers = twgl.createTextures(gl, { ...sources }); // canvases upload immediately
gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quad); // names matched to shader inputs
Object.values(covers).forEach((texture, i) => {
twgl.setUniforms(programInfo, { uCover: texture, uOffset: [-0.8 + i * 0.32, 0] });
twgl.drawBufferInfo(gl, quad);
});
document.getElementById('out').textContent =
`attributes found by name: ${Object.keys(programInfo.attribSetters).join(', ')} | uniforms: ${Object.keys(programInfo.uniformSetters).join(', ')}`;
</script>