WebGL - Colors
WebGL - Applying Colors to Geometry
Section titled “WebGL - Applying Colors to Geometry”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.
Applying Per-Vertex Colors
Section titled “Applying Per-Vertex Colors”The process involves these main steps:
- 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. - Create Color Buffer: Create a new WebGL buffer object (
gl.createBuffer) specifically for the color data. - Upload Color Data: Bind the color buffer (
gl.bindBuffer) and upload the color TypedArray data into it (gl.bufferData). - Update Vertex Shader: Add an
attribute(GLSL 1.00) orin(GLSL 3.00) variable to receive the per-vertex color. Add avarying(GLSL 1.00) orout(GLSL 3.00) variable to pass the color (or a processed version of it) to the fragment shader. - Update Fragment Shader: Add a corresponding
varying(GLSL 1.00) orin(GLSL 3.00) variable to receive the interpolated color from the vertex shader. Use this incoming color value to set the finalgl_FragColor(GLSL 1.00) or the fragment shader’soutcolor variable (GLSL 3.00). - 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 usinggl.vertexAttribPointer. Enable the color attribute array (gl.enableVertexAttribArray).
Example: Drawing a Multi-Colored Quad
Section titled “Example: Drawing a Multi-Colored Quad”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 Bufferconst vertexBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Color Bufferconst colorBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
// Index Bufferconst 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.
Vertex Shader (GLSL ES 1.00):
Section titled “Vertex Shader (GLSL ES 1.00):”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 }`;Fragment Shader (GLSL ES 1.00):
Section titled “Fragment Shader (GLSL ES 1.00):”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 locationsconst positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
// Configure Position Attributegl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(positionAttributeLocation);
// Configure Color Attributegl.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 indicesconst primitiveType = gl.TRIANGLES;const count = indices.length; // Number of indices to drawconst indexType = gl.UNSIGNED_SHORT; // Data type of indicesconst drawOffset = 0; // Start at the beginning of the index buffergl.drawElements(primitiveType, count, indexType, drawOffset);Complete Example Code Snippet:
Section titled “Complete Example Code Snippet:”<!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).