Article · · Drift Technologies

Six 3D engines measured in the browser

DriftEngine beside Three.js, Babylon.js, PlayCanvas, Godot and Bevy: five scenes timed on WebGPU and WebGL2, on a desktop GPU and a phone, with a picture of every scene, what each engine ships for a game, and where DriftEngine is behind.

We make DriftEngine. This is our comparison with Three.js, Babylon.js, PlayCanvas, Godot and Bevy. The results page has the method and tables; we welcome corrections from people who know the other engines better than we do.

Intel's Sponza at noon, drawn in a browser by DriftEngine on WebGPU

The opening picture comes from DriftEngine's own Sponza demo, separate from the benchmark. Intel Sponza (2022) is from the Intel Sample Library under Creative Commons Attribution; see the model credits.

DriftEngine is a 3D game engine written in TypeScript. It renders through WebGPU, falls back to WebGL2 when a browser has no usable WebGPU device, and packages the same game for the web, Linux, Windows, macOS and Android.

We built it for browser games, including games ported from elsewhere. Here we explain what it provides and measure it alongside the other engines. Every scene's timing table has pictures of what each engine drew beside it: a fast frame only counts if it shows the scene. We print the numbers that favour DriftEngine and those that favour the others.

What a game gets

Install the packages your game needs. @driftengine/core provides the fixed-step loop, both renderers, cameras, an input action map, the scene graph, geometry, text and physics. Separate packages add animation, audio, model loading, navigation meshes, terrain, lockstep and rollback netcode, Gaussian splats, a 2D layer for sprites and menus, and native packaging. We publish them on npm under Apache-2.0.

Core includes rigid bodies, joints, ray and shape queries, sensors, a character controller with air control and a jump buffer, a raycast vehicle with tyre curves, ragdolls and cloth. The matrix below records built-in rigid-body physics, character controllers, vehicles and ragdolls for both DriftEngine and Godot, and lists the dependencies the other engines use.

The renderer supports directional, point and spot lights through clustered shading, plus rectangular area lights with shadows that soften as the emitter grows. It provides image-based lighting, exposure, ambient occlusion, temporal antialiasing, bloom, depth of field, motion blur, tone mapping and colour grading. The sky follows a latitude and season. Fog, rain and water share wind with other systems. WebGPU also offers an optional GPU-driven pipeline with instance and cluster culling, and temporal reconstruction from a lower resolution.

Our baker reads glTF, OBJ, STL, USD, 3MF, FBX and Blender files and writes .drft containers. It quantises geometry, recovers repeated meshes as instances, keeps textures in GPU compression formats and divides scenes into regions that stream nearest first. The benchmark loads a container baked from Intel's Sponza glTF and renders its main pass through the loader's draw() method. That API call can submit several GPU draws; the scene also has a shadow pass.

The engine imposes no character type or level format. Its functions take positions, colours, sizes and time, leaving you to organise the game.

Built for an agent to write

We provide unattended project setup and checks that a coding agent can run after a change. npm create @driftengine@latest my-game creates a project without prompts. It includes an AGENTS.md with project instructions, the engine's skill in the locations agents look for it, and npm run check.

That command typechecks the project, runs its tests in Node, then opens and photographs the game on WebGPU and WebGL2 using the machine's GPU. It rejects console errors and warnings, a flat-colour canvas, or a different backend from the one requested. The image check can catch an empty frame even when compilation and unit tests pass.

The simulation is plain TypeScript, so game rules can be tested in Node without a GPU. An agent can check a jump's height or a score calculation before rendering anything. Every export carries its documentation; we also publish the manual as Markdown, an llms.txt index and an installable skill.

Orbfall is a small 3D arcade game an agent built from one prompt using this setup. Its page includes the original prompt and the agent's log of its checks.

Two backends, one picture

WebGPU and WebGL2 share one API. We try WebGPU first, draw a probe frame and read its pixels back before accepting the device. Checking for the API alone would miss a device that uses a software rasteriser or draws nothing. If we reject it, we fall back to WebGL2 and return a reason the game can display or log.

We write shaders once and generate the WebGPU versions from them; the WebGL2 path does not need a translator. In the still scenes here, instancing, lights and Sponza, DriftEngine's two backends draw nearly identical pictures, with nearly every pixel the same.

Determinism

The simulation advances in fixed steps of a sixtieth of a second, and rendering interpolates between them. Our seeded random generator keeps its sequence across releases; a new distribution gets a new function. Physics computes a world fingerprint at each step.

Replays and rollback also need reproducible arithmetic and game logic that uses recorded inputs instead of external time or unseeded randomness. With those conditions met, rollback can rewind, apply a late input and simulate forward to the same state. Tests can compare fingerprints from the same initial state and inputs to find the step where runs diverge. The matrix below describes the simulation support and integrations available in each engine.

The numbers

How they were taken

We wrote five scenes from each engine's documentation and examples:

  1. Cube: one lit, spinning cube, used to measure download size, time to first frame and memory.
  2. Separate objects: individually transformed cubes. Bevy batches these automatically, so an object count is not a draw-call count across engines.
  3. Instances: a cube repeated through each engine's instancing path.
  4. Lights: a floor and 64 pillars under coloured point lights.
  5. Sponza: Intel's 2022 base scene, with textures capped at 1,024 pixels and a sun casting shadows through the same size of shadow map in each engine.

The desktop has an AMD Radeon RX 9070 XT, with Chrome using Vulkan. Vsync and the frame-rate cap are off. Each canvas is 1280 by 720, with a pixel ratio of one and no multisampling. Fixed-count runs discard 120 warm-up frames, then record up to 600 frames within 15 seconds. The probe tracks GPU completion and allows 2 frames in flight. Each timing reading averages 4 frames; tables show the median and 95th percentile of those readings. Desktop fixed-count cells take the median of each metric from 3 interleaved runs. Desktop searches and all phone cells ran once.

For counts at sixty frames a second, we double the count while the 95th percentile is at or under 18 milliseconds, then narrow the interval to the greater of 5% or 16 objects. The search also checks for a flat image and GPU errors; it cannot verify that every object or light was drawn. The results page gives the full timing and search method.

All engines use pinned release builds: DriftEngine 4.13.0, Three.js r186, Babylon.js 9.30.0, PlayCanvas 2.23.2, Godot 4.7.2 and Bevy 0.20.0. Each uses its default output encoding. Godot exports only WebGL2; Bevy needs a build for each backend. For Sponza, Godot imports the model ahead of time and DriftEngine bakes it; the other engines load the glTF.

We exclude runs that use another backend or a software renderer, report GPU errors or produce a flat canvas. Repeating DriftEngine and Three.js desktop cube and separate-object measurements in two sessions gave a median difference of 2.0% and a largest difference of 4.9%. These repeats cover only those engines and desktop scenes.

Bold marks the best value and timings within 2.0% or counts within 5% of it. These bands group nearby results without establishing a tie. Download, first-frame and memory columns bold only the best value. If every value falls within a column's band, none is bold. A plus sign means the search reached its cap, leaving the maximum unknown. A figure an engine reached by shading fewer lights than it was given is never bold.

The pictures beneath the timing tables are separate desktop captures at the fixed counts being timed. They show neither the search ceilings nor the phone runs. The results page explains the lighting settings that changed between measurement and photography.

Download and first frame

Engine Backend Brotli gzip First frame Memory
DriftEngine WebGPU 544.5 KB 928.1 KB 456 ms 26.6 MB
DriftEngine WebGL2 304.1 KB 378.6 KB 148 ms 13.7 MB
Three.js WebGPU 241.7 KB 300.3 KB 156 ms 13.1 MB
Three.js WebGL2 163.6 KB 198.9 KB 81 ms 10.6 MB
Babylon.js WebGPU 317.5 KB 368.6 KB 258 ms 10.4 MB
Babylon.js WebGL2 318.7 KB 370.2 KB 237 ms 13.5 MB
PlayCanvas WebGPU 485.3 KB 624.8 KB 169 ms 20.5 MB
PlayCanvas WebGL2 485.3 KB 624.8 KB 149 ms 19.5 MB
Godot WebGL2 6.8 MB 9.8 MB 1430 ms 63.0 MB
Bevy WebGPU 4.3 MB 6.2 MB 813 ms 65.1 MB
Bevy WebGL2 4.3 MB 6.2 MB 736 ms 48.0 MB

The one-cube scene as each of the eleven builds drew it, each engine on its own clear colour

The cube measures the cost of a small application. Three.js on WebGL2 has the smallest Brotli download, 163.6 KB, and the earliest first frame, 81 ms. DriftEngine downloads 304.1 KB on WebGL2 and 544.5 KB on WebGPU. Its WebGPU first frame is 456 ms, the latest among the JavaScript builds. That includes our device probe, whose individual cost we did not measure. Godot and Bevy download WebAssembly engines and have larger page totals.

Many separate objects

Engine Backend 1,000: median 1,000: p95 10,000: median 10,000: p95 Most at 60 fps
DriftEngine WebGPU 2.67 ms 3.24 ms 22.39 ms 25.34 ms 7,168
DriftEngine WebGL2 2.67 ms 3.21 ms 21.68 ms 24.49 ms 7,424
Three.js WebGPU 4.71 ms 6.45 ms 55.29 ms 62.17 ms 2,688
Three.js WebGL2 1.47 ms 1.93 ms 12.51 ms 14.11 ms 9,472
Babylon.js WebGPU 7.67 ms 8.93 ms 87.99 ms 93.07 ms 1,792
Babylon.js WebGL2 4.10 ms 6.21 ms 61.86 ms 64.71 ms 2,688
PlayCanvas WebGPU 1.35 ms 1.77 ms 16.60 ms 21.43 ms 8,192
PlayCanvas WebGL2 1.48 ms 2.03 ms 18.37 ms 21.83 ms 7,424
Godot WebGL2 3.04 ms 3.46 ms 29.14 ms 31.48 ms 5,376
Bevy WebGPU 3.12 ms 4.02 ms 10.58 ms 11.75 ms 16,896
Bevy WebGL2 2.41 ms 3.29 ms 10.77 ms 12.04 ms 15,360

Ten thousand separate cubes in a grid, as each of the eleven builds drew them

This scene measures updating and rendering ten thousand separate objects. Bevy's WebGPU build has the lowest median, 10.6 ms, and holds 16,896 objects at sixty frames a second. Its automatic batching matters when interpreting these counts.

DriftEngine is third on WebGPU, behind Bevy and PlayCanvas, at 22.4 ms. Three.js takes 55.3 ms and Babylon.js 88.0 ms. On WebGL2, Three.js takes 12.5 ms, PlayCanvas 18.4 ms and DriftEngine 21.7 ms.

One object, many times

Engine Backend 100,000: median 100,000: p95 1,000,000: median 1,000,000: p95 Most at 60 fps
DriftEngine WebGPU 0.45 ms 0.55 ms 1.08 ms 1.64 ms 4,194,304+
DriftEngine WebGL2 0.47 ms 0.65 ms 3.15 ms 4.04 ms 3,801,088
Three.js WebGPU 0.37 ms 0.55 ms 1.49 ms 2.20 ms 4,063,232
Three.js WebGL2 0.33 ms 0.56 ms 1.12 ms 1.43 ms 4,194,304+
Babylon.js WebGPU 0.31 ms 0.46 ms 1.13 ms 1.70 ms 4,194,304+
Babylon.js WebGL2 0.30 ms 0.47 ms 1.14 ms 1.40 ms 4,194,304+
PlayCanvas WebGPU 0.37 ms 0.59 ms 1.13 ms 1.71 ms 4,194,304+
PlayCanvas WebGL2 0.33 ms 0.53 ms 1.32 ms 2.02 ms 4,194,304+
Godot WebGL2 0.51 ms 0.65 ms 2.05 ms 3.03 ms 4,194,304+
Bevy WebGPU 24.12 ms 25.65 ms 211.10 ms 218.15 ms 71,680
Bevy WebGL2 94.71 ms 97.27 ms 895.04 ms 908.40 ms 15,872

A million instanced cubes in a grid, as each of the eleven builds drew them

Instancing lets a game repeat a mesh efficiently, as in a forest or crowd. At a million copies, DriftEngine on WebGPU has the lowest median, 1.1 ms. Babylon.js on WebGL2 has the lowest 95th percentile, 1.40 ms, against DriftEngine's WebGPU 1.64 ms. Our WebGL2 median, 3.1 ms, is the highest of the JavaScript builds.

Several builds reach the search cap. Three.js on WebGPU accepts 4,063,232 copies. Bevy creates an entity for each cube and uses automatic instancing, so its figures include entity processing as well as rendering. We did not measure those costs separately.

Many lights

DriftEngine's desktop lights figures include point-light shadows, which none of the other engines draws here. We turned them off on the phone.

Engine Backend 64 lights 256 lights Most lights at 60 fps Limits
DriftEngine WebGPU 0.48 ms 0.70 ms 4,096+ shades the nearest 320 of 4096 lights
DriftEngine WebGL2 0.57 ms 0.92 ms 4,096+ shades the nearest 320 of 4096 lights
Three.js WebGPU 0.51 ms 0.57 ms 4,096+
Three.js WebGL2 0.45 ms 1.21 ms 992 forward: every light is evaluated for every fragment
Babylon.js WebGPU 0.72 ms 0.78 ms 4,096+
Babylon.js WebGL2 0.59 ms 0.64 ms 4,096+
PlayCanvas WebGPU 0.52 ms 0.77 ms 4,096+
PlayCanvas WebGL2 0.48 ms 0.72 ms 4,096+
Godot WebGL2 0.62 ms 0.69 ms 4,096+ Compatibility renderer: at most 32 lights per object
Bevy WebGPU 2.52 ms 2.46 ms 4,096+
Bevy WebGL2 2.01 ms 2.39 ms 4,096+ WebGL2: at most 256 clusterable lights

The floor and pillars under 256 coloured point lights, as each of the eleven builds drew them: Godot lights only the middle of its floor, Babylon.js's floor runs to white, and Bevy on WebGL2 leaves the far end dark

At 256 requested lights, Three.js on WebGPU has the lowest median, 0.57 ms. DriftEngine takes 0.70 ms on WebGPU and 0.92 ms on WebGL2. Three.js on WebGL2 has the highest median among the JavaScript builds and accepts 992 lights in the desktop search.

Read the limits column alongside the counts. DriftEngine, Godot and Bevy on WebGL2 limit the lights they shade at once, so reaching the search cap does not mean shading every requested light. The pictures show how those choices affect the floor; we discuss them below.

Sponza

Engine Backend Download Load to first frame Frame
DriftEngine (baked) WebGPU 44.2 MB 2445 ms 1.09 ms
DriftEngine (baked) WebGL2 44.0 MB 1974 ms 1.25 ms
Three.js WebGPU 73.7 MB 1778 ms 2.71 ms
Three.js WebGL2 73.6 MB 1638 ms 1.85 ms
Babylon.js WebGPU 74.0 MB 2108 ms 4.27 ms
Babylon.js WebGL2 74.0 MB 2722 ms 2.47 ms
PlayCanvas WebGPU 73.9 MB 1753 ms 1.00 ms
PlayCanvas WebGL2 73.9 MB 1678 ms 1.44 ms
Godot (imported) WebGL2 230.1 MB 6386 ms 2.71 ms
Bevy WebGPU 77.8 MB 5680 ms 4.17 ms
Bevy WebGL2 77.7 MB 5515 ms 9.45 ms

Intel's Sponza as each of the eleven builds drew it, with the sun and sky the benchmark set for that engine

We chose each engine's sun and sky settings separately for the benchmark. The pictures show those settings and do not rank rendering quality.

Sponza brings a more varied workload: detailed geometry, many materials, alpha-tested foliage, blended decals and sun shadows. DriftEngine's median is 1.1 ms on WebGPU, behind PlayCanvas at 1.0 ms, and the lowest on WebGL2 at 1.2 ms. DriftEngine also has the smallest total page download: 44.2 MB on WebGPU and 44.0 MB on WebGL2. Three.js on WebGPU downloads 73.7 MB. These totals include engine and page resources as well as the model. DriftEngine reaches its first frame later than Three.js and PlayCanvas on both backends.

On a phone

Engine Backend Most draws at 60 fps Most instances at 60 fps Most lights at 60 fps Sponza frame Sponza first frame
DriftEngine WebGPU 1,728 63,488 160 37.5 ms 6386 ms
DriftEngine WebGL2 2,688 262,144 192 33.3 ms 3001 ms
Three.js WebGPU 1,024 688,128 4,096+ 16.7 ms 3226 ms
Three.js WebGL2 8,704 753,664 48 16.7 ms 2561 ms
Babylon.js WebGPU 1,280 753,664 1,792 16.7 ms 3309 ms
Babylon.js WebGL2 4,096 786,432 4,096+ 16.7 ms 3326 ms
PlayCanvas WebGPU 7,168 540,672 4,096+ 16.7 ms 3034 ms
PlayCanvas WebGL2 7,936 819,200 4,096+ 16.7 ms 2933 ms
Godot WebGL2 3,712 360,448 4,096+ 16.7 ms 38643 ms
Bevy WebGPU 12,288 53,248 624 25.0 ms 6850 ms
Bevy WebGL2 12,288 13,824 4,096+ 20.8 ms 7659 ms

We ran the same pages on a Samsung Galaxy S23 Ultra with an Adreno 740, using Chrome on Android over USB. The canvas remains 1280 by 720 at a pixel ratio of one. Vsync stays on, so the cube mostly measures the display's refresh. The useful comparisons are the counts held at sixty frames a second and the heavier Sponza scene. Phone temperature can move the ceilings; we measured the lights column in one sitting.

DriftEngine has the highest Sponza median on each backend: 37.5 ms on WebGPU and 33.3 ms on WebGL2. Three.js, Babylon.js, PlayCanvas and Godot keep up with the display. DriftEngine's WebGPU instancing holds 63,488 copies at sixty frames a second, against 688,128 for Three.js. Our desktop lead at a million copies does not carry over to this phone's search.

DriftEngine holds 160 lights on WebGPU and 192 on WebGL2. Most engines reach the search cap; Godot and Bevy's WebGL2 light limits still apply. Bevy on WebGPU holds the most separate objects, 12,288. DriftEngine's 1,728 exceeds Three.js and Babylon.js on that backend, but trails PlayCanvas's 7,168.

Godot takes 38643 ms to reach Sponza's first frame. Its export packs the scene into one download; the total page transfer is 230.1 MB.

What the pictures show

Godot's Compatibility renderer shades at most 32 lights per object. Beyond that count, its floor is lit only around the middle. Bevy's WebGL2 build leaves the far end of the floor dark at 256 lights. Babylon.js's standard material fades each light linearly to its range; with this many overlapping lights, the floor runs to white. That is a difference in appearance, with the floor still present.

Three.js's WebGL2 renderer drew a blank frame at 1,024 lights on the desktop and at 64 on the phone. Its WebGPU renderer lost the device at the step after 4,063,232 copies on the desktop.

Every DriftEngine picture shows the whole scene at the photographed count, on both backends. No DriftEngine step on either device drew a blank frame or lost its device. Babylon.js, PlayCanvas, Godot and Bevy had none either; Three.js was the only engine with either outcome in these runs.

A fast frame is only worth having if it shows the scene. Our backend rules aim to preserve that: anything added to one backend must reach the other, and a capability the device cannot provide must be refused with a clear message when the renderer starts. We photograph rendering changes on both backends before shipping them.

Where DriftEngine loses

For a minimal scene on WebGPU, DriftEngine downloads more and reaches its first frame later than Three.js and PlayCanvas. On WebGL2 its download is larger than Three.js's and smaller than PlayCanvas's. It also trails several JavaScript builds when drawing separate objects or a million instances on WebGL2, and takes longer than Three.js and PlayCanvas to show Sponza's first frame.

Our strongest desktop results are on WebGPU: the lowest median at a million instances, and second at 256 lights and in Sponza. Those rankings depend on the count; they do not apply across every row of the tables.

The phone leaves us more work. DriftEngine has the highest Sponza median, and its WebGPU instance count and light counts on both backends trail several other engines. These are priorities for our phone work.

What each engine ships

The feature matrix shows what each engine provides and what you would need to add. The table puts every engine in one of four states for each capability: built in, an official add-on from the engine's own organisation, a community plugin from someone else, or none found, where we found neither and the game would build it. An official add-on is not an absence. Every state links to the page that shows it, and the results page has the note on each cell.

Capability DriftEngine Three.js Babylon.js PlayCanvas Godot Bevy
Rigid-body physics Built in Official add-on Official add-on Official add-on Built in Community plugin
Character controller Built in Community plugin Official add-on Official add-on Built in Community plugin
Vehicle physics Built in Community plugin None found Official add-on Built in Community plugin
Ragdolls Built in None found Official add-on None found Built in None found
Deterministic simulation and replays Built in None found None found None found None found Community plugin
Rollback netcode Official add-on None found None found None found Community plugin Community plugin
WebGPU with an automatic WebGL2 fallback Built in Built in Built in Built in None found None found
Native desktop builds Official add-on Community plugin Official add-on Community plugin Built in Built in
Android builds Official add-on Community plugin Official add-on Community plugin Built in Built in
Navigation meshes Official add-on Community plugin Built in Community plugin Built in Community plugin
Terrain Official add-on Community plugin Built in None found Community plugin Community plugin
Spatial audio Official add-on Built in Built in Built in Built in Built in
Gaussian splats Official add-on Official add-on Built in Built in None found Community plugin
A scripting language of its own Official add-on None found Built in None found Built in Community plugin
Coding-agent tooling Official add-on Official add-on Official add-on Official add-on Community plugin Community plugin
Visual editor Official add-on Official add-on Official add-on Official add-on Built in Community plugin

DriftEngine has gaps of its own in that table, and the largest is the editor.

And Unreal

Unreal Engine has no official browser target, so it is outside these measurements. We have not compared native rendering performance. The practical differences for a browser project are:

DriftEngine Unreal Engine
Runs in a browser Yes, it is the first target Not since version 4.24, when HTML5 moved out of the engine to a community-supported extension (Epic's forum)
Native apps Linux, Windows, macOS and Android, from the same project Desktop, console and mobile platforms
Language TypeScript, and DriftScript for scripts C++ and Blueprints (Epic)
Terms Apache-2.0 A five per cent royalty on a product's revenue past its first US$1 million, three and a half per cent for qualifying Launch Everywhere releases, with the exclusions the EULA lists. Some other uses need paid seats. See the EULA, section 3 and the Royalty Addendum.
Editor Early: a scene tree, an inspector and play-in-editor An established visual editor with level design, animation and profiling tools

Our editor is still a substantial gap. It provides a scene tree, an inspector and play-in-editor; a full visual editor is our next major step. Teams that rely on an editor for daily development will find much more in Unreal. For teams targeting browsers with TypeScript, we offer a workflow built around code, with agent instructions and automated browser checks.

Porting a game to the browser

An existing game's language, assets and target platforms determine how much of it can carry over.

What language is the game in? Godot and Bevy export supported projects to the browser through WebAssembly. Both have web restrictions, including Godot 4's lack of C# web export. DriftEngine uses TypeScript, so game code written in another language needs rewriting. You can carry over the design and data, then test the port's rules in Node against results from the original. An existing fixed-step simulation fits our loop naturally.

Where are its assets? Our baker reads glTF, FBX, OBJ, STL, USD, 3MF and Blender files and converts them into streaming containers. Textures retain GPU compression; when a phone does not support the desktop format, a worker re-encodes them.

Must it still run outside a browser? Our packager builds desktop and Android apps from the same project. The native host runs the game on Dawn and SDL without a browser. Browser and native pictures match in measurements made in the engine's own repository.

For ports from another JavaScript engine, our guides map the concepts: from Three.js, from Babylon.js and from PlayCanvas.

Start here

npm create @driftengine@latest my-game
cd my-game
npm install
npm run dev

Add -- --template first-game after the project's name to start from a complete 3D game with physics, a character, shadows and sound rather than a lit cube. To give an existing project's agent the engine's skill, run npx skills add drftrun/driftengine in it.

Unreal and Unreal Engine are trademarks or registered trademarks of Epic Games, Inc. Three.js, Babylon.js, PlayCanvas, Godot and Bevy are the names of their projects, used only to say which engine each figure belongs to. DriftEngine and Drift Technologies are not affiliated with, endorsed by or partnered with any of them.