Skip to content

WebGL - Quick Guide

This guide provides a rapid overview of WebGL concepts and fundamentals.

Historically, rendering 3D graphics on the web involved browser plugins like Java applets (with JOGL) or Adobe Flash (with Stage3D). These approaches faced challenges with compatibility, security, and reliance on proprietary technology.

The need for a standardized, plugin-free way to access the GPU (Graphics Processing Unit) directly from web browsers led to the development of WebGL.

OpenGL (Open Graphics Library) is a long-standing, cross-platform API for rendering 2D and 3D graphics. WebGL is directly based on a specific version of OpenGL designed for embedded systems.

API/TechnologyDescription
OpenGL ESA subset of OpenGL tailored for embedded systems like mobile phones, consoles, and vehicles. WebGL 1.0 is based on OpenGL ES 2.0, and WebGL 2.0 is based on OpenGL ES 3.0. Maintained by the Khronos Group (www.khronos.org).
WebGLA JavaScript API that exposes OpenGL ES capabilities to web developers via the HTML <canvas> element. Allows for hardware-accelerated graphics rendering directly within compatible web browsers. WebGL 1.0 and WebGL 2.0 are the current standards, maintained by the Khronos Group.
GLSLOpenGL Shading Language. A C-like language used to write shaders – small programs that run directly on the GPU to control vertex positions and pixel colors. WebGL uses GLSL ES (GLSL for Embedded Systems).

WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 2D and 3D graphics within any compatible web browser without the use of plug-ins. It works by providing low-level access to the device’s GPU through the HTML <canvas> element.

WebGL integrates seamlessly with other web standards like HTML, CSS, and JavaScript, enabling rich graphical experiences within web applications.

The initial work was started by Vladimir Vukićević at Mozilla in 2006 as an experiment to port OpenGL to the Canvas element. The effort evolved into a collaborative project under the Khronos Group, involving major browser vendors like Apple, Google, Microsoft, and Mozilla. WebGL 1.0 was officially released in March 2011.

WebGL enjoys widespread support across modern desktop and mobile browsers.

Desktop Browsers (as of late 2023/early 2024)

Section titled “Desktop Browsers (as of late 2023/early 2024)”
BrowserSupport Status
Google ChromeYes (WebGL 1 & 2)
Mozilla FirefoxYes (WebGL 1 & 2)
Apple SafariYes (WebGL 1 & 2)
Microsoft EdgeYes (WebGL 1 & 2, Chromium-based)
OperaYes (WebGL 1 & 2, Chromium-based)

Mobile Browsers (as of late 2023/early 2024)

Section titled “Mobile Browsers (as of late 2023/early 2024)”
BrowserSupport Status
Chrome for AndroidYes (WebGL 1 & 2)
Safari on iOSYes (WebGL 1 & 2)
Firefox for AndroidYes (WebGL 1 & 2)
Samsung InternetYes (WebGL 1 & 2)

Note: Specific feature support and performance can vary. Check resources like caniuse.com (https://caniuse.com/?search=webgl) for detailed, up-to-date information.

  • Plugin-Free: Runs natively in the browser without requiring external plugins.
  • Hardware Acceleration: Leverages the GPU for high-performance graphics rendering.
  • Cross-Platform: Works on various operating systems and devices supported by compatible browsers.
  • JavaScript Integration: Seamlessly integrates with standard web technologies (HTML, CSS, DOM, other JS libraries).
  • Open Standard: Developed and maintained by the Khronos Group, ensuring interoperability.
  • Growing Ecosystem: Supported by numerous libraries and frameworks (like Three.js, Babylon.js) that simplify development.

No special setup is required beyond a modern web browser that supports WebGL and a text editor for writing code. Browser developer tools are essential for debugging.

Understanding core computer graphics concepts is fundamental to working with WebGL.

Rendering is the process of generating a 2D image from a 2D or 3D model description using computer programs. This description includes information about geometry (shapes), viewpoint (camera), textures (surface appearance), lighting, and shading. The rendering pipeline takes this scene description and produces the final pixel data for display.

Rendering Types:

  • Software Rendering: Calculations are performed primarily by the CPU. Generally slower for complex scenes.
  • Hardware Rendering: Computations are offloaded to the specialized Graphics Processing Unit (GPU). WebGL utilizes hardware rendering for performance.

WebGL performs client-side hardware rendering. The browser uses the user’s GPU to process graphics instructions sent via the JavaScript API.

A GPU is a specialized electronic circuit designed to rapidly manipulate and alter memory to accelerate the creation of images in a frame buffer intended for output to a display device. GPUs are highly parallel, meaning they can perform many calculations simultaneously, making them ideal for the repetitive tasks involved in graphics rendering (like processing thousands of vertices or millions of pixels).

Key GPU characteristics relevant to WebGL:

  • Parallel Processing: Executes many instances of small programs (shaders) concurrently.
  • Dedicated Memory (VRAM): Has its own high-speed memory for storing graphics data (textures, vertex data). Data must be transferred from main system RAM to VRAM for the GPU to access it efficiently.
  • Specialized Pipelines: Hardware is optimized for specific graphics tasks like transformations, clipping, rasterization, and texturing.

In the context of WebGL, GPU acceleration means the browser offloads the computationally intensive graphics tasks (defined by your JavaScript code and GLSL shaders) to the GPU. The CPU handles the main application logic, prepares data, and sends drawing commands to the GPU via the WebGL API.

Efficient WebGL development involves minimizing the communication overhead between the CPU and GPU. This often means uploading data (vertices, textures) to GPU memory (buffers) once and then referencing that data in multiple drawing commands, rather than sending new data for every frame.

The HTML5 <canvas> element provides a drawing surface within an HTML document. JavaScript can then access this surface to draw graphics dynamically. WebGL utilizes the <canvas> element as its target for rendering.

The <canvas> element itself is simple, primarily defined by its width and height attributes, which specify the size of the drawing buffer in pixels.

<canvas id="myCanvas" width="640" height="480">
<!-- Fallback content for browsers that don't support canvas -->
Your browser does not support the HTML canvas element.
</canvas>
  • id: A unique identifier used to access the element via JavaScript (e.g., using document.getElementById).
  • width: The width of the canvas drawing surface in CSS pixels.
  • height: The height of the canvas drawing surface in CSS pixels.

Important: It’s generally recommended to set the width and height attributes directly on the <canvas> tag rather than using CSS. Setting dimensions via CSS scales the canvas element and its contents, potentially leading to blurring, while setting the attributes defines the actual size of the drawing buffer.

This HTML creates a 300x300 canvas with a red border for visibility.

<!DOCTYPE html>
<html>
<head>
<title>Canvas Example</title>
<style>
#myRenderingCanvas {
border: 1px solid red;
}
</style>
</head>
<body>
<canvas id="myRenderingCanvas" width="300" height="300"></canvas>
</body>
</html>

This will display an empty 300x300 square box with a red border.

The <canvas> element is just a container. To draw on it, you need to obtain a rendering context using the getContext() method in JavaScript.

Common Context Types:

  • ‘2d’: Provides the CanvasRenderingContext2D interface for drawing 2D shapes, text, and images.
  • ‘webgl’: Provides the WebGLRenderingContext interface (based on OpenGL ES 2.0) for hardware-accelerated 2D and 3D graphics.
  • ‘webgl2’: Provides the WebGL2RenderingContext interface (based on OpenGL ES 3.0), offering more advanced features.
const canvas = document.getElementById('myRenderingCanvas');
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 150, 100); // Draw a green rectangle
} else {
console.error('2D context not supported');
}

To use WebGL, you request the 'webgl' or 'webgl2' context. It’s good practice to check if the context was successfully obtained, as WebGL might not be supported or enabled.

const canvas = document.getElementById('myRenderingCanvas');
// Try getting WebGL 2, fallback to WebGL 1
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!gl) {
alert('Unable to initialize WebGL. Your browser or machine may not support it.');
// Handle the error appropriately
} else {
// WebGL context obtained successfully! Start setting up.
gl.clearColor(0.9, 0.9, 0.8, 1.0); // Set clear color to light gray
gl.clear(gl.COLOR_BUFFER_BIT); // Clear the canvas
console.log('WebGL initialized successfully!');
}

Once you have the WebGL context (often stored in a variable named gl), you use its methods to configure the rendering pipeline, create buffers, compile shaders, and draw graphics.

For more details on the 2D Canvas API, refer to MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API

WebGL provides a low-level API based on OpenGL ES. Understanding its coordinate system and fundamental concepts like vertices, buffers, and shaders is crucial.

After vertex processing, WebGL expects coordinates to be in a system called Clip Space. This is a cube ranging from -1 to +1 on all three axes (X, Y, Z).

  • X-axis: +1 is to the right, -1 is to the left.
  • Y-axis: +1 is towards the top, -1 is towards the bottom.
  • Z-axis: +1 is towards the viewer (closer), -1 is away from the viewer (further). This might seem counter-intuitive if you expect Z to increase with depth, but it’s the standard convention in OpenGL/WebGL clip space.

Anything outside this [-1, +1] cube in any dimension will be “clipped” (discarded) and not rendered. Your vertex shader’s primary job is to transform your model’s original coordinates (often called Model Space or World Space) into this Clip Space, typically by multiplying them with Model, View, and Projection matrices.

A textual representation of the axes:

+Y
|
|
-X ----+---- +X
/|
/ |
+Z -Y (Towards Viewer)

Key terms and concepts in WebGL programming:

A vertex (plural: vertices) is a point in 2D or 3D space, usually defined by its coordinates (X, Y, Z). Vertices are the fundamental building blocks of geometric shapes (like triangles, lines, points). A vertex can also have associated data like color, texture coordinates, or normals (vectors indicating surface direction, used for lighting).

// Data for three vertices (X, Y, Z)
const triangleVertices = [
0.0, 0.5, 0.0, // Top vertex
-0.5, -0.5, 0.0, // Bottom-left vertex
0.5, -0.5, 0.0 // Bottom-right vertex
];

Often, multiple polygons in a mesh share the same vertices. To avoid duplicating vertex data, we can use an index buffer. We define each unique vertex once in a vertex buffer. The index buffer then contains a list of integers (indices) that specify the order in which to draw the vertices from the vertex buffer to form primitives (like triangles). This saves memory and can improve performance.

Example (Indices for a Quad using 4 vertices):

Section titled “Example (Indices for a Quad using 4 vertices):”
const quadVertices = [
-0.5, 0.5, 0.0, // Vertex 0: Top-left
-0.5, -0.5, 0.0, // Vertex 1: Bottom-left
0.5, -0.5, 0.0, // Vertex 2: Bottom-right
0.5, 0.5, 0.0 // Vertex 3: Top-right
];
// Indices define two triangles (0,1,2) and (0,2,3) to form the quad
const quadIndices = [
0, 1, 2, // First triangle
0, 2, 3 // Second triangle
];

WebGL requires geometric data (vertices, indices, colors, etc.) to be passed in Typed Arrays (e.g., Float32Array, Uint16Array). These are JavaScript objects that provide a mechanism for accessing raw binary data, which is essential for efficient transfer to the GPU.

const vertices = new Float32Array([ 0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0 ]);
const indices = new Uint16Array([ 0, 1, 2 ]);

WebGL Buffers are memory allocations on the GPU used to store graphics data. Data from JavaScript Typed Arrays is copied into these buffers.

  • Vertex Buffer Object (VBO): Stores per-vertex data like positions, colors, texture coordinates, normals. Data is typically bound to the gl.ARRAY_BUFFER target.
  • Index Buffer Object (IBO): Stores vertex indices. Data is typically bound to the gl.ELEMENT_ARRAY_BUFFER target.
  • Frame Buffer Object (FBO): Represents a collection of rendering destinations (color buffers, depth buffer, stencil buffer). By default, WebGL draws to the canvas’s main framebuffer, but FBOs allow for off-screen rendering, useful for effects like post-processing or reflections.

A mesh is a collection of vertices, edges, and faces (usually triangles or quads) that defines the shape of a 3D object. WebGL primarily renders using points, lines, or triangles. Complex objects are constructed by assembling many of these simple primitives into a mesh.

WebGL provides drawArrays() (uses only VBOs) and drawElements() (uses VBOs and an IBO) to render the primitives defined in the buffers, forming the final mesh on screen.

Shaders are small programs written in GLSL (OpenGL Shading Language) that execute directly on the GPU. They are essential for defining how vertices are transformed and how pixels are colored in WebGL.

Why Shaders?

  • Programmability: Give developers direct control over the graphics pipeline.
  • Performance: Run massively parallel computations on the GPU, essential for real-time graphics.
  • Custom Effects: Enable complex visual effects beyond the fixed-function capabilities of older graphics APIs.

There are two main types of shaders required for a basic WebGL program:

The vertex shader is executed once for each vertex you provide. Its primary responsibilities include:

  • Vertex Transformation: Calculating the final position of the vertex in Clip Space. This usually involves applying Model, View, and Projection matrices to the input vertex coordinates. The result must be assigned to the special built-in variable gl_Position (a vec4).
  • Processing Vertex Attributes: Receiving per-vertex data (like color, texture coordinates, normals) passed from your JavaScript code via attributes.
  • Passing Data to Fragment Shader: Forwarding processed data (like interpolated color or texture coordinates) to the fragment shader via varying variables.

After vertices are processed and assembled into primitives (like triangles), the GPU determines which pixels on the screen are covered by these primitives (Rasterization). The fragment shader is executed once for each potential pixel (fragment) within these primitives. Its main responsibility is:

  • Determining Pixel Color: Calculating the final color of the fragment. This can involve using interpolated values received from the vertex shader (via varying variables), sampling textures, performing lighting calculations, etc. The result must be assigned to the special built-in variable gl_FragColor (a vec4 representing RGBA color).

Note: Fragment shaders operate in parallel for many pixels, they don’t know about neighboring pixels directly.

GLSL ES (the version used by WebGL) uses qualifiers to specify how variables link data between JavaScript, the vertex shader, and the fragment shader.

  • attribute (WebGL 1 / GLSL ES 1.00): Used only in Vertex Shaders. Represents input data that changes per vertex (e.g., position, color, texture coordinates). Attributes receive their data from WebGL Buffers configured via vertexAttribPointer in JavaScript. Note: Replaced by in in WebGL 2 / GLSL ES 3.00.
  • uniform: Used in both Vertex and Fragment Shaders. Represents input data that is constant for all vertices/fragments within a single draw call (e.g., transformation matrices, light position, global color). Uniforms receive their data from JavaScript via gl.uniform* functions.
  • varying (WebGL 1 / GLSL ES 1.00): Used to pass data from the Vertex Shader to the Fragment Shader. The GPU interpolates (blends) the values of varying variables across the surface of the primitive. For example, vertex colors are blended to create smooth color gradients across a triangle. Note: Replaced by out (vertex shader) and in (fragment shader) in WebGL 2 / GLSL ES 3.00.

GLSL ES requires specifying precision for floating-point (float) and integer (int) types to manage performance and power consumption on diverse hardware. Common qualifiers are highp, mediump, and lowp.

It’s mandatory to specify a default precision for floats in the fragment shader. precision mediump float; is a common starting point.

// Fragment Shader Snippet
precision mediump float;
varying vec3 vColor; // Interpolated color from vertex shader
void main() {
gl_FragColor = vec4(vColor, 1.0);
}

The WebGL graphics pipeline (or rendering pipeline) is a sequence of stages that process your data (vertices, indices, textures) and shader programs to produce the final image on the canvas. Understanding this flow is key to effective WebGL programming.

  • Initialization: Get the WebGL context from the canvas.
  • Data Preparation: Define geometry (vertices, indices, colors, texture coordinates) using JavaScript arrays, typically Typed Arrays (Float32Array, Uint16Array).
  • Buffer Creation: Create WebGL buffer objects (gl.createBuffer).
  • Data Transfer: Bind buffers (gl.bindBuffer) and transfer data from JavaScript Typed Arrays to GPU memory (gl.bufferData).
  • Shader Management: Create (gl.createShader), compile (gl.compileShader), and link (gl.linkProgram) vertex and fragment shaders.
  • Attribute/Uniform Setup: Get locations of shader attributes (gl.getAttribLocation) and uniforms (gl.getUniformLocation). Configure attributes to read from buffers (gl.vertexAttribPointer, gl.enableVertexAttribArray). Set uniform values (gl.uniform*).
  • State Configuration: Set WebGL state like clear color, depth testing, blending, viewport.
  • Drawing Commands: Tell WebGL to draw using gl.drawArrays or gl.drawElements.
  • Receives vertex data via attribute variables linked to VBOs.
  • Receives global data via uniform variables.
  • Performs calculations like transformations (Model * View * Projection).
  • Outputs the final vertex position in Clip Space to gl_Position.
  • Outputs data for the fragment shader via varying variables.

3. Primitive Assembly (GPU Fixed Function):

Section titled “3. Primitive Assembly (GPU Fixed Function):”

Connects the processed vertices into specified geometric primitives (points, lines, triangles) based on the mode used in the draw call (gl.POINTS, gl.TRIANGLES, etc.) and the order defined by vertices or indices.

  • Clipping: Discards parts of primitives that lie outside the Clip Space volume [-1, +1].
  • Culling: Optionally discards primitives based on their orientation (front-facing or back-facing) using gl.enable(gl.CULL_FACE) and gl.cullFace(). Default is often to draw both sides.
  • Interpolation: Calculates the values of varying variables for each pixel inside the primitive by interpolating the values from the primitive’s vertices.
  • Receives interpolated data from the vertex shader via varying variables.
  • Receives global data via uniform variables.
  • Performs calculations like texture lookups, lighting effects, etc.
  • Outputs the final RGBA color for the fragment to gl_FragColor.

6. Per-Fragment Operations (GPU Fixed Function):

Section titled “6. Per-Fragment Operations (GPU Fixed Function):”
  • Scissor Test: Discards fragments outside a defined rectangular region (gl.enable(gl.SCISSOR_TEST), gl.scissor).
  • Stencil Test: Allows complex masking operations based on values in the stencil buffer (gl.enable(gl.STENCIL_TEST)).
  • Depth Test: Compares the fragment’s depth (Z value) with the value already in the depth buffer. Discards fragments that are behind previously drawn opaque objects (gl.enable(gl.DEPTH_TEST), gl.depthFunc). Crucial for correct 3D rendering.
  • Blending: Combines the fragment’s color with the color already in the color buffer, often used for transparency effects (gl.enable(gl.BLEND), gl.blendFunc).
  • Dithering: Can be used to simulate more colors than are available in the framebuffer (less common now).
  • Color Buffer: Stores the RGBA color values for each pixel.
  • Depth Buffer: Stores the depth (Z) value for each pixel.
  • Stencil Buffer: Stores stencil values for each pixel.

The contents of the default framebuffer are ultimately displayed on the canvas.

Let’s consolidate the concepts into a minimal, complete WebGL application that draws a single, static triangle.

A typical WebGL application involves:

  • HTML: A <canvas> element to provide the drawing surface.
  • CSS: (Optional) Styling for the canvas and surrounding page.
  • JavaScript: The main logic for initializing WebGL, setting up buffers, managing shaders, handling user input, and issuing draw commands.
  • GLSL: Vertex and Fragment shader code (often embedded in JavaScript strings or loaded from separate files).

This example follows the core steps discussed previously.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Simple Triangle</title>
<style>
body { margin: 0; overflow: hidden; background-color: #333; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="glCanvas" width="640" height="480"></canvas>
<script>
// --- Main Function ---
function main() {
const canvas = document.getElementById('glCanvas');
// Use webgl context, not experimental
const gl = canvas.getContext('webgl');
// Check if WebGL is available
if (!gl) {
alert('Unable to initialize WebGL. Your browser or machine may not support it.');
return;
}
// --- Step 3: Vertex and Fragment Shader Source ---
// Use template literals for readability
const vsSource = `
attribute vec3 aVertexPosition;
void main(void) {
// Directly use clip space coordinates for this simple example
gl_Position = vec4(aVertexPosition, 1.0);
}
`;
const fsSource = `
// Set default precision for floats
precision mediump float;
void main(void) {
// Simple solid color (Red)
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// --- Step 3: Compile and Link Shaders ---
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
if (!shaderProgram) return;
// Store program info (attribute/uniform locations)
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
},
// No uniforms needed for this simple example
};
// Check if attribute location was found
if (programInfo.attribLocations.vertexPosition < 0) {
console.error("Attribute 'aVertexPosition' not found in shader program.");
return;
}
// --- Step 2: Define Geometry and Create Buffers ---
const buffers = initBuffers(gl);
// --- Step 5: Drawing the Scene ---
drawScene(gl, programInfo, buffers);
}
// --- Helper Function: Initialize Shader Program ---
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
gl.deleteProgram(shaderProgram); // Clean up
return null;
}
return shaderProgram;
}
// --- Helper Function: Load and Compile Shader ---
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// --- Helper Function: Initialize Buffers ---
function initBuffers(gl) {
// Create buffer for the triangle's positions.
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Define vertices in clip space coordinates
const positions = [
0.0, 0.5, 0.0, // Top vertex
-0.5, -0.5, 0.0, // Bottom left vertex
0.5, -0.5, 0.0 // Bottom right vertex
];
// Pass the list of positions into WebGL (as a Float32Array)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
return {
position: positionBuffer,
};
}
// --- Helper Function: Draw the Scene ---
function drawScene(gl, programInfo, buffers) {
// --- Step 1 & 5: Prepare Canvas ---
gl.clearColor(0.0, 0.0, 0.4, 1.0); // Clear to dark blue
gl.clearDepth(1.0); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // Set viewport
// --- Step 4: Associate Shaders and Buffers ---
// Tell WebGL how to pull positions from the buffer
{
const numComponents = 3; // pull out 3 values per iteration (x,y,z)
const type = gl.FLOAT; // the data in the buffer is 32bit floats
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set to the next (0 = use type & numComponents)
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); // Important: Bind before pointer setup
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.vertexPosition);
}
// Tell WebGL to use our program when drawing
gl.useProgram(programInfo.program);
// --- Step 5: Draw ---
{
const offset = 0;
const vertexCount = 3; // Drawing 3 vertices for one triangle
gl.drawArrays(gl.TRIANGLES, offset, vertexCount);
}
}
// --- Run the main function when the page loads ---
window.onload = main;
</script>
</body>
</html>

Expected Output:

This code will display a solid red triangle pointing upwards, centered horizontally on a dark blue background within the 640x480 canvas.

Explanation of Steps in the Code:

Step 1: Prepare the canvas and get WebGL context

Done inside main() using document.getElementById and canvas.getContext('webgl'), including a check for success.

Step 2: Define the geometry and store it in buffer objects

Handled by the initBuffers() function. It defines the positions array and creates a Float32Array. It then creates a WebGL buffer (gl.createBuffer), binds it (gl.bindBuffer), and transfers the data (gl.bufferData).

Step 3: Create and compile Shader programs

The GLSL source code is defined in vsSource and fsSource. The initShaderProgram() function, using the helper loadShader(), compiles each shader and links them into a shaderProgram. Error checking is included.

Step 4: Associate the shader programs with buffer objects

Inside drawScene(), gl.bindBuffer selects the position buffer. gl.vertexAttribPointer tells WebGL how the data within the buffer is structured (3 floats per vertex, etc.) and links it to the aVertexPosition attribute location (obtained earlier). gl.enableVertexAttribArray activates this link.

Step 5: Drawing the required object (triangle)

Also within drawScene(). gl.clearColor, gl.clear, gl.enable(gl.DEPTH_TEST), and gl.viewport set up the drawing environment. gl.useProgram activates the compiled shaders. Finally, gl.drawArrays(gl.TRIANGLES, 0, 3) instructs the GPU to draw one triangle using the first 3 vertices from the currently configured buffers and attributes.

The starting point for any WebGL application is obtaining the WebGL rendering context from an HTML <canvas> element. This context object is the gateway to the WebGL API.

  • 1. Create an HTML Canvas: Add a <canvas> tag to your HTML document, giving it a unique id and specifying width and height attributes.
  • 2. Get Canvas Element in JavaScript: Use document.getElementById('yourCanvasId') to get a reference to the canvas DOM element.
  • 3. Request WebGL Context: Call the .getContext() method on the canvas element, passing the string 'webgl' or 'webgl2'.
<!DOCTYPE html>
<html>
<head>
<title>WebGL Context</title>
<style>
#myWebGLCanvas { border: 1px solid blue; }
</style>
</head>
<body>
<canvas id="myWebGLCanvas" width="500" height="500">
Your browser doesn't support HTML5 canvas.
</canvas>
<script src="app.js"></script> <!-- Link to your JavaScript -->
</body>
</html>
// Get the canvas element
const canvas = document.getElementById('myWebGLCanvas');
// Try to get the WebGL rendering context
const gl = canvas.getContext('webgl');
// Check if context was obtained
if (!gl) {
// Try WebGL 2 as a fallback or provide a more specific message
console.error('WebGL not supported, trying WebGL 2...');
gl = canvas.getContext('webgl2');
}
if (!gl) {
alert('WebGL is not supported by your browser or device.');
// Potentially display alternative content or message
} else {
console.log('WebGL context obtained successfully!');
// Proceed with WebGL setup...
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}

Older tutorials often used 'experimental-webgl'. While some browsers might still support this for backward compatibility, the standard and recommended context IDs are 'webgl' (for WebGL 1.0) and 'webgl2' (for WebGL 2.0). Always prefer the standard IDs.

The getContext() method can optionally accept a second argument: an object containing context attributes that request specific features or configurations for the drawing buffer.

const context = canvas.getContext(contextType, contextAttributes);

Common Attributes (default values vary slightly by browser implementation but generally follow these):

AttributeDescriptionTypical Default
alphaBoolean. If true, the drawing buffer has an alpha channel (for transparency). Set to false if you don’t need transparency and want potential performance gains.true
depthBoolean. If true, requests a depth buffer (at least 16 bits). Essential for 3D rendering to handle object occlusion correctly.true
stencilBoolean. If true, requests a stencil buffer (at least 8 bits). Used for complex masking effects.false
antialiasBoolean. If true, requests the browser to attempt anti-aliasing (smoothing jagged edges). Performance impact varies.true
premultipliedAlphaBoolean. If true, assumes colors in the drawing buffer have pre-multiplied alpha. Affects how blending operations work.true
preserveDrawingBufferBoolean. If true, the contents of the drawing buffer are preserved after being displayed. If false (default), the buffer’s contents may become undefined, which can be more efficient. Useful for taking screenshots or certain multi-pass effects, but usually kept false.false
powerPreferenceString: ‘default’, ‘low-power’, ‘high-performance’. Hints to the browser which GPU to use if multiple are available (e.g., integrated vs dedicated).‘default’
const contextOptions = {
alpha: false, // Don't need transparency
antialias: true, // Request anti-aliasing
depth: true, // Need depth buffer for 3D
stencil: false // Don't need stencil buffer
};
const gl = canvas.getContext('webgl', contextOptions);
if (!gl) {
// Handle error
}

The WebGLRenderingContext (or WebGL2RenderingContext) object returned by getContext() holds all the methods and state needed to interact with WebGL.

The context object has properties providing information about the canvas and drawing buffer:

S.No.AttributeDescription
1canvasA read-only reference back to the HTMLCanvasElement that created this context.
2drawingBufferWidthRead-only. The actual width of the drawing buffer in pixels. This might differ from canvas.width in high-DPI scenarios.
3drawingBufferHeightRead-only. The actual height of the drawing buffer in pixels. This might differ from canvas.height in high-DPI scenarios.

Geometry defines the shape of objects in WebGL. It’s primarily represented by vertices and, optionally, indices. This data needs to be stored in WebGL buffers on the GPU before it can be rendered.

You first define your geometric data using standard JavaScript arrays or, more efficiently, Typed Arrays.

// Using a standard array initially
const triangleVertexData = [
0.0, 0.5, 0.0, // Vertex 0 (X, Y, Z)
-0.5, -0.5, 0.0, // Vertex 1
0.5, -0.5, 0.0 // Vertex 2
];
// Convert to a Typed Array for WebGL
const vertices = new Float32Array(triangleVertexData);
const quadVertexData = [
// X, Y, Z
-0.5, 0.5, 0.0, // Vertex 0: Top-left
-0.5, -0.5, 0.0, // Vertex 1: Bottom-left
0.5, -0.5, 0.0, // Vertex 2: Bottom-right
0.5, 0.5, 0.0 // Vertex 3: Top-right
];
const quadIndexData = [
0, 1, 2, // First triangle (Top-left, Bottom-left, Bottom-right)
0, 2, 3 // Second triangle (Top-left, Bottom-right, Top-right)
];
// Convert to Typed Arrays
const quadVertices = new Float32Array(quadVertexData);
const quadIndices = new Uint16Array(quadIndexData); // Use Uint16Array for indices if < 65536 vertices

WebGL functions that transfer data to the GPU (like gl.bufferData) require Typed Arrays (Float32Array, Uint16Array, Int8Array, etc.). These arrays represent contiguous blocks of memory with a specific data type, allowing for efficient, low-level data transfer.

WebGL Buffers are chunks of memory managed by the GPU, designed to hold large amounts of data like vertex attributes or indices.

  • Vertex Buffer Object (VBO): Stores vertex attribute data (positions, colors, texture coordinates, normals). Bound to the gl.ARRAY_BUFFER target.
  • Index Buffer Object (IBO): Stores vertex indices for use with gl.drawElements. Bound to the gl.ELEMENT_ARRAY_BUFFER target.

Using buffers is crucial for performance because it minimizes data transfers between the CPU (JavaScript) and the GPU. You typically upload the data once during initialization.

Use gl.createBuffer() to ask WebGL to allocate a buffer object.

const vertexBuffer = gl.createBuffer();
const indexBuffer = gl.createBuffer();

Use gl.bindBuffer(target, buffer) to make a buffer ‘active’ on a specific target binding point. Subsequent buffer operations for that target will affect the bound buffer.

  • target: gl.ARRAY_BUFFER for vertex data, or gl.ELEMENT_ARRAY_BUFFER for index data.
  • buffer: The buffer object returned by gl.createBuffer().
// Bind the VBO
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Bind the IBO
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

Use gl.bufferData(target, data, usage) to copy data from your JavaScript Typed Array into the currently bound buffer on the GPU.

  • target: Same as in bindBuffer (gl.ARRAY_BUFFER or gl.ELEMENT_ARRAY_BUFFER).
  • data: The Typed Array containing the data (e.g., new Float32Array(...)).
  • usage: A hint to WebGL about how you intend to use the data. This helps the driver optimize memory placement. Common values:
    • gl.STATIC_DRAW: Data is set once and used many times (e.g., static model geometry). Most common.
    • gl.DYNAMIC_DRAW: Data is updated frequently and used many times (e.g., animated character vertices).
    • gl.STREAM_DRAW: Data is set once and used only a few times.
// Assuming 'vertices' is a Float32Array and 'indices' is a Uint16Array
// Transfer vertex data to the bound ARRAY_BUFFER (vertexBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Transfer index data to the bound ELEMENT_ARRAY_BUFFER (indexBuffer)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

4. Unbind the Buffer (Optional but Good Practice):

Section titled “4. Unbind the Buffer (Optional but Good Practice):”

You can unbind a buffer from a target by binding null to it: gl.bindBuffer(gl.ARRAY_BUFFER, null);. This prevents accidental modification of the buffer later.

gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

Once data is in the buffers, you need to tell the shaders how to access it using attributes (covered next).

S.No.MethodDescription
1createBuffer()Creates a new, empty WebGL buffer object.
2deleteBuffer(buffer)Deletes a WebGL buffer object, freeing GPU memory.
3bindBuffer(target, buffer)Binds a buffer object to a target (gl.ARRAY_BUFFER or gl.ELEMENT_ARRAY_BUFFER).
4bufferData(target, data, usage)Copies data from a Typed Array (data) into the buffer currently bound to target. Specifies usage hint.
5bufferSubData(target, offset, data)Updates a portion of an existing buffer’s data without reallocating the whole buffer.
6isBuffer(buffer)Returns true if the provided object is a valid WebGL buffer.
7getBufferParameter(target, pname)Retrieves information about the buffer bound to target (e.g., pname = gl.BUFFER_SIZE).

Shaders are the heart of programmable graphics in WebGL. They are small programs written in GLSL ES that run on the GPU, defining vertex transformations and pixel colors.

GLSL ES (OpenGL Shading Language for Embedded Systems) is a C-like language with features tailored for graphics processing.

S.No.TypeDescription
1voidRepresents no value (used for function return types).
2boolBoolean value (true or false).
3intSigned integer.
4floatFloating-point number (requires precision specification).
5vec2, vec3, vec4Floating-point vectors with 2, 3, or 4 components (e.g., for positions, colors, texture coordinates).
6bvec2, bvec3, bvec4Boolean vectors.
7ivec2, ivec3, ivec4Signed integer vectors.
8mat2, mat3, mat42x2, 3x3, 4x4 floating-point matrices (used for transformations).
9sampler2DRepresents a 2D texture unit, used to access texture data.
10samplerCubeRepresents a cube map texture unit.

Storage Qualifiers (WebGL 1 / GLSL ES 1.00):

Section titled “Storage Qualifiers (WebGL 1 / GLSL ES 1.00):”
S.No.QualifierDescription
1attributeVertex Shader input. Links per-vertex data from WebGL buffers. Value changes per vertex invocation.
2uniformVertex/Fragment Shader input. Links data constant across a draw call from JavaScript (gl.uniform*). Read-only in shader. Example: uniform mat4 uProjectionMatrix;
3varyingLinks data passed from Vertex Shader output to Fragment Shader input. Values are interpolated across primitives. Example: varying vec3 vColor;
4constDeclares a compile-time constant variable within the shader.

Specify the computational precision for float and int types: highp, mediump, lowp. Mandatory default float precision in fragment shaders (precision mediump float;). Can be specified per variable.

GLSL provides special built-in variables for essential pipeline interactions:

  • Vertex Shader Output:
    • gl_Position (highp vec4): Must be written to. The final vertex position in Clip Space.
    • gl_PointSize (mediump float): Optional. Sets the size (in pixels) for primitives drawn with gl.POINTS.
  • Fragment Shader Input/Output:
    • gl_FragCoord (mediump vec4): Read-only input. Contains the window-relative coordinates (x, y, z, 1/w) of the fragment.
    • gl_FrontFacing (bool): Read-only input. True if the fragment belongs to a front-facing primitive (useful for two-sided lighting).
    • gl_PointCoord (mediump vec2): Read-only input. Contains the coordinate within a point primitive (useful for texturing points).
    • gl_FragColor (mediump vec4): Must be written to in WebGL 1. The final RGBA output color of the fragment.
    • gl_FragData[n] (mediump vec4): Output array used for Multiple Render Targets (MRT) extension in WebGL 1 / standard in WebGL 2.
// GLSL ES 1.00 Vertex Shader
attribute vec3 aVertexPosition; // Input: Per-vertex position
uniform mat4 uModelViewMatrix; // Input: Model-view transform
uniform mat4 uProjectionMatrix; // Input: Projection transform
varying lowp vec4 vColor; // Output: Pass color to fragment shader
void main(void) {
// Calculate final clip space position
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
// Example: Set a fixed color to pass (could also be an attribute)
vColor = vec4(0.5, 0.0, 0.5, 1.0); // Purple
}
// GLSL ES 1.00 Fragment Shader
precision mediump float; // Mandatory precision for floats
varying lowp vec4 vColor; // Input: Interpolated color from vertex shader
void main(void) {
// Output the interpolated color
gl_FragColor = vColor;
}

Compiling and Linking Shaders in JavaScript

Section titled “Compiling and Linking Shaders in JavaScript”

You need JavaScript code to take your GLSL strings, send them to the GPU for compilation, and then link them into a usable WebGL program.

  • 1. Create Shader Objects: Use gl.createShader(type) where type is gl.VERTEX_SHADER or gl.FRAGMENT_SHADER.
  • 2. Attach Source Code: Use gl.shaderSource(shader, sourceString) to provide the GLSL code.
  • 3. Compile Shader: Use gl.compileShader(shader).
  • 4. Check Compilation Status: Use gl.getShaderParameter(shader, gl.COMPILE_STATUS) and gl.getShaderInfoLog(shader) to check for errors. This is crucial for debugging!
  • 5. Create Program Object: Use gl.createProgram().
  • 6. Attach Shaders: Use gl.attachShader(program, vertexShader) and gl.attachShader(program, fragmentShader).
  • 7. Link Program: Use gl.linkProgram(program).
  • 8. Check Linking Status: Use gl.getProgramParameter(program, gl.LINK_STATUS) and gl.getProgramInfoLog(program) to check for errors.
  • 9. Use Program: Use gl.useProgram(program) before drawing to activate this shader program.

Example (Helper functions from Sample Application):

Section titled “Example (Helper functions from Sample Application):”
// Function to load and compile a single shader
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(`Error compiling ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:`, gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// Function to initialize the shader program
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Error linking shader program:', gl.getProgramInfoLog(shaderProgram));
gl.deleteProgram(shaderProgram);
return null;
}
return shaderProgram;
}

Remember to implement robust error checking for both shader compilation and program linking, as GLSL errors are common during development.

After creating buffers with vertex data and compiling/linking your shader program, you need to tell WebGL how the data in the buffers corresponds to the attribute variables declared in your vertex shader.

This involves three main steps:

Each attribute variable in your shader program has a specific location (an index) assigned by WebGL during program linking. You need to query this location using gl.getAttribLocation(program, name). You’ll use this location index in subsequent steps.

  • program: The linked WebGL shader program object.
  • name: A string containing the exact name of the attribute variable in your vertex shader (e.g., 'aVertexPosition', 'aVertexColor').

It returns the integer location of the attribute, or -1 if the attribute is not found or inactive (perhaps optimized away by the compiler if unused). Always check if the location is valid (>= 0)!

const shaderProgram = /* ... your linked program ... */;
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
const colorAttribLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
if (positionAttribLocation < 0) {
console.error("Attribute 'aVertexPosition' not found in shader!");
}
if (colorAttribLocation < 0) {
console.error("Attribute 'aVertexColor' not found in shader!");
}

2. Point Attribute to the Vertex Buffer Object (VBO)

Section titled “2. Point Attribute to the Vertex Buffer Object (VBO)”

This is the core step where you specify how WebGL should interpret the raw byte data within the currently bound ARRAY_BUFFER to feed data into a specific attribute. This is done using gl.vertexAttribPointer(location, size, type, normalized, stride, offset).

Parameters:

  • location: The integer location of the attribute, obtained from getAttribLocation.
  • size: The number of components per vertex attribute (e.g., 3 for a vec3 position [x, y, z], 4 for a vec4 color [r, g, b, a]).
  • type: The data type of each component in the buffer (e.g., gl.FLOAT, gl.UNSIGNED_BYTE, gl.SHORT). Must match the data type used in bufferData.
  • normalized: Boolean. For non-float data types (like gl.BYTE, gl.UNSIGNED_BYTE), specifies whether to normalize the values to a range [-1, 1] (for signed types) or [0, 1] (for unsigned types) when accessed by the shader. Usually false for floats.
  • stride: The byte offset between consecutive vertex attributes. If your attributes are tightly packed (like [P1x, P1y, P1z, P2x, P2y, P2z,…]), the stride is 0. If data is interleaved (like [P1x, P1y, C1r, C1g, P2x, P2y, C2r, C2g,…]), stride is the number of bytes from the start of one vertex’s data to the start of the next.
  • offset: The byte offset within the buffer where the data for this attribute begins. Usually 0 if the buffer contains only this attribute’s data, or an offset if data is interleaved.

Important: You must have the relevant VBO bound to gl.ARRAY_BUFFER using gl.bindBuffer() before calling gl.vertexAttribPointer.

const positionBuffer = /* ... buffer with vertex positions ... */;
const positionAttribLocation = /* ... location of 'aVertexPosition' ... */;
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Bind the VBO
gl.vertexAttribPointer(
positionAttribLocation, // Attribute location
3, // size: 3 components per vertex (X, Y, Z)
gl.FLOAT, // type: the data is 32bit floats
false, // normalized: don't normalize
0, // stride: 0 = tightly packed
0 // offset: 0 = start at the beginning
);
// Buffer contains [PosX1, PosY1, PosZ1, ColR1, ColG1, ColB1, PosX2, PosY2, PosZ2, ColR2, ColG2, ColB2, ...]
const interleavedBuffer = /* ... buffer with interleaved data ... */;
const positionAttribLocation = /* ... location of 'aVertexPosition' ... */;
const colorAttribLocation = /* ... location of 'aVertexColor' ... */;
const bytesPerFloat = 4;
const stride = 6 * bytesPerFloat; // 6 floats per vertex (3 pos + 3 color)
gl.bindBuffer(gl.ARRAY_BUFFER, interleavedBuffer);
// Setup position attribute
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, stride, 0);
// Setup color attribute
const colorOffset = 3 * bytesPerFloat; // Color data starts after 3 position floats
gl.vertexAttribPointer(colorAttribLocation, 3, gl.FLOAT, false, stride, colorOffset);

Finally, you must enable the vertex attribute array using gl.enableVertexAttribArray(location). This tells WebGL to actually pull data from the buffer according to the setup defined by vertexAttribPointer when drawArrays or drawElements is called.

gl.enableVertexAttribArray(positionAttribLocation);
// If using color attribute as well:
// gl.enableVertexAttribArray(colorAttribLocation);

You can disable an attribute later using gl.disableVertexAttribArray(location) if needed.

Vertex Array Objects (VAOs) - WebGL 2 / Extension

Section titled “Vertex Array Objects (VAOs) - WebGL 2 / Extension”

Setting up multiple attributes with bindBuffer, vertexAttribPointer, and enableVertexAttribArray for each object can be repetitive. Vertex Array Objects (VAOs) (available in WebGL 2 or via the OES_vertex_array_object extension in WebGL 1) allow you to encapsulate all this attribute configuration state into a single object. You set up the VAO once, and then simply bind the VAO before drawing an object, significantly reducing the number of WebGL calls per draw.

While potentially beyond the absolute basics, VAOs are a crucial best practice for performance in applications with many objects or complex vertex setups. See MDN for details: https://developer.mozilla.org/en-US/docs/Web/API/WebGLVertexArrayObject

Once you have initialized WebGL, created buffers, compiled shaders, and configured vertex attributes, the final step is to issue drawing commands to render your geometry.

The two main drawing commands are gl.drawArrays() and gl.drawElements().

Use drawArrays when your vertices in the ARRAY_BUFFER are already ordered correctly to form the desired primitives, and you are not using an index buffer (ELEMENT_ARRAY_BUFFER).

void gl.drawArrays(mode, first, count);

Parameters:

  • mode: Specifies the type of primitive to render. Common values:
    • gl.POINTS: Renders individual points.
    • gl.LINES: Renders individual line segments (vertices 0-1, 2-3, etc.).
    • gl.LINE_STRIP: Renders connected line segments (0-1, 1-2, 2-3, …).
    • gl.LINE_LOOP: Like LINE_STRIP, but also connects the last vertex back to the first.
    • gl.TRIANGLES: Renders individual triangles (vertices 0-1-2, 3-4-5, etc.).
    • gl.TRIANGLE_STRIP: Renders connected triangles (0-1-2, 1-2-3, 2-3-4, …). Efficient for meshes.
    • gl.TRIANGLE_FAN: Renders connected triangles sharing the first vertex (0-1-2, 0-2-3, 0-3-4, …). Useful for circles/cones.
  • first: The starting index in the enabled vertex attribute arrays.
  • count: The number of vertices to render.
// Assumes a VBO with 3 vertices is bound and attributes are configured
const vertexCount = 3;
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
// Assumes a VBO with 6 vertices (3 for first triangle, 3 for second)
const vertexCount = 6;
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);

Use drawElements when you have defined unique vertices in an ARRAY_BUFFER (VBO) and are using an ELEMENT_ARRAY_BUFFER (IBO) containing indices to specify the order in which to draw those vertices.

This is generally more efficient for complex meshes as it avoids duplicating vertex data.

void gl.drawElements(mode, count, type, offset);

Parameters:

  • mode: Same primitive types as drawArrays (e.g., gl.TRIANGLES).
  • count: The number of indices to render from the index buffer.
  • type: The data type of the values in the index buffer. Must be gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, or (with an extension/WebGL 2) gl.UNSIGNED_INT. This must match the Typed Array type used for the index buffer (e.g., Uint16Array matches gl.UNSIGNED_SHORT).
  • offset: A byte offset into the ELEMENT_ARRAY_BUFFER where the indices begin. Usually 0.

Important: You must have the relevant IBO bound to gl.ELEMENT_ARRAY_BUFFER using gl.bindBuffer() before calling gl.drawElements.

Example (Drawing a quad using 4 vertices and 6 indices):

Section titled “Example (Drawing a quad using 4 vertices and 6 indices):”
// Assume VBO with 4 vertices is bound to ARRAY_BUFFER and attributes configured
// Assume IBO with 6 indices [0,1,2, 0,2,3] is bound to ELEMENT_ARRAY_BUFFER
const indexCount = 6;
const indexType = gl.UNSIGNED_SHORT; // Assuming Uint16Array was used for indices
const offset = 0;
gl.drawElements(gl.TRIANGLES, indexCount, indexType, offset);

Before calling drawArrays or drawElements in each frame (or each time you draw an object), you typically need to perform these setup operations:

Use gl.clearColor(r, g, b, a) to set the background color (values 0.0 to 1.0). Then use gl.clear(mask) to actually clear the buffers.

The mask is a bitwise OR combination of:

  • gl.COLOR_BUFFER_BIT: Clears the color buffer.
  • gl.DEPTH_BUFFER_BIT: Clears the depth buffer.
  • gl.STENCIL_BUFFER_BIT: Clears the stencil buffer.
gl.clearColor(0.1, 0.1, 0.2, 1.0); // Dark slate blue
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear color and depth

For 3D rendering, enable depth testing so objects closer to the camera correctly obscure objects farther away: gl.enable(gl.DEPTH_TEST);. You might also set the depth function using gl.depthFunc(gl.LEQUAL); (the default).

gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL); // Default, but good to be explicit

Define the rectangular area of the canvas where WebGL should draw using gl.viewport(x, y, width, height). This maps the normalized [-1, +1] clip space coordinates to pixel coordinates on the canvas.

// Typically set to the full canvas size
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

Activate the desired shader program: gl.useProgram(yourShaderProgram);

5. Bind Buffers and Set Attributes/Uniforms:

Section titled “5. Bind Buffers and Set Attributes/Uniforms:”

Ensure the correct VBOs are bound (gl.bindBuffer(gl.ARRAY_BUFFER, ...)), attribute pointers are set (gl.vertexAttribPointer, gl.enableVertexAttribArray), the correct IBO is bound if using drawElements (gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ...)), and any necessary uniforms (like transformation matrices, colors, texture units) are updated for the current object/frame (gl.uniform*).

After these setup steps, you can finally call gl.drawArrays(...) or gl.drawElements(...).