For over a decade, WebGL has powered nearly every interactive 3D experience in the browser. It is built on OpenGL ES 2.0 and 3.0 — hardware assumptions from 2011. Its architecture rests on a single-threaded execution model and a rigid global state machine: a design that traps modern, massively parallel GPUs inside a single-lane system.
That bottleneck has a name: CPU bubbling. A simple call like gl.getError() forces a synchronous round-trip between the JavaScript engine and the isolated GPU process. The CPU thread halts. The GPU sits idle, waiting for its next instruction. Multiply that stall across thousands of draw calls, and frame rates collapse.
As of early 2026, that era is ending. WebGPU now has baseline support across Chrome, Edge, Safari, and Firefox. It is not an incremental patch — it is a ground-up redesign, drawing directly from the modern low-level APIs: Vulkan, Metal, and Direct3D 12. It replaces synchronous, blocking calls with an asynchronous model built on promises. It exposes the GPU not just as a pixel-drawing tool, but as a general-purpose parallel processor. And it gives developers explicit control over memory and pipeline management — control WebGL never offered.
Three.js has already responded. Release r182 makes the WebGPURenderer the default rendering engine for the industry-standard 3D library. So the question everyone asks first: does this force a rewrite, or does it carry existing code forward?
Three.js is the bridge, not the rewrite
Writing raw WebGPU or WebGL code demands immense boilerplate: manual buffer allocations, explicit vertex layouts, compiled shader modules, manually orchestrated render passes. Three.js exists to eliminate that burden. It abstracts the hardware away entirely — you work with a hierarchical scene graph, Scene, Camera, Light, and Mesh — and the WebGPURenderer handles translation into optimized command buffers behind the scenes.
That convenience comes with one new rule. Unlike the WebGLRenderer, which acquires its drawing context synchronously and starts immediately, the WebGPURenderer must initialize asynchronously:
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();
Skip that await and the result is a race condition. It is a genuine shift in developer ergonomics, and it catches out anyone porting old code directly.
But asynchronous setup enables something more valuable: a graceful fallback architecture. During initialization, the renderer queries the browser for a WebGPU adapter. If support is missing, or the device fails initialization, it automatically drops back to a WebGL 2 backend — no second codebase required. You can also force this fallback manually with forceWebGL: true, which is useful for debugging and compatibility testing.
Abstraction only goes so far, though. Underneath that scene graph, real data — vertices, matrices, memory buffers — still has to move to the GPU, obeying rules far stricter than JavaScript ever demanded.
Under the hood: pipeline, memory, and matrices
Every 3D object on screen passes through the same six-stage pipeline. Vertex fetch pulls geometry from memory. Vertex processing transforms coordinates from local space into clip space. Primitive assembly groups vertices into triangles and discards anything outside the camera's view. Rasterization converts those triangles into fragments — potential pixels. Fragment processing computes each fragment's final color and lighting. Output merging tests depth and blends the result into the frame. WebGL and WebGPU both follow this sequence; only the implementation underneath changes.
Before any of that happens, geometry has to leave JavaScript's memory and land in the GPU's VRAM. In Three.js, BufferGeometry and BufferAttribute wrap TypedArrays — Float32Array, Uint16Array — to make that transfer possible. But GPU memory doesn't forgive sloppy layouts the way JavaScript does. WGSL enforces strict alignment rules, inherited from the std140 and std430 standards.
Here is where it gets counterintuitive. A vec3<f32> is three 32-bit floats — 12 bytes. Logically, that is all it should need. But WGSL forces every vec3 to align to a 16-byte boundary. Think of it like assigned parking spaces sized for a truck even when a compact car pulls in — four bytes sit empty every time. Stack that across a struct with a position and a color vec3, and 24 bytes of real data balloons to 32 bytes of allocated memory — waste that can hit 25% in large datasets. TSL now automates that padding calculation, so you rarely touch it by hand.
Once the data is safely on the GPU, three matrices take over: Model, View, and Projection. The Model matrix moves a vertex from its own local space into shared world space. The View matrix repositions the entire world so the camera sits at the origin, looking down the negative Z-axis. The Projection matrix then applies perspective, using field of view and aspect ratio to create the illusion of depth. In old WebGL workflows, you recalculated and uploaded these matrices manually before every draw call. The WebGPURenderer now tracks scene graph changes automatically, updating and uploading them without developer intervention.
That is the raw mechanics of getting an object on screen. But the logic that controls what happens inside that pipeline — colors, displacement, lighting — used to mean writing shader code by hand. That changes next.
TSL: rewriting how we write shaders
Historically, authoring custom shaders in Three.js meant writing raw GLSL as concatenated strings. No syntax highlighting, no type-checking, and debugging meant deciphering cryptic compiler errors dumped straight into the browser console. Injecting custom logic into standard materials required the onBeforeCompile hook — a fragile system relying on string-replacement heuristics that frequently broke across library updates.
TSL, the Three.js Shading Language, replaces that entirely. Instead of writing imperative strings, you instantiate JavaScript objects — Nodes — representing mathematical operations, control flow, or data inputs. Chained together, these nodes form an Abstract Syntax Tree: a structured blueprint the engine can read and optimize before it ever reaches the GPU. String-based classes like ShaderMaterial are being deprecated in favor of Node Materials — MeshStandardNodeMaterial, MeshPhysicalNodeMaterial — built for this system.
The difference shows clearly in code. A displacement effect once required raw GLSL:
float pulse = sin(time * 2.0) * 0.5 + 0.5;
vec3 displacedPos = position + normal * pulse;
In TSL, the same logic becomes a composable graph:
const pulse = time.mul(2.0).sin().mul(0.5).add(0.5);
return positionLocal.add(normalLocal.mul(pulse));
Using the Fn wrapper, you write If, Loop, and Select statements directly in JavaScript — logic that compiles into native GPU branching instructions, not CPU-side code.
The most important detail: TSL is backend-agnostic. The same AST transpiles automatically to WGSL when running on the WebGPURenderer, or to GLSL when the system falls back to WebGL 2. One codebase, two rendering backends, zero manual translation.
Shaders now write once and run anywhere. But writing correct code is only half the story — the other half is speed.
Speed: the render loop and the rise of compute
Every 3D application runs on a render loop — in Three.js, abstracted into renderer.setAnimationLoop(). Dozens to hundreds of times per second, this loop clears framebuffers, updates uniforms, recalculates matrix transforms, and issues draw calls. Its efficiency directly determines what the end user perceives as smooth motion.
Under WebGL, that loop had a ceiling. JavaScript's single-threaded nature meant CPU-bound work inside the loop — say, updating position matrices for 10,000 objects — delayed the generation of graphics commands, stalling the GPU and dragging frame rates down. WebGL's global state machine made it worse: changing a single material forced the CPU to rebind textures, update uniform locations, and verify framebuffer completeness, sequentially, every time.
WebGPU removes that ceiling by using stateless, immutable pipelines and pre-recorded command buffers. Shader modules, vertex layouts, and blend states bundle into a single immutable object, so the CPU spends far less time re-validating state on every frame. The measured results are substantial:
- Draw calls per second: roughly 10,000 → 85,000 — an 8.5x improvement.
- Dynamic object limit at 60 fps: about 15,000 → 200,000 — 13.3x.
- Memory bandwidth: roughly 40 GB/s → 180 GB/s — 4.5x.
That extra throughput unlocks the compute shader — decoupled entirely from the rasterization pipeline, turning the GPU into a general-purpose parallel processor. Threads execute in workgroups, typically sized 64, 128, or 256, each one identified by a unique global invocation ID. Because massively parallel execution offers no guarantee of thread completion order, reading and writing the same buffer at once causes data races. The fix is the ping-pong buffer: one buffer holds the current frame's positions and velocities, the compute shader calculates the next frame's physics, and writes the result into a second buffer. The two swap roles each frame. For accumulated results, atomic operations allow safe read-modify-write sequences across threads.
This architecture keeps data resident entirely in VRAM — no CPU round trip — enabling N-body gravitational simulations, black hole accretion disk rendering, and Boids-style flocking at scales the browser couldn't previously support.
Raw pipeline speed and parallel compute set the stage for what comes next: visual effects and entirely new rendering paradigms built on top of this foundation.
New visual frontiers: post-processing and Gaussian splatting
Post-processing takes the finished frame and layers global effects on top: bloom, depth of field, screen space ambient occlusion, chromatic aberration. In the WebGL era, Three.js handled this through the EffectComposer — a linear conveyor belt. The scene rendered to a framebuffer, and a sequence of ShaderPass objects processed the image one after another, each pass feeding the next. If an effect needed auxiliary data — depth values for SSAO, for instance — the EffectComposer often forced redundant geometry passes over the entire scene, multiplying draw calls and dragging down frame rates.
The WebGPURenderer replaces that conveyor belt with a RenderPipeline and PassNode system, built directly on TSL. Effects become composable nodes in a graph rather than a fixed sequence. WebGPU's native support for Multiple Render Targets makes this possible: a single scene pass can output albedo color, depth, and world-space normals simultaneously, in one geometry evaluation. Downstream nodes sample whichever channel they need without forcing the GPU to re-evaluate the scene — cutting draw calls in complex post-processing stacks roughly in half. One caveat remains: MSAA combined with custom PassNodes still requires specific configuration to work reliably across both backends.
This same computational leap enables an entirely different rendering paradigm: 3D Gaussian Splatting. Rather than triangles and UV-mapped textures, a scene is represented as millions of unstructured, anisotropic 3D Gaussians — each one an ellipsoid defined by a position and a covariance matrix, carrying Spherical Harmonics coefficients that let it shift color depending on viewing angle, mimicking specular highlights without ray tracing.
Because each splat is semi-transparent, correct blending demands strict back-to-front sorting, every single frame. On WebGL, that sort ran on the CPU via WebWorkers — a serious bottleneck. WebGPU moves it to the GPU itself, using compute-shader-based radix sorting and tile-based rasterization: the screen splits into tiles, and a compute shader assigns 64-bit depth-based sorting keys to determine which Gaussians overlap which tile.
Storage remains a challenge — millions of Spherical Harmonics sets demand gigabytes of memory. The SOG format compresses that data into 2D grid textures, cutting file size by up to 80%. Researchers are also exploring stochastic rasterization, which removes sorting entirely through dithering.
These paradigms don't exist in isolation — they're colliding with an even bigger force reshaping the field: AI-generated content.
What's next: AI content and a changing curriculum
Machine learning models can now generate viable 3D meshes, procedural textures, and complete Gaussian Splat scenes directly from text prompts or sparse 2D images. That shifts the entire content pipeline. Technical artists once spent weeks modeling topology and baking textures for optimized output. AI-generated assets skip that step — and arrive dense, topologically chaotic, and unoptimized. WebGPU's low CPU overhead and superior handling of massive buffer arrays make it the runtime capable of ingesting that output smoothly, without the manual cleanup WebGL would demand.
This shift extends to education. Teaching graphics once meant starting students in raw GLSL syntax and C-style memory pointers — a steep, front-loaded learning curve. Now curricula can start top-down: teaching TSL's visual, node-based logic first, letting students see the effect of sine waves and matrix transforms instantly, before ever touching WGSL transpilation, vec3 alignment rules, or compute-shader-driven ping-pong buffers.
That is the full arc: WebGL's decade-long ceiling, Three.js as the bridge, the pipeline and memory rules underneath, TSL's rewrite of shader authoring, the compute-shader-driven performance leap, and the new visual paradigms — Gaussian Splatting and AI-generated scenes — that leap makes possible. WebGPU turns the browser from a document viewer with graphics bolted on into a sandboxed platform that rivals native application performance.