Skip to content

WebGL - Colors

Previously, we often set a single color for the entire object in the fragment shader (e.g., gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); for red). A more flexible approach is to define colors per vertex. WebGL can then interpolate these colors across the faces of primitives (like triangles), creating smooth gradients or allowing different parts of a model to have distinct colors.

The process involves these main steps:

  1. Define Color Data: Create a JavaScript TypedArray (usually Float32Array) containing the color data for each vertex. Colors are typically specified as RGB or RGBA values, normalized to the range [0.0, 1.0]. The order of colors must correspond to the order of vertices.
  2. Create Color Buffer: Create a new WebGL buffer object (gl.createBuffer) specifically for the color data.
  3. Upload Color Data: Bind the color buffer (gl.bindBuffer) and upload the color TypedArray data into it (gl.bufferData).
  4. Update Vertex Shader: Add an attribute (GLSL 1.00) or in (GLSL 3.00) variable to receive the per-vertex color. Add a varying (GLSL 1.00) or out (GLSL 3.00) variable to pass the color (or a processed version of it) to the fragment shader.
  5. Update Fragment Shader: Add a corresponding varying (GLSL 1.00) or in (GLSL 3.00) variable to receive the interpolated color from the vertex shader. Use this incoming color value to set the final gl_FragColor (GLSL 1.00) or the fragment shader’s out color variable (GLSL 3.00).
  6. Configure Color Attribute: In JavaScript, get the location of the color attribute (gl.getAttribLocation). Bind the color buffer. Tell WebGL how to read data from the color buffer into the attribute using gl.vertexAttribPointer. Enable the color attribute array (gl.enableVertexAttribArray).

Let’s modify the simple triangle example to draw a square (quad) composed of two triangles, with a different color assigned to each vertex.

Step 1: Prepare Canvas and Context

Same as before: get the canvas element and the WebGL context.

const canvas = document.getElementById('my_Canvas');
const gl = canvas.getContext('webgl');
if (!gl) { /* ... error handling ... */ }

Step 2: Define Geometry and Colors

We define 4 vertices for the quad and corresponding colors (Red, Green, Blue, White). We also define indices to draw the two triangles.

const vertices = new Float32Array([
-0.5, 0.5, // Vertex 0: Top-left
0.5, 0.5, // Vertex 1: Top-right
0.5, -0.5, // Vertex 2: Bottom-right
-0.5, -0.5 // Vertex 3: Bottom-left
]);
// Colors for each vertex (R, G, B), normalized [0.0, 1.0]
const colors = new Float32Array([
1.0, 0.0, 0.0, // Vertex 0: Red
0.0, 1.0, 0.0, // Vertex 1: Green
0.0, 0.0, 1.0, // Vertex 2: Blue
1.0, 1.0, 1.0 // Vertex 3: White
]);
// Indices to form two triangles: (0, 3, 1) and (1, 3, 2)
const indices = new Uint16Array([
0, 3, 1,
1, 3, 2
]);

Step 3: Create and Populate Buffers

Create separate buffers for vertices, colors, and indices.

// Vertex Buffer
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Color Buffer
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
// Index Buffer
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

Step 4: Create and Compile Shaders

The shaders now need to handle the color attribute.

const vsSource = `
attribute vec2 aVertexPosition;
attribute vec3 aVertexColor; // Input: Per-vertex color
varying lowp vec3 vColor; // Output: Color to fragment shader
void main(void) {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
vColor = aVertexColor; // Pass the color through
}
`;
const fsSource = `
varying lowp vec3 vColor; // Input: Interpolated color from vertex shader
void main(void) {
// Output the interpolated color
gl_FragColor = vec4(vColor, 1.0);
}
`;

(Compile and link these shaders into shaderProgram as shown in previous examples, including error checking).

Step 5: Associate Shaders and Buffers

Get attribute locations and configure both position and color attributes.

gl.useProgram(shaderProgram);
// Get attribute locations
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
// Configure Position Attribute
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
// Configure Color Attribute
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // *** Bind the color buffer ***
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 0, 0); // 3 components (RGB)
gl.enableVertexAttribArray(colorAttributeLocation);
// Bind the index buffer (will be used by drawElements)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

Step 6: Drawing the Object

Clear the canvas and use gl.drawElements since we are using an index buffer.

gl.clearColor(0.8, 0.8, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Draw using indices
const primitiveType = gl.TRIANGLES;
const count = indices.length; // Number of indices to draw
const indexType = gl.UNSIGNED_SHORT; // Data type of indices
const drawOffset = 0; // Start at the beginning of the index buffer
gl.drawElements(primitiveType, count, indexType, drawOffset);
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>WebGL Colored Quad</title></head>
<body>
<canvas id="my_Canvas" width="300" height="300" style="border:1px solid black;"></canvas>
<script>
// --- GET CONTEXT ---
const canvas = document.getElementById('my_Canvas');
const gl = canvas.getContext('webgl');
if (!gl) { alert('WebGL not supported'); throw 'WebGL not supported'; }
// --- GEOMETRY & COLORS ---
const vertices = new Float32Array([-0.5,0.5, 0.5,0.5, 0.5,-0.5, -0.5,-0.5]);
const colors = new Float32Array([1,0,0, 0,1,0, 0,0,1, 1,1,1]); // R, G, B, W
const indices = new Uint16Array([0, 3, 1, 1, 3, 2]);
// --- BUFFERS ---
const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// --- SHADERS ---
const vsSource = 'attribute vec2 pos; attribute vec3 col; varying lowp vec3 vCol; void main() { gl_Position = vec4(pos, 0.0, 1.0); vCol = col; }';
const fsSource = 'varying lowp vec3 vCol; void main() { gl_FragColor = vec4(vCol, 1.0); }';
function compileShader(gl, type, src) { /* ... (error checking compile function) ... */
const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(s)); gl.deleteShader(s); return null;}
return s;
}
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
const prog = gl.createProgram(); gl.attachShader(prog, vertexShader); gl.attachShader(prog, fragmentShader); gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(prog)); throw 'Link failed'; }
gl.useProgram(prog);
// --- ATTRIBUTES ---
const posLoc = gl.getAttribLocation(prog, 'pos');
const colLoc = gl.getAttribLocation(prog, 'col');
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.vertexAttribPointer(colLoc, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(colLoc);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// --- DRAW ---
gl.clearColor(0.8, 0.8, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
</script>
</body>
</html>

Expected Result:

This will display a square (quad) on a grey background. The colors will smoothly blend between the corners: red (top-left), green (top-right), blue (bottom-right), and white (bottom-left).