Learn WebGL in 30 Days

Learn WebGL in 30 Days

Verified Sources
Jun 25, 2026

WebGL is a low-level JavaScript API that provides GPU-accelerated rendering of 2D and 3D graphics directly inside any compatible web browser — no plugins required. Built on OpenGL ES, WebGL 1.0 exposes the OpenGL ES 2.0 feature set, while WebGL 2.0 brings the more powerful OpenGL ES 3.0 feature set to the browser. As of 2024, WebGL 2.0 enjoys pervasive support across all major browsers including Chrome, Firefox, Safari, and Edge.

This comprehensive 30-day curriculum takes you from zero to proficient. You will master the rendering pipeline, write GLSL shaders from scratch, implement lighting models, handle textures, and optimize your real-time applications for production. Whether your goal is data visualization, browser-based games, or immersive 3D web experiences, this course provides the structured path you need.

The WebGL rendering pipeline involves programmable stages (vertex shader, fragment shader) and fixed-function stages (rasterization, per-fragment operations). Understanding this pipeline is the foundation of everything we build:

WebGL operates through a context obtained from an HTML <canvas> element. Everything you draw passes through the GPU pipeline described above. By the end of Day 30, you will have built a complete 3D scene with lighting, textures, interactions, and performance optimizations.

Footnotes

  1. WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs.

  2. WebGL 2.0 Achieves Pervasive Support — Khronos - All major browsers now ship WebGL 2.0 support.

30-Day WebGL Learning Roadmap

Foundations

Days 1–5

Set up the WebGL context, understand the graphics pipeline, draw your first triangle, and learn GLSL basics."

Transformations & 3D

Days 6–10

Model/View/Projection matrices, 3D geometry, index buffers, and camera fundamentals."

Lighting & Shading

Days 11–15

Phong lighting model, normals, multiple light types, and smooth Gouraud/Phong shading."

Textures & Materials

Days 16–20

Texture mapping, mipmaps, UV coordinates, normal maps, and cubemap environment reflections."

Advanced Techniques

Days 21–25

Framebuffers, post-processing, instancing, shadow mapping, and particle systems."

Optimization & Project

Days 26–30

Draw call batching, state management, profiling, and a capstone 3D scene project."

Week 1: Foundations (Days 1–5)

Day 1 — Setting Up the WebGL Context

Everything begins with a <canvas> element and a call to getContext('webgl2') (or 'webgl' for fallback). WebGL 2.0, based on OpenGL ES 3.0, provides a significantly more capable pipeline including uniform buffer objects, transform feedback, instanced rendering, and multiple render targets.

1const canvas = document.getElementById('glCanvas'); 2const gl = canvas.getContext('webgl2'); // Falls back to 'webgl' if needed 3if (!gl) { 4 console.error('WebGL not supported'); 5}

Day 2 — The Rendering Pipeline

The WebGL pipeline transforms 3D vertex data into colored pixels on your screen. It has programmable stages (where you write code) and fixed-function stages (handled by hardware):

StageTypePurpose
Vertex ShaderProgrammableTransform vertex positions, pass attributes
Primitive AssemblyFixedAssemble vertices into points/lines/triangles
RasterizationFixedGenerate fragments (potential pixels)
Fragment ShaderProgrammableCompute per-fragment color
Depth/Stencil TestFixedDetermine visibility of fragments
BlendingFixedCombine fragment color with framebuffer

Day 3–4 — Drawing Your First Triangle

The traditional "Hello World" of graphics programming. You write two shaders: a vertex shader that positions each corner of the triangle, and a fragment shader that colors every pixel inside it. These are compiled, linked into a shader program, and executed via gl.drawArrays().

Day 5 — GLSL Data Types and Varyings

GLSL (OpenGL Shading Language) is a C-like language with built-in vector and matrix types. Key types include float, vec2/3/4, mat3/4, int, and sampler2D. Data flows between the CPU and GPU through three channels:

  • Attributes: Per-vertex data (position, normal, UV) — supplied via buffers
  • Uniforms: Global constants sent once per draw call (matrices, colors, time)
  • Varyings: Data passed from the vertex shader to the fragment shader, interpolated across the primitive surface

Footnotes

  1. WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs.

  2. WebGL Tutorial — MDN - Official MDN WebGL tutorial covering context setup, shaders, and drawing basics.

Compiling and Linking a WebGL Shader Program

  1. 1
    Step 1

    Define your vertex shader and fragment shader as GLSL strings. The vertex shader must write to gl_Position; the fragment shader must assign to the output variable (e.g., outColor).

  2. 2
    Step 2

    Create shader objects with gl.createShader(), attach source with gl.shaderSource(), and compile with gl.compileShader(). Always check gl.getShaderParameter(shader, gl.COMPILE_STATUS) for compilation errors.

  3. 3
    Step 3

    Create a program with gl.createProgram(), attach both compiled shaders with gl.attachShader(), then link with gl.linkProgram(). Verify success with gl.getProgramParameter(program, gl.LINK_STATUS).

  4. 4
    Step 4

    Use gl.getAttribLocation(program, name) and gl.getUniformLocation(program, name) to cache the indices/locations for later binding. This avoids costly look-ups during render loops.

  5. 5
    Step 5

    Call gl.useProgram(program), bind your vertex buffers, set uniform values, and issue a draw call like gl.drawArrays(gl.TRIANGLES, 0, vertexCount) or gl.drawElements().

Week 2: Transformations & 3D (Days 6–10)

Understanding Coordinate Spaces

A 3D model passes through several coordinate systems before reaching your screen. Each transformation is represented by a 4×44 \times 4 homogeneous matrix:

pclip=MprojMviewMmodelplocal\mathbf{p}_{\text{clip}} = \mathbf{M}_{\text{proj}} \cdot \mathbf{M}_{\text{view}} \cdot \mathbf{M}_{\text{model}} \cdot \mathbf{p}_{\text{local}}

SpacePurposeMatrix
Local / ObjectOriginal vertex positions
WorldPosition in the sceneModel Matrix
View / CameraRelative to the cameraView Matrix
ClipNormalized for clippingProjection Matrix
NDCNormalized Device Coordinates ([1,1][-1,1])Perspective Division
ScreenPixel coordinatesViewport Transform

The View and Projection Matrices

The view matrix encodes the camera's position and orientation. The projection matrix defines the visible viewing volume — a frustum for perspective projection or a box for orthographic projection.

For perspective projection:

Mproj=[1atan(fov2)00001tan(fov2)0000f+nfn2fnfn0010]\mathbf{M}_{\text{proj}} = \begin{bmatrix} \frac{1}{a \cdot \tan(\frac{fov}{2})} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\frac{fov}{2})} & 0 & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix}

where aa is the aspect ratio, fovfov is the vertical field of view, and nn and ff are the near and far plane distances.

Cubes and Index Buffers

A cube has 6 faces × 2 triangles × 3 vertices = 36 vertices — but only 8 unique corner positions. Index buffers (also called element buffers) let you define 8 vertices and then index into them, dramatically reducing memory and data transfer.

Understanding Shaders Is Easy, Actually

Week 3: Lighting & Shading (Days 11–15)

The Phong Reflection Model

The most widely taught lighting model separates light into three components:

I=Iambient+Idiffuse+IspecularI = I_{\text{ambient}} + I_{\text{diffuse}} + I_{\text{specular}}

ComponentFormulaEffect
AmbientkaIak_a \cdot I_aConstant base illumination
Diffusekd(LN)Idk_d \cdot (\mathbf{L} \cdot \mathbf{N}) \cdot I_dSmooth shading based on light direction L\mathbf{L} and surface normal N\mathbf{N}
Specularks(RV)αIsk_s \cdot (\mathbf{R} \cdot \mathbf{V})^{\alpha} \cdot I_sHighlight/shininess based on reflection R\mathbf{R} and view V\mathbf{V}

The exponent α\alpha controls the shininess of the surface — high values produce sharp, mirror-like highlights; low values produce broad, plastic-like reflections.

Surface Normals

Normals are unit vectors perpendicular to the surface at each vertex. For smooth surfaces, vertex normals are computed as the average of adjacent face normals. In the shader, you must transform normals with the inverse-transpose of the model matrix:

N=(M1)N\mathbf{N}' = (\mathbf{M}^{-1})^{\top} \cdot \mathbf{N}

Failure to apply this transform results in incorrect lighting when the object is non-uniformly scaled.

Gouraud vs. Phong Shading

In Gouraud shading, lighting is computed per-vertex in the vertex shader and interpolated across fragments. In Phong shading, normals are interpolated and lighting is computed per-fragment. Phong shading produces much more accurate results, especially on low-poly geometry, at a higher fragment processing cost.

Footnotes

  1. WebGL 2.0 Achieves Pervasive Support — Khronos - All major browsers now ship WebGL 2.0 support.

WebGL Shader & Lighting Concepts

1 / 5
Question · Term

What is the role of the vertex shader?

Click to reveal
Answer · Definition

It transforms each vertex's position from local space to clip space via gl_Position, and passes per-vertex attributes (normals, UVs, colors) to the rasterizer via varyings/out variables.

Week 4: Textures & Materials (Days 16–20)

Texture Mapping

Texture mapping assigns a 2D image to a 3D surface, vastly increasing visual detail without additional geometry. Each vertex receives UV coordinates in the range [0,1][0, 1], and the GPU interpolates these across fragments. The fragment shader then samples the texture:

1// Fragment shader (GLSL ES 3.00) 2uniform sampler2D uTexture; 3in vec2 vUv; 4out vec4 fragColor; 5 6void main() { 7 fragColor = texture(uTexture, vUv); 8}

Mipmaps and Filtering

Mipmaps are precomputed downscaled versions of a texture. They cost only ~33% extra memory but dramatically improve both quality (reducing moiré/aliasing) and performance (fewer cache misses at distance). Generate them with gl.generateMipmap(gl.TEXTURE_2D) after uploading the image.

Common texture filter modes:

FilterBehavior
NEARESTPick closest texel — fast, pixelated
LINEARBilinear interpolation — smooth
NEAREST_MIPMAP_NEARESTPick closest mipmap level, then nearest texel
LINEAR_MIPMAP_LINEARTrilinear interpolation — highest quality

Normal Maps

Rather than adding geometric detail, normal mapping encodes perturbed normals in a blueish-purple texture. The fragment shader unpacks the normal from RGB and uses it for lighting calculations, creating the illusion of fine surface detail without the cost of additional triangles.

Cube Maps and Environment Reflections

A cube map provides six images (positive/negative X, Y, Z) forming a full environment. The fragment shader computes a reflection vector R=I2(NI)N\mathbf{R} = \mathbf{I} - 2(\mathbf{N} \cdot \mathbf{I})\mathbf{N} and samples the cube map to produce realistic environment reflections or skybox backgrounds.

Footnotes

  1. WebGL Best Practices — MDN - Browser vendor recommendations for mipmap usage, shader builtins, vertex shader work, and rendering resolution.

Power of Two Textures

While WebGL 2 supports non-power-of-two (NPOT) textures more flexibly than WebGL 1, mipmapping and repeat wrap modes still work most reliably with power-of-two dimensions (256, 512, 1024, etc.). If your textures look wrong or generate errors, check whether your dimensions are powers of two and whether you've properly configured gl.texParameteri() for wrap and filter modes.

Week 5: Advanced Techniques (Days 21–25)

Framebuffer Objects and Offscreen Rendering

A framebuffer object (FBO) lets you render to a texture instead of the screen. This is the foundation of virtually every advanced technique: shadow maps, post-processing effects (bloom, blur, tone mapping), deferred shading, and mirror/reflection cameras.

The workflow:

  1. Create FBO and attach a color texture (and optionally a depth texture/renderbuffer)
  2. Bind the FBO with gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)
  3. Render your scene — output goes to the attached textures
  4. Unbind (gl.bindFramebuffer(gl.FRAMEBUFFER, null)) to return to the screen
  5. Render a fullscreen quad, sampling the FBO's color texture to apply post-processing

Shadow Mapping

Shadow mapping is a two-pass technique:

  1. Depth pass: Render the scene from the light's perspective into a depth FBO
  2. Shadow pass: Render the scene from the camera's perspective, comparing each fragment's depth (from the light's view) against the shadow map. If the fragment is farther than the stored depth, it's in shadow.

Instanced Rendering

WebGL 2 provides gl.drawArraysInstanced() and gl.drawElementsInstanced(), which draw the same geometry many times with per-instance attributes (position offsets, colors, sizes) — perfect for particle systems, forests of trees, or crowd rendering. This can reduce draw calls from thousands to one.

Footnotes

  1. WebGL Academy — Interactive Tutorials - Progressive tutorials covering shadow mapping, deferred shading, and advanced techniques.

  2. WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs.

1#version 300 es 2precision highp float; 3 4// Per-vertex attributes 5in vec3 aPosition; 6in vec3 aNormal; 7in vec2 aUv; 8 9// Per-instance attributes 10in vec3 aInstanceOffset; 11in vec4 aInstanceColor; 12 13// Uniforms 14uniform mat4 uModelView; 15uniform mat4 uProjection; 16uniform mat3 uNormalMatrix; 17 18// Outputs to fragment shader 19out vec3 vNormal; 20out vec2 vUv; 21out vec4 vColor; 22out vec3 vPosition; 23 24void main() { 25 vec3 worldPos = aPosition + aInstanceOffset; 26 vPosition = (uModelView * vec4(worldPos, 1.0)).xyz; 27 vNormal = uNormalMatrix * aNormal; 28 vUv = aUv; 29 vColor = aInstanceColor; 30 gl_Position = uProjection * uModelView * vec4(worldPos, 1.0); 31}

Week 6: Optimization & Capstone Project (Days 26–30)

Minimizing Draw Calls

Each gl.drawArrays() or gl.drawElements() call incurs CPU-side overhead for WebGL state validation — a cost that is notably higher in WebGL than in native OpenGL due to the browser's security checks. Best practices include:

  • Batching: Combine objects sharing the same material into a single draw call
  • Instancing: Use drawArraysInstanced / drawElementsInstanced for repeated geometry
  • Ubershaders: Use a single large shader with #ifdef / uniform booleans to handle multiple material types, reducing program switches

State Management

A common performance pitfall is redundant state changes — re-binding the same buffer, re-setting the same depth test, or re-compiling the same shader every frame. Always cache the current GL state and only make changes when necessary:

1// BAD: Reseting state every draw call 2gl.bindBuffer(gl.ARRAY_BUFFER, 0); 3gl.useProgram(0); 4// ... re-bind everything 5 6// GOOD: Only change what differs between draw calls 7// Cache current state, transition minimally

As the Emscripten optimization guide emphasizes: "Avoid all types of renderer patterns which reset the GL to some specific ground state after each draw call. Instead, lazily change only the GL state that is needed when transitioning from one draw call to another."

Texture and Shader Best Practices

Per MDN's WebGL best practices:

  • Use mipmaps for any texture visible in 3D — only ~30% memory overhead, large performance gains
  • Prefer shader builtins (dot, mix, normalize) over custom implementations — hardware has specialized instructions
  • Do work in the vertex shader — fragment shaders run many more times; move computations that can be interpolated to vertex processing

Rendering at Lower Resolution

For mobile devices or heavy scenes, a powerful technique is rendering to a smaller back buffer and upscaling. Reduce canvas.width/height while keeping canvas.style.width/height at the display size. This trades quality for a significant performance gain with minimal code changes.

Footnotes

  1. Optimizing WebGL — Emscripten Documentation - Guidance on state caching, avoiding redundant GL calls, and minimizing WebGL security validation overhead. 2

  2. WebGL Best Practices — MDN - Browser vendor recommendations for mipmap usage, shader builtins, vertex shader work, and rendering resolution. 2

Relative Performance Impact of Common WebGL Optimizations

Estimated performance gains from applying key optimization techniques (higher = greater impact)

Common WebGL Questions & Troubleshooting

Use requestAnimationFrame — Never setTimeout

Always use requestAnimationFrame() for your render loop instead of setTimeout() or setInterval(). requestAnimationFrame synchronizes with the browser's repaint cycle, automatically pauses when the tab is backgrounded (saving battery), and provides a smooth, vsync-aligned frame rate. Using setTimeout fights the browser scheduler and causes janky, power-wasting rendering.

WebGL Security Validation Overhead

Unlike native OpenGL, WebGL has additional CPU-side validation to prevent security exploits (e.g., out-of-bounds texture reads, invalid framebuffer states). This means each GL function call in WebGL has higher overhead than its native counterpart. This is why minimizing draw calls, caching state, and batching operations is even more critical in WebGL than in desktop OpenGL. Profile your application with browser DevTools to identify whether your bottleneck is CPU-bound (too many GL calls) or GPU-bound (fragment/vertex processing).

Capstone Project: Build a Complete 3D Scene (Days 28–30)

By the final three days, you will combine every technique into a single, polished 3D WebGL application. The recommended project is an interactive 3D scene viewer featuring:

  1. Multiple geometric objects with instanced rendering
  2. Phong lighting with a point light the user can move
  3. Textured surfaces with diffuse + normal maps
  4. Real-time shadows via shadow mapping
  5. Post-processing via FBO-based bloom or tone mapping
  6. Mouse/keyboard orbit camera for scene navigation
  7. On-screen FPS counter and performance stats

Knowledge Check

Question 1 of 5
Q1Single choice

In the WebGL rendering pipeline, which stages are programmable (you write the code)?

Explore Related Topics

1

Learn Linux in 30 Days

A 30‑day curriculum guides beginners to practical Linux fluency by progressing from core navigation to system control and automation.

  • Master filesystem commands (pwd, ls, cd, cp, mv, rm) and hierarchical paths.
  • Understand and set permissions using chmod/chown, with numeric mode computed as mode digit=4(read)+2(write)+1(execute)\text{mode digit}=4(\text{read})+2(\text{write})+1(\text{execute}).
  • Monitor processes and resources with ps, top, df, du, and free.
  • Install, update, and remove software via APT (Debian/Ubuntu) or DNF (Fedora/Red Hat).
  • Build Bash scripts, chain tools with pipes, and schedule recurring jobs using cron.
2

Learn JavaScript in 30 Days

The course provides a 30‑day roadmap that guides beginners from core JavaScript syntax to building interactive, async web applications with vanilla JavaScript.

  • Daily coding sessions of 6060120120 minutes, followed by brief 10101515 minute reviews.
  • Structured timeline: Days 1‑5 syntax & data types, 6‑10 functions & structures, 11‑15 DOM & events, 16‑20 modern ES6+, 21‑25 async / fetch, 26‑30 projects.
  • Covers variables, control flow, functions, arrays/objects, DOM manipulation, modules, promises, and async/await.
  • Project sequence builds confidence: calculator → counter → to‑do list → API‑driven app → mini dashboard.
  • Key habits: use const by default, learn APIs by building, finish each topic with a working example.
3

Learn AI in 90 Days: A Complete Roadmap

Artificial Intelligence is no longer a niche specialty—it is the defining technology of the decade. From healthcare diagnostics to autonomous vehicles, from financial fraud detection to generative content creation, AI is reshaping every industry. For professionals and students alike, acquiring AI co