vertexAttribPointer(location, size, type, normalized, stride, offset) records the buffer bound to ARRAY_BUFFER and how to read it: size components (1 to 4) of type every stride bytes from offset. enableVertexAttribArray() switches the attribute from its constant vertexAttrib*() value, (0, 0, 0, 1) by default, to that array. normalized maps integers to 0 to 1, so colors can travel as bytes:
<canvas id="c" width="960" height="240" style="width:480px;height:120px"></canvas>
<script src="webgl-helper.js"></script>
<script>
const gl = GLH.context('c');
const { attributes: a } = GLH.load(gl, `#version 300 es
in vec2 aPosition; in vec3 aColor; in float aBrightness; out vec3 vColor;
void main() { vColor = aColor * aBrightness; gl_Position = vec4(aPosition, 0.0, 1.0); }`,
`#version 300 es
precision mediump float; in vec3 vColor; out vec4 c; void main() { c = vec4(vColor, 1); }`);
GLH.buffer(gl, new Float32Array([0.0, -0.85, -0.3, 0.85, 0.3, 0.85]));
gl.vertexAttribPointer(a.aPosition, 2, gl.FLOAT, false, 0, 0); // stride 0: packed
gl.enableVertexAttribArray(a.aPosition);
GLH.buffer(gl, new Uint8Array([224, 154, 16, 31, 95, 139, 42, 157, 143])); // books 3, 1, 6
gl.vertexAttribPointer(a.aColor, 3, gl.UNSIGNED_BYTE, true, 0, 0); // 255 becomes 1.0
gl.enableVertexAttribArray(a.aColor);
gl.vertexAttrib1f(a.aBrightness, 1.1); // not enabled: one value for every vertex
gl.drawArrays(gl.TRIANGLES, 0, 3);
window.__done = true;
</script>
The rasterizer blends the vertex colors across the triangle: that interpolation is what a varying is. For int attributes WebGL 2 adds vertexAttribIPointer(); WebGL 1 has none and rejects gl.INT data with INVALID_ENUM.
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; }
canvas { display: block; width: 100%; max-width: 480px; height: auto; }
</style>
<canvas id="c" width="960" height="240"></canvas>
<script>
const gl = document.getElementById('c').getContext('webgl2');
gl.clearColor(0.93, 0.91, 0.87, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
in vec2 aPosition; in vec3 aColor; in float aBrightness; out vec3 vColor;
void main() { vColor = aColor * aBrightness; gl_Position = vec4(aPosition, 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); }`]]) {
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);
const a = {
aPosition: gl.getAttribLocation(program, 'aPosition'),
aColor: gl.getAttribLocation(program, 'aColor'),
aBrightness: gl.getAttribLocation(program, 'aBrightness'),
};
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, -0.85, -0.3, 0.85, 0.3, 0.85]), gl.STATIC_DRAW);
gl.vertexAttribPointer(a.aPosition, 2, gl.FLOAT, false, 0, 0); // stride 0: packed
gl.enableVertexAttribArray(a.aPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); // books 3, 1, 6 as bytes
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([224, 154, 16, 31, 95, 139, 42, 157, 143]), gl.STATIC_DRAW);
gl.vertexAttribPointer(a.aColor, 3, gl.UNSIGNED_BYTE, true, 0, 0); // normalized: 255 becomes 1.0
gl.enableVertexAttribArray(a.aColor);
gl.vertexAttrib1f(a.aBrightness, 1.1); // not enabled: one value for every vertex
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>