Learn WebGL in 30 Days
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
-
WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs. ↩
-
WebGL 2.0 Achieves Pervasive Support — Khronos - All major browsers now ship WebGL 2.0 support. ↩
30-Day WebGL Learning Roadmap
Foundations
Days 1–5Set up the WebGL context, understand the graphics pipeline, draw your first triangle, and learn GLSL basics."
Transformations & 3D
Days 6–10Model/View/Projection matrices, 3D geometry, index buffers, and camera fundamentals."
Lighting & Shading
Days 11–15Phong lighting model, normals, multiple light types, and smooth Gouraud/Phong shading."
Textures & Materials
Days 16–20Texture mapping, mipmaps, UV coordinates, normal maps, and cubemap environment reflections."
Advanced Techniques
Days 21–25Framebuffers, post-processing, instancing, shadow mapping, and particle systems."
Optimization & Project
Days 26–30Draw 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):
| Stage | Type | Purpose |
|---|---|---|
| Vertex Shader | Programmable | Transform vertex positions, pass attributes |
| Primitive Assembly | Fixed | Assemble vertices into points/lines/triangles |
| Rasterization | Fixed | Generate fragments (potential pixels) |
| Fragment Shader | Programmable | Compute per-fragment color |
| Depth/Stencil Test | Fixed | Determine visibility of fragments |
| Blending | Fixed | Combine 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
-
WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs. ↩
-
WebGL Tutorial — MDN - Official MDN WebGL tutorial covering context setup, shaders, and drawing basics. ↩
Compiling and Linking a WebGL Shader Program
- 1Step 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). - 2Step 2
Create shader objects with
gl.createShader(), attach source withgl.shaderSource(), and compile withgl.compileShader(). Always checkgl.getShaderParameter(shader, gl.COMPILE_STATUS)for compilation errors. - 3Step 3
Create a program with
gl.createProgram(), attach both compiled shaders withgl.attachShader(), then link withgl.linkProgram(). Verify success withgl.getProgramParameter(program, gl.LINK_STATUS). - 4Step 4
Use
gl.getAttribLocation(program, name)andgl.getUniformLocation(program, name)to cache the indices/locations for later binding. This avoids costly look-ups during render loops. - 5Step 5
Call
gl.useProgram(program), bind your vertex buffers, set uniform values, and issue a draw call likegl.drawArrays(gl.TRIANGLES, 0, vertexCount)orgl.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 homogeneous matrix:
| Space | Purpose | Matrix |
|---|---|---|
| Local / Object | Original vertex positions | — |
| World | Position in the scene | Model Matrix |
| View / Camera | Relative to the camera | View Matrix |
| Clip | Normalized for clipping | Projection Matrix |
| NDC | Normalized Device Coordinates () | Perspective Division |
| Screen | Pixel coordinates | Viewport 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:
where is the aspect ratio, is the vertical field of view, and and 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:
| Component | Formula | Effect |
|---|---|---|
| Ambient | Constant base illumination | |
| Diffuse | Smooth shading based on light direction and surface normal | |
| Specular | Highlight/shininess based on reflection and view |
The exponent 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:
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
-
WebGL 2.0 Achieves Pervasive Support — Khronos - All major browsers now ship WebGL 2.0 support. ↩
WebGL Shader & Lighting Concepts
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 , 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:
| Filter | Behavior |
|---|---|
NEAREST | Pick closest texel — fast, pixelated |
LINEAR | Bilinear interpolation — smooth |
NEAREST_MIPMAP_NEAREST | Pick closest mipmap level, then nearest texel |
LINEAR_MIPMAP_LINEAR | Trilinear 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 and samples the cube map to produce realistic environment reflections or skybox backgrounds.
Footnotes
-
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:
- Create FBO and attach a color texture (and optionally a depth texture/renderbuffer)
- Bind the FBO with
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) - Render your scene — output goes to the attached textures
- Unbind (
gl.bindFramebuffer(gl.FRAMEBUFFER, null)) to return to the screen - Render a fullscreen quad, sampling the FBO's color texture to apply post-processing
Shadow Mapping
Shadow mapping is a two-pass technique:
- Depth pass: Render the scene from the light's perspective into a depth FBO
- 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
-
WebGL Academy — Interactive Tutorials - Progressive tutorials covering shadow mapping, deferred shading, and advanced techniques. ↩
-
WebGL 2.0 Arrives — Khronos Blog - WebGL 2.0 feature overview including transform feedback, instanced rendering, MRT, and UBOs. ↩
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/drawElementsInstancedfor 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
-
Optimizing WebGL — Emscripten Documentation - Guidance on state caching, avoiding redundant GL calls, and minimizing WebGL security validation overhead. ↩ ↩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:
- Multiple geometric objects with instanced rendering
- Phong lighting with a point light the user can move
- Textured surfaces with diffuse + normal maps
- Real-time shadows via shadow mapping
- Post-processing via FBO-based bloom or tone mapping
- Mouse/keyboard orbit camera for scene navigation
- On-screen FPS counter and performance stats
Knowledge Check
In the WebGL rendering pipeline, which stages are programmable (you write the code)?
Explore Related Topics
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 . - Monitor processes and resources with
ps,top,df,du, andfree. - 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.
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 ‑ minutes, followed by brief ‑ 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
constby default, learn APIs by building, finish each topic with a working example.
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