Olha Stefanishyna
← Back to home

Getting Started with WebGL 2: A Developer's Journey

Recently, I've started to learn WebGL, and I've found that one of the best ways to reinforce new knowledge is to share it with others. I've been posting updates on X (Twitter), but the format is too limiting for technical articles, so I'm expanding into these blog articles to document my journey in the world of browser-based graphics programming.

What is WebGL 2?

WebGL 2 (Web Graphics Library) is a JavaScript API that allows rendering interactive 2D and 3D graphics in compatible web browsers without requiring plugins. It's based on OpenGL ES (Embedded Systems), specifically designed for mobile devices and web applications.

This is fundamentally different from traditional DOM manipulation. While DOM operations primarily work with elements in a tree structure, WebGL 2 provides low-level access to your graphics card through the canvas element, allowing for hardware-accelerated graphics rendering.

Here's a simple example of initializing a WebGL 2 context:

javascript
1// Get the canvas element
2const canvas = document.getElementById('myCanvas');
3
4// Initialize WebGL 2 context
5const gl = canvas.getContext('webgl2');
6
7if (!gl) {
8 console.error('WebGL 2 not supported or enabled on this browser');
9 // Fallback or error message
10} else {
11 console.log('WebGL 2 initialized successfully');
12 // Continue with your WebGL 2 code
13}

To better understand the difference in complexity between these APIs, let's compare what it takes to draw a simple red square in Canvas 2D and WebGL 2:

javascript
1// CANVAS 2D API - Drawing a red square (3 lines of code)
2const ctx = canvas.getContext('2d');
3ctx.fillStyle = 'red'; // Set the color to red
4ctx.fillRect(10, 10, 100, 100); // Draw a 100x100 square at position (10,10)
5
6// WEBGL 2 - Drawing a red square (simplified, but still ~30 lines minimum)
7const gl = canvas.getContext('webgl2');
8
9// 1. Define vertex shader (runs once per vertex)
10const vertexShaderSource = `#version 300 es
11in vec4 a_position;
12void main() {
13 gl_Position = a_position;
14}`;
15
16// 2. Define fragment shader (runs once per pixel)
17const fragmentShaderSource = `#version 300 es
18precision highp float;
19out vec4 outColor;
20void main() {
21 outColor = vec4(1.0, 0.0, 0.0, 1.0); // Red color
22}`;
23
24// 3. Create and compile shaders
25// ... (multiple lines of setup code omitted)
26
27// 4. Create program and link shaders
28// ... (more setup code omitted)
29
30// 5. Define geometry for a square
31// ... (more setup code omitted)
32
33// 6. Finally draw
34gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black
35gl.clear(gl.COLOR_BUFFER_BIT);
36gl.useProgram(program);
37gl.bindVertexArray(vao);
38gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Draw the square

This comparison illustrates the fundamental trade-off: Canvas 2D is designed for simplicity and requires minimal code for basic operations, while WebGL 2 demands more setup but gives you direct control over the GPU's rendering pipeline. This extra complexity enables the advanced 3D graphics and performance that WebGL 2 is designed for.

By leveraging GPU acceleration, WebGL 2 can render complex 3D scenes at 60fps or higher, enabling applications like:

  • Interactive 3D data visualizations
  • Browser-based games with sophisticated graphics
  • Architectural and product visualization tools
  • Physics simulations and particle systems
  • Image processing and filters with real-time performance

The cost? A steeper learning curve compared to other web technologies, but the payoff is tremendous visual power that was previously only available in native applications.

WebGL 2 as a State Machine

One of the first concepts needed to grasp is understanding that WebGL 2 functions as a state machine. This means its operations depend on previously set states rather than exclusively on parameters passed to functions - a fundamental characteristic that makes WebGL 2 different from typical JavaScript patterns.

WebGL 2 State Machine Diagram
WebGL 2 State Machine Diagram

In most JavaScript code, a function's behavior is determined primarily by the parameters you pass in:

javascript
1// Regular JavaScript: behavior determined by parameters
2const result = calculateSomething(paramA, paramB);

WebGL 2, however, works differently:

javascript
1// Set a state
2gl.useProgram(shaderProgram);
3gl.uniform1f(uniformLocation, 0.5);
4
5// This function's behavior depends on the states set above
6gl.drawArrays(gl.TRIANGLES, 0, 3);

In this example, drawArrays() will use whatever shader program and uniform values were previously set. Understanding this state-based architecture is crucial for effective WebGL 2 programming.

The Programmable Pipeline

A fundamental aspect of WebGL 2 is its enhanced programmable pipeline, implemented through shader programs.

There are two mandatory shader programs that you need to write:

  • Vertex Shader: Executes once per vertex. Must output gl_Position (the clip-space position)
  • Fragment Shader: Executes once per fragment (potential pixel). Must output a color value

Here's a minimal example of both using GLSL 3.00 ES (the shader language for WebGL 2):

glsl
1// Vertex Shader (GLSL 3.00 ES)
2#version 300 es
3in vec4 position;
4void main() {
5 gl_Position = position;
6}
7
8// Fragment Shader (GLSL 3.00 ES)
9#version 300 es
10precision highp float;
11out vec4 outColor;
12void main() {
13 outColor = vec4(1.0, 0.0, 0.0, 1.0); // Red color
14}

Note the key differences from WebGL 1:

  • The #version 300 es declaration
  • Using in instead of attribute
  • Using out for outputs instead of built-in variables like gl_FragColor

The programmable pipeline represents a fundamental shift from fixed-function graphics rendering to a flexible, developer-controlled system where you can write custom programs that execute directly on the GPU.

WebGL 2 significantly enhances this pipeline with features like:

  • Multiple render targets
  • Transform feedback
  • Uniform buffer objects
  • Instanced rendering
  • Integer textures and operations

Before programmable shaders, graphics hardware used a "fixed-function pipeline" with built-in, unchangeable algorithms for:

  • Vertex transformation (multiply by matrices)
  • Lighting (typically Phong or Blinn-Phong model)
  • Texture mapping (basic sampling only)
  • Fog calculations
  • Color blending

The modern programmable pipeline mirrors the physical hardware architecture of GPUs and enables efficient parallel execution of custom rendering code, giving developers unprecedented control over visual output.

WebGL 2 Coordinate System

Another important concept is the WebGL 2 coordinate system, which differs fundamentally from what web developers are used to in CSS.

In WebGL 2:

  • The origin (0,0) is located at the center of the canvas, not the top-left corner
  • The Y-axis points upward (positive is up), not downward
  • All coordinates are normalized between -1.0 and 1.0 regardless of canvas size
WebGL Coordinate System showing the normalized device coordinates with the origin (0,0) at center
WebGL Coordinate System showing the normalized device coordinates with the origin (0,0) at center

Understanding this coordinate system from the beginning helps prevent common positioning and orientation errors in your WebGL 2 projects.

What's Next

This post covered the WebGL 2 fundamentals. In upcoming articles, I'll deep dive into: shader techniques, performance optimization strategies, building a practical fluid simulation.

If you're working on graphics-heavy applications or need to squeeze more performance out of browser-based visualizations, WebGL 2's capabilities are worth the steeper learning curve.


This is part of my series on implementing interactive 3D visualizations with WebGL 2.

Let's talk