Three.js vs Raw WebGL: Do You Need the Library?

Three.js is a library built on top of WebGL (and now WebGPU), not a competing alternative to it. WebGL is the low-level browser graphics API, and Three.js wraps it in a scene graph, camera/light/material abstractions and asset loaders so you write far less boilerplate. Use Three.js for nearly everything, and drop to raw WebGL only for narrow cases where a library's abstractions cost more than they save.

Last updated . Verified against three.js WebGL API.

Read the FAQJump to code

The actual relationship: a library, not a competitor

This comparison is a category error in the strictest sense. Three.js uses WebGL internally (via WebGLRenderer, or WebGPU via WebGPURenderer) to actually draw pixels. There's no scenario where you pick "WebGL" instead of Three.js in the way you'd pick Babylon.js instead of Three.js; see the Three.js vs Babylon.js comparison for that kind of choice. The real question this comparison is usually getting at is: should I use Three.js's abstractions, or write against the WebGL API directly myself?

What Three.js actually saves you

Raw WebGL requires manually managing shader compilation, buffer uploads, uniform locations, render state, and matrix math for every object: dozens of lines of boilerplate before a single triangle appears on screen. Three.js provides:

  • A scene graph: parent/child transforms, so moving a group moves everything in it, computed for you.
  • Materials and geometries as reusable, composable objects instead of manual shader/buffer management.
  • Loaders for glTF, textures, and other formats, with no hand-written parsing.
  • Lighting and shadow systems that would otherwise be substantial shader code to implement correctly.
  • A large ecosystem of controls, post-processing and helpers built on top.

When raw WebGL still makes sense

Narrow, specific cases: a tiny, bundle-size-critical widget (a small inline visualization where even Three.js's footprint matters), a rendering technique that fights Three.js's assumptions (highly specialized GPU pipelines outside what the scene-graph model expects), or learning purposes. Understanding raw WebGL genuinely deepens understanding of what Three.js is doing underneath, which is valuable even if you'll use Three.js for real projects afterward.

For the overwhelming majority of real projects, the boilerplate raw WebGL requires isn't buying you anything Three.js doesn't already provide well.

Which should you use?

Use Three.js. It's not a tradeoff against WebGL in any meaningful sense: Three.js runs on WebGL and saves you from writing the same low-level boilerplate on every project. Reach for raw WebGL only in the narrow cases above, and even then, expect to write substantially more code for the same result.

Weighing this for a real project? Talk to a Three.js developer.

Tools and libraries

Code

Raw WebGL, just compiling one shader (partial)
1// This is only the shader compilation step. Buffers, uniforms, matrix
2// math and the render loop are all still to come.
3const gl = canvas.getContext('webgl2')
4
5function compileShader(type, source) {
6 const shader = gl.createShader(type)
7 gl.shaderSource(shader, source)
8 gl.compileShader(shader)
9 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
10 throw new Error(gl.getShaderInfoLog(shader))
11 }
12 return shader
13}
14
15const vertexShader = compileShader(gl.VERTEX_SHADER, vertexSource)
16const fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSource)
17const program = gl.createProgram()
18gl.attachShader(program, vertexShader)
19gl.attachShader(program, fragmentShader)
20gl.linkProgram(program)
21// ...still need: buffers, attribute/uniform locations, matrices, the draw call
The same result in Three.js (r181)
1import * as THREE from 'three'
2
3const scene = new THREE.Scene()
4const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100)
5const renderer = new THREE.WebGLRenderer()
6renderer.setSize(innerWidth, innerHeight)
7document.body.appendChild(renderer.domElement)
8
9scene.add(new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshStandardMaterial({ color: 'orange' })))
10scene.add(new THREE.DirectionalLight())
11renderer.render(scene, camera)
12// Shader compilation, buffers, uniforms and matrices are all handled internally.

Learn this properly

Learn Practical TSL

Your First Node Material

Start building with Three.js's abstractions directly.

Start the lesson (6 minutes)

Frequently asked questions

Is Three.js built on WebGL?

Yes. Three.js's WebGLRenderer uses the browser's WebGL API internally to draw. Three.js is a library on top of WebGL (and, via WebGPURenderer, WebGPU too), not a separate or competing technology.

Is it worth learning raw WebGL before Three.js?

Not required. Most developers learn Three.js directly and pick up WebGL concepts as needed. Learning raw WebGL can deepen understanding of what's happening underneath, but it's not a prerequisite to being productive with Three.js.

When should I use raw WebGL instead of Three.js?

Rarely: a bundle-size-critical tiny widget, a highly specialized rendering technique that doesn't fit Three.js's scene-graph model, or explicitly for learning purposes. For the large majority of real projects, Three.js's abstractions save far more than they cost.

Keep reading