Three.js Game Development: Building 3D Games for the Web
In This Guide
- What Is Three.js
- Why Use Three.js for Games
- Core Concepts for Game Development
- Scenes, Cameras, and Renderers
- Game Loop Fundamentals
- 3D Models and Assets
- Physics and Collision Detection
- Player Controls and Input
- Multiplayer and Networking
- Shaders and Visual Effects
- Performance and Optimization
- The Three.js Ecosystem in 2026
What Is Three.js
Three.js is an open-source JavaScript library created by Ricardo Cabello (mrdoob) in 2010 that provides a high-level abstraction over WebGL, the browser's native API for GPU-accelerated 3D rendering. Rather than writing hundreds of lines of shader code and buffer management to draw a single triangle, Three.js lets you work with familiar concepts like scenes, cameras, lights, and meshes. As of 2026, the library is at version r184, and it has added production-ready support for the WebGPU rendering backend, which is now available across Chrome, Firefox, Edge, and Safari on both desktop and mobile.
The library is not a game engine in the traditional sense. It does not ship with a built-in physics system, entity-component architecture, or level editor. Instead, it provides the rendering layer, and developers compose game functionality from community libraries and their own code. This modular approach is both the greatest strength and the steepest learning curve of Three.js game development. You choose exactly the physics engine, networking layer, and input system your project needs, rather than inheriting a monolithic framework with features you may never use.
Three.js handles the translation between your scene graph and the GPU draw calls that produce each frame. It manages textures, materials, geometries, lights, shadows, post-processing effects, skeletal animations, and more. The renderer sorts objects by material and depth, batches draw calls where possible, and handles the state management that raw WebGL forces you to do manually. When using the WebGPU backend, Three.js also takes advantage of compute shaders and more efficient pipeline management that WebGPU provides.
Why Use Three.js for Games
Browser-based games have a distribution advantage that native games cannot match. A player clicks a link and is playing within seconds, no download, no install, no app store review. Three.js makes this possible for 3D games by running entirely in the browser with no plugins required. Every modern device with a web browser can run a Three.js game, from high-end desktops to budget smartphones.
The community around Three.js is one of the largest in web 3D development. The official examples repository contains over 300 working demos, and the Discourse forum has thousands of active developers sharing solutions. Libraries like drei (a collection of useful Three.js helpers for React Three Fiber), troika (text rendering), and lil-gui (debug interfaces) extend Three.js with battle-tested functionality. When you encounter a rendering challenge, someone in the Three.js community has almost certainly solved it before.
Three.js also integrates naturally with the broader JavaScript ecosystem. You can use it with React via React Three Fiber, with Svelte via Threlte, or with vanilla JavaScript and any bundler. State management, UI overlays, networking, and build tooling all come from the npm ecosystem you already know. This means your Three.js game can use the same authentication, database, and deployment infrastructure as any other web application.
From a business perspective, web games avoid the 30% revenue cut that Apple and Google charge on their app stores. Updates deploy instantly without waiting for review cycles. Analytics, A/B testing, and user acquisition all use standard web tools. For indie developers and small studios, these advantages can make the difference between a project that is financially viable and one that is not.
Core Concepts for Game Development
Building a game with Three.js means combining its rendering capabilities with several systems that the library does not provide out of the box. Understanding which pieces you need, and where they come from, is the first step toward a solid architecture.
The scene graph is Three.js's way of organizing everything visible in your game. Objects are arranged in a parent-child hierarchy, where transformations (position, rotation, scale) cascade from parent to child. A character model might be a parent object with weapon, armor, and particle effect children. Moving the character moves everything attached to it. This hierarchy is also how Three.js determines rendering order and applies culling optimizations.
The game loop is the heartbeat of any real-time game. In Three.js, you typically use renderer.setAnimationLoop(callback), which calls your function at the display's refresh rate (usually 60Hz). Inside this callback, you update game state (move entities, check collisions, process input), then call renderer.render(scene, camera) to draw the frame. Separating update logic from render logic is critical for consistent gameplay across different hardware.
Input handling in browser games relies on the DOM event system. Keyboard events, mouse movement, pointer lock, touch events, and the Gamepad API all feed into your input layer. Three.js provides helper classes like PointerLockControls and OrbitControls, but for production games you typically build a custom input manager that normalizes these sources into a unified interface.
Physics simulation is handled by external libraries. The most common choice in 2026 is Rapier, a Rust-based physics engine compiled to WebAssembly that offers excellent performance for rigid body dynamics, character controllers, and raycasting. Cannon-es remains a pure JavaScript alternative that is simpler to set up but slower for complex simulations. Ammo.js, a port of the Bullet physics engine, is still used in some projects but has fallen out of favor due to its large WASM bundle size and less ergonomic API.
Audio in Three.js games uses the Web Audio API, which Three.js wraps with its AudioListener and PositionalAudio classes. These provide 3D spatialized sound that adjusts volume, panning, and filtering based on the listener's position relative to the sound source. For music and ambient effects, non-positional audio sources work as simple playback channels.
Scenes, Cameras, and Renderers
Every Three.js application starts with three objects: a Scene, a Camera, and a Renderer. The Scene is a container that holds all the objects, lights, and effects in your game world. The Camera defines the viewpoint from which the scene is rendered. The Renderer takes the scene and camera and produces the pixels that appear on screen.
For most games, you will use a PerspectiveCamera with a field of view between 60 and 90 degrees. A narrower FOV creates a zoomed-in, cinematic feel, while a wider FOV gives better peripheral awareness at the cost of more distortion at the edges. First-person shooters typically use 75-90 degrees, while third-person games work well around 60-70 degrees. The camera's near and far clipping planes control the depth range that is rendered. Setting these tightly around your actual content improves depth buffer precision, which prevents the z-fighting artifacts that make surfaces flicker at a distance.
The WebGLRenderer has been the default for years, but Three.js r152 and later also support WebGPURenderer. In 2026, with WebGPU available on all major browsers, new projects should consider starting with the WebGPU backend. It offers better performance for scenes with many draw calls, compute shader support for GPU-side particle systems and terrain generation, and more predictable frame timing. The API is largely the same from the developer's perspective, as Three.js abstracts the backend differences behind its material and geometry systems.
Shadow mapping is one of the most expensive rendering features in any 3D application. Three.js supports several shadow map types, with PCFSoftShadowMap providing the best visual quality at moderate cost. For games, you often need to limit shadows to the player's immediate area using a directional light with a tightly configured shadow camera frustum. Cascaded shadow maps, while not built into Three.js, can be implemented with community libraries to provide high-quality shadows across large outdoor environments.
Game Loop Fundamentals
The game loop is where your game lives. Every frame, it reads input, updates the simulation, and renders the result. A well-structured loop separates these concerns so that game logic runs at a consistent rate regardless of rendering performance.
The simplest approach uses renderer.setAnimationLoop and tracks elapsed time with a THREE.Clock. The clock's getDelta() method returns the time in seconds since the last frame, and you multiply movement speeds and animation progress by this delta to ensure frame-rate independence. A character that moves at 5 units per second will cover that distance whether the game runs at 30fps or 144fps.
For physics-driven games, a fixed timestep is essential. Physics engines like Rapier expect to be stepped at a constant interval, typically 1/60th of a second. Your game loop accumulates elapsed time and steps the physics simulation in fixed increments, interpolating visual positions between physics steps for smooth rendering. This prevents the simulation instabilities that occur when physics updates at variable rates, such as objects tunneling through walls during frame rate drops.
State management in the game loop varies by project complexity. Simple games can use plain objects and arrays. Larger projects benefit from an entity-component system (ECS) where game objects are composed of data components (position, health, inventory) processed by systems (movement, combat, rendering). Libraries like bitECS provide performant ECS implementations for JavaScript that work naturally with Three.js.
3D Models and Assets
Three.js supports multiple 3D model formats, but glTF (GL Transmission Format) is the standard for web delivery. The format is maintained by the Khronos Group, supports meshes, materials, textures, skeletal animations, and morph targets, and is designed for efficient transmission and loading. Three.js includes GLTFLoader as its primary model loader, and the format has become so dominant that most 3D tools export to it natively.
The binary variant, GLB, packs everything into a single file, which reduces HTTP requests and simplifies deployment. For production, you should also use Draco compression (via DRACOLoader) to reduce geometry data size by 80-90%, and KTX2/Basis Universal compressed textures (via KTX2Loader) to reduce texture memory and download size while maintaining GPU-native decompression. A character model that would be 15MB uncompressed might come down to 2-3MB with these optimizations.
Animations in glTF use the skeletal animation system. Three.js provides AnimationMixer to play, blend, and crossfade animations. A game character might have idle, walk, run, jump, and attack animations that blend smoothly between each other based on game state. The mixer handles interpolation between keyframes and supports animation events for triggering sounds or effects at specific points in an animation.
Free and commercial game assets are available from sources like Kenney.nl (CC0 licensed), Sketchfab, Quaternius, and the Khronos glTF sample models repository. For prototyping, Three.js's built-in geometries (boxes, spheres, planes, cylinders) combined with colored materials work well before you invest in production art.
Physics and Collision Detection
Physics simulation adds believable movement, gravity, and interaction to game objects. Without a physics engine, you must manually code collision responses, which becomes unmanageable as scene complexity grows. A physics engine handles rigid body dynamics, collision detection, raycasting, and constraint solving in an optimized simulation loop.
Rapier has emerged as the preferred physics engine for Three.js games in 2026. Written in Rust and compiled to WebAssembly, it delivers performance comparable to native physics engines while running in the browser. Rapier supports rigid bodies (dynamic, kinematic, and static), a variety of collider shapes (boxes, spheres, capsules, convex hulls, triangle meshes, heightfields), joints, and character controllers. Its deterministic simulation mode is valuable for multiplayer games where physics must produce identical results across clients.
Integration between Rapier and Three.js involves maintaining two parallel representations of your game objects: a Three.js mesh for rendering and a Rapier rigid body for physics. Each frame, after stepping the physics simulation, you copy positions and rotations from Rapier bodies to Three.js meshes. This synchronization is straightforward but must be handled carefully to avoid visual jitter, especially when using interpolation between fixed physics steps.
Raycasting, the process of shooting an invisible ray into the scene and detecting what it hits, is fundamental to game interactions. Three.js has its own raycaster for mouse picking and line-of-sight checks against the visual scene graph. Rapier provides a separate raycaster that operates on the physics world, which is faster and more appropriate for gameplay queries like bullet trajectories, ground detection, and AI vision cones.
Player Controls and Input
Player controls determine how your game feels to play, and getting them right is one of the most important aspects of game development. Three.js provides several control schemes as starting points, but production games almost always customize or replace them.
PointerLockControls captures the mouse cursor and provides raw mouse movement deltas, which is the foundation for first-person camera controls. Combined with keyboard input for WASD movement and a physics character controller for collision response, this creates the standard FPS control scheme. The Pointer Lock API requires a user gesture (like a click) to activate, and the browser shows a prompt the first time, which your game's UI needs to account for.
Third-person cameras need to follow and orbit around the player character while avoiding clipping through walls and terrain. This involves raycasting from the target to the desired camera position and pulling the camera forward if an obstacle is hit. Camera smoothing, using interpolation rather than snapping to the target position, prevents motion sickness and makes movement feel polished.
Mobile browsers require touch controls, which typically means virtual joysticks rendered as HTML overlays or canvas elements. The Gamepad API provides support for physical controllers connected via Bluetooth or USB. A well-designed input system normalizes all these sources into abstract actions (move, jump, shoot) so that game logic does not need to know which physical device produced the input.
Multiplayer and Networking
Adding multiplayer to a Three.js game introduces networking challenges that are independent of the rendering library. The fundamental architecture choice is between authoritative servers, where the server runs the simulation and clients send inputs, and peer-to-peer, where clients communicate directly. Authoritative servers prevent cheating and provide consistent game state, while peer-to-peer reduces infrastructure costs for casual games.
WebSocket connections, often managed through Socket.io or the native WebSocket API, handle the communication between clients and server. The server typically runs a headless simulation (no rendering) using the same game logic as the client. Node.js is a natural choice for the server runtime since it shares the JavaScript language with the client, allowing code reuse for physics, entity management, and game rules.
Latency compensation techniques are essential for responsive multiplayer gameplay. Client-side prediction lets the local player move immediately without waiting for server confirmation, then reconciles with the server's authoritative state when it arrives. Entity interpolation smooths the movement of remote players by rendering them slightly in the past, using buffered position updates. These techniques are complex to implement correctly but are well-documented patterns used by multiplayer games of all scales.
Shaders and Visual Effects
Shaders are programs that run on the GPU and control how every pixel is drawn. Three.js provides a rich material system (MeshStandardMaterial, MeshPhysicalMaterial) that handles most rendering needs, but custom shaders unlock effects that standard materials cannot achieve. Water surfaces, energy shields, portals, dissolve effects, and stylized rendering all require custom shader code.
Three.js offers ShaderMaterial and RawShaderMaterial for writing custom GLSL shaders. ShaderMaterial includes Three.js's built-in uniforms and attributes (like lighting calculations and fog), while RawShaderMaterial gives you a blank slate. Uniforms pass data from JavaScript to the GPU each frame, allowing you to animate shader effects based on game time, player position, or any other dynamic value.
Post-processing effects apply screen-wide visual treatments after the scene is rendered. Three.js's EffectComposer chains together render passes like bloom (bright areas glow), depth of field (distance blur), SSAO (ambient shadows in crevices), and color grading. These effects significantly improve visual quality but have a performance cost, so you should profile and provide quality settings that let players on lower-end hardware disable expensive passes.
Performance and Optimization
Browser games must run well on a wider range of hardware than native games, because you cannot control what device your players use. Performance optimization in Three.js focuses on reducing draw calls, minimizing GPU memory usage, and avoiding JavaScript bottlenecks that cause frame drops.
Draw call reduction is the single most impactful optimization for most Three.js games. Each unique material-geometry combination generates a draw call, and the CPU cost of submitting draw calls is the primary bottleneck in WebGL. Instanced rendering (InstancedMesh) draws many copies of the same geometry with different transforms in a single draw call, which is ideal for trees, rocks, bullets, particles, and any repeated object. Merging static geometries with BufferGeometryUtils.mergeGeometries combines separate meshes into one, eliminating their individual draw calls entirely.
Texture management affects both download time and GPU memory. Use the smallest texture resolution that looks acceptable, prefer compressed formats (KTX2/Basis), and share textures between objects using texture atlases. Dispose of textures, geometries, and materials when they are no longer needed by calling their .dispose() methods, otherwise they remain in GPU memory indefinitely.
Level of Detail (LOD) reduces the polygon count of distant objects using Three.js's LOD class. A tree might use a 5000-polygon model up close, a 500-polygon model at medium distance, and a flat billboard at long range. Combined with frustum culling (which Three.js performs automatically) and occlusion culling, LOD keeps the rendered polygon count manageable in large open worlds.
The Three.js Ecosystem in 2026
The Three.js ecosystem has matured significantly. React Three Fiber (R3F) brings Three.js into the React component model, making it easier to build complex 3D applications with familiar patterns. Drei provides dozens of ready-made components for R3F, from cameras and controls to environment maps and text rendering. Threlte does the same for Svelte developers.
Development tooling has also improved. Leva and lil-gui provide real-time parameter tweaking during development. Three.js Editor is a browser-based scene editor for prototyping. The Three.js DevTools browser extension shows scene graphs, material properties, and rendering statistics in real time. For serious game projects, r3f-perf provides detailed performance monitoring specific to Three.js rendering.
The WebGPU transition is the biggest technical shift in Three.js's history. While WebGL remains fully supported, WebGPU offers compute shaders (for GPU-side particle systems, terrain generation, and spatial queries), better multi-threaded rendering, and reduced driver overhead. New Three.js projects in 2026 can target WebGPU as the primary backend with WebGL as a fallback for older browsers, getting the best of both worlds.