WebGL - Shaders
WebGL - Shaders
Section titled “WebGL - Shaders”Shaders are small programs written in a C-like language called GLSL (OpenGL Shading Language) that execute directly on the Graphics Processing Unit (GPU). They are fundamental to WebGL, defining how vertices are positioned and how pixels (fragments) are colored.
WebGL 1.0 uses GLSL ES 1.00, which is based on OpenGL ES 2.0’s shading language. WebGL 2.0 uses the more feature-rich GLSL ES 3.00, based on OpenGL ES 3.0. This tutorial primarily focuses on GLSL ES 1.00 concepts relevant to WebGL 1.0.
GLSL has its own data types, qualifiers, built-in variables, and functions tailored for graphics programming.
Core GLSL ES 1.00 Data Types
Section titled “Core GLSL ES 1.00 Data Types”Common data types used in WebGL shaders:
| S.No. | Type | Description |
|---|---|---|
| 1 | void | Represents no value, typically used for function return types that don’t return anything. |
| 2 | bool | Represents a boolean value (true or false). |
| 3 | int | Represents a signed integer. |
| 4 | float | Represents a single-precision floating-point number. |
| 5 | vec2, vec3, vec4 | Represents a 2, 3, or 4-component floating-point vector (e.g., for positions, colors, texture coordinates). |
| 6 | bvec2, bvec3, bvec4 | Represents a 2, 3, or 4-component boolean vector. |
| 7 | ivec2, ivec3, ivec4 | Represents a 2, 3, or 4-component signed integer vector. |
| 8 | mat2, mat3, mat4 | Represents a 2x2, 3x3, or 4x4 floating-point matrix (used for transformations). |
| 9 | sampler2D | Used in fragment shaders to access 2D textures. |
| 10 | samplerCube | Used in fragment shaders to access cube map textures. |
GLSL ES 1.00 Storage Qualifiers
Section titled “GLSL ES 1.00 Storage Qualifiers”Qualifiers define how variables are stored and how they communicate data between the WebGL application and the shaders, or between shader stages.
| S.No. | Qualifier | Description |
|---|---|---|
| 1 | attribute | (Vertex Shader Only) Links per-vertex data (like position, color, texture coordinates) provided by the WebGL application (via buffer objects) to the vertex shader. The value changes for each vertex processed. Note: Replaced by in in GLSL ES 3.00 / WebGL 2. |
| 2 | uniform | Links data provided by the WebGL application that remains constant across all vertices or fragments for a single draw call (e.g., transformation matrices, lighting parameters, textures). Read-only within the shader. Can be used in both vertex and fragment shaders. Example: uniform mat4 u_modelViewProjectionMatrix; |
| 3 | varying | Passes data from the vertex shader to the fragment shader. The GPU interpolates these values across the primitive’s surface before they reach the fragment shader. Example: varying vec3 v_normal;. Note: Replaced by out (in vertex shader) and in (in fragment shader) in GLSL ES 3.00 / WebGL 2. |
Precision Qualifiers
Section titled “Precision Qualifiers”GLSL ES requires specifying precision for float and int types to manage performance and power consumption, especially on mobile devices. Qualifiers are highp, mediump, lowp. You usually set a default precision at the beginning of the shader.
// Example: Setting default float precision in a fragment shaderprecision mediump float;Vertex Shader
Section titled “Vertex Shader”The vertex shader is executed once for each vertex submitted in a draw call. Its primary responsibility is to calculate the final position of the vertex in 3D space (clip space coordinates). It receives per-vertex data via attribute variables and global data via uniform variables.
Key tasks often performed in a vertex shader:
- Applying transformations (model, view, projection matrices) to vertex positions.
- Transforming normal vectors for lighting calculations.
- Generating or transforming texture coordinates.
- Passing data (like color, normals, texture coordinates) to the fragment shader via
varyingvariables.
Built-in Vertex Shader Variables (GLSL ES 1.00)
Section titled “Built-in Vertex Shader Variables (GLSL ES 1.00)”| S.No. | Variable | Description |
|---|---|---|
| 1 | gl_Position | (highp vec4, Output) The mandatory output of the vertex shader. It specifies the vertex’s position in clip space coordinates. The subsequent fixed-function stages use this value for primitive assembly, clipping, etc. |
| 2 | gl_PointSize | (mediump float, Output) Specifies the size (in pixels) to render point primitives (gl.POINTS). Only relevant when drawing points. |
Simple Vertex Shader Example
Section titled “Simple Vertex Shader Example”This minimal vertex shader takes 2D coordinates and outputs them as clip space positions.
// Input: Per-vertex 2D coordinates from a bufferattribute vec2 a_position;
// Uniform: A transformation matrix (example)uniform mat4 u_matrix;
void main() { // Output the final clip space position. // Convert 2D position to 4D (x, y, z, w) // z = 0.0 (assuming 2D drawing on z=0 plane) // w = 1.0 (required for perspective division) // Apply transformation matrix (optional) gl_Position = u_matrix * vec4(a_position, 0.0, 1.0);}In this example, a_position receives data from a vertex buffer linked via JavaScript. u_matrix would be set via gl.uniformMatrix4fv. The final gl_Position determines where the vertex appears on screen after perspective division and viewport transformation.
Fragment Shader (Pixel Shader)
Section titled “Fragment Shader (Pixel Shader)”After vertices are processed and assembled into primitives (like triangles), the fragment shader is executed for every pixel covered by the primitive on the screen. Its primary responsibility is to determine the final color of that pixel (fragment).
Key tasks often performed in a fragment shader:
- Calculating the final color based on interpolated data (from
varyingvariables), uniforms, and textures. - Performing lighting calculations.
- Applying textures to surfaces.
- Implementing effects like fog.
Built-in Fragment Shader Variables (GLSL ES 1.00)
Section titled “Built-in Fragment Shader Variables (GLSL ES 1.00)”| S.No. | Variable | Description |
|---|---|---|
| 1 | gl_FragCoord | (mediump vec4, Read-only Input) Contains the window-relative coordinates (x, y, z, 1/w) of the fragment. |
| 2 | gl_FrontFacing | (bool, Read-only Input) true if the fragment belongs to a front-facing primitive (based on vertex winding order and culling settings). |
| 3 | gl_PointCoord | (mediump vec2, Read-only Input) Contains the 2D coordinate within a point primitive (gl.POINTS), ranging from 0.0 to 1.0. Useful for texturing points. |
| 4 | gl_FragColor | (mediump vec4, Output) The primary output variable. Specifies the final RGBA color for the fragment. Note: Deprecated in GLSL ES 3.00 / WebGL 2, where custom out variables are used. |
| 5 | gl_FragData[n] | (mediump vec4, Output Array) Used for writing to multiple render targets (MRT), an advanced feature available via extensions in WebGL 1 or standard in WebGL 2. Note: Also deprecated in GLSL ES 3.00. |
Simple Fragment Shader Example
Section titled “Simple Fragment Shader Example”This minimal fragment shader sets every pixel of the primitive to a solid green color.
// Set default precision for floatsprecision mediump float;
void main() { // Output a solid green color (Red=0, Green=0.8, Blue=0, Alpha=1.0) gl_FragColor = vec4(0.0, 0.8, 0.0, 1.0);}The gl_FragColor value determined here is written to the color buffer for the current pixel.
Storing, Compiling, and Linking Shaders in JavaScript
Section titled “Storing, Compiling, and Linking Shaders in JavaScript”In your JavaScript code, you need to load, compile, and link your vertex and fragment shaders into a shader program that WebGL can execute.
1. Storing Shader Source Code
Section titled “1. Storing Shader Source Code”Shader code is typically stored as JavaScript strings. You can embed them directly or fetch them from separate files.
const vsSource = ` attribute vec4 a_position; void main() { gl_Position = a_position; }`;
const fsSource = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Red }`;2. Compiling Individual Shaders
Section titled “2. Compiling Individual Shaders”Each shader (vertex and fragment) must be compiled individually.
Steps:
- Create a shader object:
const shader = gl.createShader(type);(wheretypeisgl.VERTEX_SHADERorgl.FRAGMENT_SHADER) - Attach the source code:
gl.shaderSource(shader, sourceString); - Compile the shader:
gl.compileShader(shader); - Check for compilation errors (Crucial!): Use
gl.getShaderParameter(shader, gl.COMPILE_STATUS)andgl.getShaderInfoLog(shader)to detect and report errors. Compilation failures are common!
Example Helper Function (Conceptual):
function loadShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader);
// Check compilation status if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(`An error occurred compiling the ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${gl.getShaderInfoLog(shader)}`); gl.deleteShader(shader); return null; }
return shader;}
// Usage:const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);3. Creating and Linking the Shader Program
Section titled “3. Creating and Linking the Shader Program”Once both shaders are compiled successfully, link them together into a single shader program.
Steps:
- Create a program object:
const shaderProgram = gl.createProgram(); - Attach the compiled shaders:
gl.attachShader(shaderProgram, vertexShader);,gl.attachShader(shaderProgram, fragmentShader); - Link the program:
gl.linkProgram(shaderProgram); - Check for linking errors (Crucial!): Use
gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)andgl.getProgramInfoLog(shaderProgram)to verify successful linking.
Example Linking Code (Conceptual):
// Assuming vertexShader and fragmentShader are compiled successfullyconst shaderProgram = gl.createProgram();gl.attachShader(shaderProgram, vertexShader);gl.attachShader(shaderProgram, fragmentShader);gl.linkProgram(shaderProgram);
// Check linking statusif (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { console.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`); gl.deleteProgram(shaderProgram); // Clean up gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return null;}
// Optional: Detach and delete shaders after successful linking if no longer needed// gl.detachShader(shaderProgram, vertexShader);// gl.detachShader(shaderProgram, fragmentShader);// gl.deleteShader(vertexShader);// gl.deleteShader(fragmentShader);
// Tell WebGL to use this program for subsequent drawing operationsgl.useProgram(shaderProgram);
// Later, you'll get locations of attributes and uniforms...4. Using the Program
Section titled “4. Using the Program”Before drawing, you must tell WebGL which shader program to use with gl.useProgram(shaderProgram);. Only one program can be active at a time.
Error checking during compilation and linking is essential for debugging WebGL applications. Shader errors often manifest as a blank screen or unexpected visual output.
For more complex shader development, consider tools like Shadertoy or GLSL Sandbox for live editing and viewing.