Introduction
I drew the LOD2 buildings of PLATEAU, Japan's open 3D city model, for Chiyoda ward in central Tokyo with five web 3D map engines under the same conditions, and measured them on three machines of mine: a MacBook Pro, an M1 Mac mini and an iPhone 16 Pro. I learned three things.
- For the first display, the leaders tie on every machine, and on the Macs even the slowest engine is only about 0.5 s behind. Almost all of the time is spent downloading tiles. On the iPhone, the engines that deliberately hold back fall behind.
- While the camera moves, every engine sticks to 60 fps, except MapLibre + deck.gl, which does not reach 60 fps on the M1 or the iPhone and needs 1 to 3 s to finish drawing after the camera stops.
- The same 60 fps hides different work. Cesium does not load while moving, Navara coarsens the picture on phones, and ortho-earth and MapLibre draw while loading. How each engine cuts corners reveals its design.
I should say up front that I am the developer of ortho-earth. To avoid favouring my own engine, I also include reference runs that align each engine's default behaviour, and I describe the strengths and weaknesses of the other engines exactly as the numbers came out. A developer's point of view may still have crept in. The code and raw data are public, so please check them yourself and judge fairly.
The five engines
All five are open-source map engines that run in the browser, but they come from different places and are good at different things.
MapLibre GL JS: one of the most widely used libraries for 2D web maps. When Mapbox GL JS stopped being open source in 2020, volunteers forked the last open version and carried it on. Its job is drawing vector tiles with WebGL, and it cannot read 3D Tiles, so here the buildings are drawn by the Tile3DLayer of the visualisation library deck.gl (born at Uber, now part of the OpenJS Foundation), overlaid on the map. Licence: BSD-3-Clause (deck.gl is MIT).
CesiumJS: the standard 3D globe engine. The 3D Tiles format itself came from Cesium and became an OGC standard. It has the longest history of reading 3D Tiles, so it serves as the yardstick here. Licence: Apache-2.0.
TerriaJS: built by Australia's national science agency CSIRO (Data61) for the national map site NationalMap. It uses CesiumJS for 3D drawing and adds the parts of a map application on top: a data catalogue, legends and sharing. I loaded PLATEAU into its sample application, TerriaMap. The first version of PLATEAU VIEW was also built with TerriaJS. Licence: Apache-2.0.
Navara: a new 3D map engine published under the MapLibre organisation on GitHub (version 0.2.1 here). Decoding and geometry building happen in a Rust (WebAssembly) layer, and drawing is left to Three.js. It offers a graded choice, from the ease of writing only a configuration down to the depth of writing shaders. Licence: MIT or Apache-2.0.
ortho-earth: the engine I build. It uses ordinary map tiles but draws the Earth on screen as a sphere. It draws with WebGPU and switches to WebGL2 where WebGPU is not available. Drawing happens inside a worker, which leaves the main thread free. Its API follows the shape of MapLibre GL JS. Licence: GPL-3.0-or-later.
How I compared them
Every engine loaded the same tileset.json from the same origin and drew only the buildings on a plain background.
| Engine | Version | How it reads 3D Tiles |
|---|---|---|
| MapLibre GL JS | 6.15.0 + deck.gl 9.4.0 | deck.gl Tile3DLayer overlaid (MapLibre alone cannot read 3D Tiles) |
| CesiumJS | 1.146.0 | Cesium3DTileset |
| TerriaJS | TerriaMap, terriajs 8.13.0 (about Cesium 1.142) | a 3d-tiles item in the init file |
| Navara | @navaramap/three 0.2.1 | 3D Tiles source |
| ortho-earth | @ortho-earth/globe 2.0.2 | map.add3DTiles |
- Data: PLATEAU Chiyoda ward, buildings LOD2 (untextured), 3D Tiles.
- Scenarios: first display (1,500 m above Tokyo Station, looking down at 60°), orbit (one lap of a 1 km radius at 500 m altitude in 30 s), zoom (from 10 km to 300 m altitude in 20 s). Vertical field of view fixed at 45°.
- Metrics: time to first display (when the tiles for the initial view have finished loading), fps and 95th-percentile frame time, time from stopping the camera to finishing drawing, memory, and the amount of tiles fetched.
- Runs: median of 5 runs each. Cache disabled, browser restarted for every scenario. Each run's screen was captured, and runs with no buildings drawn were discarded.
| Machine | Spec | Browser, display, network |
|---|---|---|
| MacBook Pro | M5 Pro, 64 GB | Chrome, external 4K 60 Hz, Wi-Fi |
| Mac mini | M1, 16 GB | Chrome, 60 Hz display, Wi-Fi |
| iPhone 16 Pro | iOS 18.7 | Safari 26.6, Wi-Fi (the Mac mini served the pages and relayed the tiles) |
To keep differences in features out of the comparison, I aligned the following:
- Drawing resolution 1x (CSS pixels) for every engine. The iPhone screen is 3x, but all engines drew with 1/9 of the pixels.
- Level-of-detail threshold (screen space error) 16 for every engine.
- No MSAA, shadows, terrain, atmosphere or base map. I turned off the default 4x MSAA of Cesium and Terria, and Terria's default 30 fps cap and shadows.
- ortho-earth's dynamic resolution (down to 0.55x under load) was fixed.
- A single sunlight for every engine; Navara was given a noon sun as well.
In this comparison every engine reads PLATEAU's 3D Tiles as they are, and ortho-earth was measured through this general path too. ortho-earth's map of Japan has a dedicated path that converts PLATEAU buildings in advance to a lighter format, serves them, and keeps them on the device for reuse, which displays faster than the numbers here (that path is not part of this measurement).
Result 1: first display
First display took 4.7 to 8.2 s. On the Macs, the spread within one machine was about 0.5 s. On the iPhone, Navara and ortho-earth fall behind.
Time to first display in seconds, median of 5 runs, vsync on, every engine at its defaults
(My measurements, 19–20 October 2026. MacBook from the re-run on 20 October, Mac mini at 60 Hz, iPhone excluding runs disturbed by sleep. The 5 runs of one engine spread by ±0.3 to 0.5 s.)
Most of the first display is the time to download the roughly 70 to 75 MB (about 110 to 120 tiles) needed for the initial view. The time to fetch a single tile hardly differs between engines; the differences come from the order of loading and how many requests are in flight at once. The top three are within about 0.1 s, less than the spread of five runs of one engine, so read them as a tie, not a ranking.
On the iPhone, two engines deliberately hold back. ortho-earth treats every touch device as low-memory and cuts concurrent tile requests from 6 to 3. Navara makes its level of detail 8 times coarser up close, but that does not affect this initial view (more in "The same 60 fps, different work").
Result 2: while moving
Apart from MapLibre + deck.gl, the four engines held 60 fps in orbit and zoom on all three machines, with a 95th-percentile frame time of 18 to 21 ms. Finishing the drawing after the camera stopped took under 15 ms.
Orbit (500 m altitude, 1 km radius): fps / p95 / time to finish after stopping
| Engine | MacBook Pro | Mac mini | iPhone 16 Pro |
|---|---|---|---|
| ortho-earth | 60 / 18.7 ms / 4 ms | 60 / 19.0 ms / 4 ms | 60 / 20 ms / 6 ms |
| Cesium | 60 / 18.5 ms / 14 ms | 60 / 18.4 ms / 8 ms | 60 / 19 ms / 2 ms |
| Navara | 60 / 18.3 ms / 14 ms | 60 / 18.6 ms / 6 ms | 59 / 19 ms / 10 ms |
| TerriaJS | 60 / 18.3 ms / 12 ms | 59 / 18.4 ms / 14 ms | 60 / 19 ms / 7 ms |
| MapLibre + deck.gl | 50 / 15.6 ms / 2.9 s | 40 / 26.8 ms / 0.8 s | 38 / 28 ms / 3.3 s |
MapLibre + deck.gl does not reach 60 fps in orbit even on the fastest MacBook, and keeps adding buildings for 1 to 3 s after the camera stops. Because deck.gl draws on a separate canvas over MapLibre, its displayed fps comes out at about twice the real rate; the table shows how often the screen actually updated (main-thread fps).
Zoom (10 km down to 300 m) has the same shape: 60 fps except MapLibre at 47 to 56 fps. But what "holding 60 fps" means differs between engines, which a later section covers.
Result 3: memory
ortho-earth's JS heap is an order of magnitude smaller at 15 MB; MapLibre + deck.gl is the largest at about 0.9 GB. Even on the 16 GB Mac mini, swap did not grow for any engine.
| Engine | JS heap | Renderer process | GPU process |
|---|---|---|---|
| ortho-earth | 15 MB | 1.4 GB | 0.20 GB |
| Cesium | 0.18–0.19 GB | 1.4 GB | 0.37–0.38 GB |
| Navara | 0.15–0.16 GB | 1.8–1.9 GB | 0.40–0.42 GB |
| TerriaJS | 0.24–0.32 GB | 1.5–1.6 GB | 0.40 GB |
| MapLibre + deck.gl | 0.91–0.94 GB | 2.2–2.3 GB | 0.37–0.40 GB |
These are the maxima over the three scenarios in Chrome; ranges reflect the MacBook and the Mac mini. ortho-earth decodes and draws tiles in workers, so its JS heap (the main thread) comes out small. That memory is counted in the renderer process instead, where it is level with Cesium. Safari on the iPhone does not let me measure memory.
The same 60 fps, different work
Every engine delivers 60 fps, but they cut corners in different places. Cesium and TerriaJS hold back loading while moving, Navara coarsens the picture up close on phones, and ortho-earth and MapLibre draw while loading.
Tiles fetched during zoom (M1 Mac mini, vsync on, 10 km → 300 m, median of 5 runs)
All of these are each engine's default behaviour and work as designed. So, apart from the main tables at the defaults, I ran four reference measurements with that behaviour switched off.
| Reference | Default | Aligned condition | What it shows |
|---|---|---|---|
| Cesium's culling while moving (cullRequestsWhileMoving and two others), Mac mini | 37 tiles, 5 MB during zoom, 60 fps | No culling: 350 tiles, 129 MB, 60 fps, p95 18.2 ms | Smooth on the M1 even without culling. First display 4.72 → 4.99 s |
| Navara's LOD multiplier on iPhone (8 on mobile) | 81 tiles, 31 MB in orbit, finishes in 10 ms | Multiplier 1: 299 tiles, 112 MB, finishes in 2.08 s | First display (8.16 → 8.46 s) and zoom unchanged; only close views are coarsened |
| ortho-earth's concurrent requests on iPhone (3 on touch devices) | First display 6.99 s (range 6.85–8.72) | 6 requests: 6.32 s (range 6.22–6.70) | The limit of 3 is the bottleneck. Smoothness unchanged, ties with Cesium |
| Cesium without vsync (no frame cap) | First display MacBook 7.31 s, Mac mini 12.2 s | vsync on: 5.91 s, 4.72 s | The uncapped render loop occupies the main thread and tile requests wait |
Cesium's culling is insurance for keeping things smooth on weaker devices; on these three machines, 60 fps held even without it. Navara and ortho-earth are built to hold back on mobile: Navara gives up detail, ortho-earth gives up loading speed. Following this measurement, ortho-earth now lets you set the number of concurrent requests with the maxInflight option of add3DTiles.
Strengths and weaknesses of each engine
Once the numbers were laid out, my impression of each engine became clear. I start with the engines that are not mine.
Cesium: the engine that impressed me most. Even on the M1 Mac mini its 95th-percentile frame time stayed at 18.1 to 18.4 ms, the most even of the five, and it finishes drawing 2 to 14 ms after the camera stops. Its renderer process memory, 1.4 GB, is on the smallest side with ortho-earth. Its culling while moving turned out to be a clever default rather than a shortcut: during zoom it reads only 37 tiles, about a tenth of the others, but the moment it stops it reads what it needs and finishes at once. With culling off, reading the same 350 tiles as the others, it still held 60 fps on the M1. It is smooth without culling; the culling is headroom for the network and the battery. I felt the depth built up over many years by the company that created 3D Tiles showing directly in the numbers. Its weakness appears when the frame cap is removed: the render loop takes over the main thread, tile requests wait, and first display stretches to 12.2 s on the M1. Ordinary browsers have vsync, so the real harm is small, but it was where the limit of drawing on the main thread showed most clearly.
TerriaJS: underneath it is about Cesium 1.142, only four versions from Cesium's own 1.146. So on the Macs, I think its gap from Cesium is better read as the overhead of the framework, the catalogue and the UI, than as Cesium's own progress. Indeed, on the Macs its first tile arrives late, at 1.0 to 1.1 s (Cesium about 0.5 s), and its JS heap is 30 to 70% larger than Cesium's. On the iPhone, on the other hand, it tied for first with 5.85 s, faster than Cesium itself (6.32 s). Its default 30 fps cap is also the right, battery-friendly choice for a finished product that shows data.
MapLibre + deck.gl: its first tile arrives earliest (0.3 to 0.4 s), and on the iPhone it ties for first display. Its loading itself is fast. But MapLibre does not draw the 3D itself; deck.gl draws it on top. So on the M1 and the iPhone it manages only about 40 fps in orbit, takes up to 3.3 s to finish after stopping, and uses the most memory of the five. This is less a weakness of MapLibre than the weight of combining a 2D map with a second library for 3D. For richness of 2D cartography, I think MapLibre is the best of the five.
Navara: still young at 0.2.1, yet it held 60 fps on all three machines, and with the cap removed on the M1 it reached 153 fps in orbit, the most headroom of any. Drawing handed to Three.js is light. Its weakness is per-tile processing, a median of 665 to 911 ms, two to four times the others, which puts it a step behind on first display. Coarsening the picture up close on the iPhone is a deliberate trade to protect smoothness. It is an engine whose growth I look forward to.
Where ortho-earth stands, and what remains
My own assessment is that ortho-earth sits in the same speed band as the mature Cesium while keeping the main thread free and memory small. On both Macs it tied for first display (5.90 s and 4.66 s), held 60 fps while moving, and finished drawing 3 to 10 ms after stopping. Its JS heap of 15 MB and GPU process of about 0.2 GB are both the smallest, and its per-tile processing, 149 ms on the M1, is the fastest.
Its defining feature is that the whole of drawing runs in a worker; of these five, ortho-earth is the only one that does this by default. With the main thread almost idle, it is built so that heavy UI on top of the map does not drag the drawing down. That said, its renderer process memory as a whole is 1.4 GB, about the same as Cesium, so it is not overwhelmingly lighter in total. And since these test pages do nothing but draw buildings, the benefit of a free main thread is not yet shown in numbers.
To be honest, four problems remained at the time of measurement. First, a brief white flash when the camera stops: it cleared the area of tiles not yet arrived before redrawing, and that is now fixed. Second, the 6.99 s first display on iPhone, because every touch device is treated as low-memory and limited to 3 concurrent requests. With 6 it ties Cesium at 6.32 s, and the number can now be set. These two ship in the next release. Third, extra redraws while the fog catches up after the camera jumps (60 to 80 right after start-up). Fourth, a WebGL error on the page that draws clouds. Of the bugs this measurement found, the inability to draw b3dm and the excess redraws at start-up were already fixed in 2.0.2.
Pitfalls in measuring
Along the way I fell into several pitfalls that badly distorted the numbers. I note them for anyone making a similar comparison.
- No buildings were drawn: Navara loads Draco's wasm from a worker by a relative path, so it was not copied into the Vite build and not a single building was drawn. Tile requests are still counted, so the numbers looked plausible. Now every run's screen is captured and runs without buildings are discarded.
- Some engines keep a cap even with vsync off: Chrome's uncapping applies only to pages that draw on the main thread. ortho-earth draws in a worker, so about 55 fps is the ceiling of the method. That is why the main tables compare with vsync on.
- Some engines get slower to first display with vsync off: Cesium's uncapped render loop occupies the main thread and tile requests wait (12.2 s to first display on the M1).
- deck.gl's fps comes out doubled: because the canvas overlaid on MapLibre is counted too. I compared how often the screen actually updated.
- Two displays cap the fps: Chrome draws in step with the display it sits on. On the Mac mini the measuring window opened on a 30 Hz secondary display and every engine ran at 30 fps. I fixed the window position and measured again.
- iPhone sleep and other devices: the iPhone was measured with a scheme that hands the next step to whichever device opens the serving page. At first results from a Mac browser got mixed in, and the iPhone also stopped when it went to sleep. I checked the device by the browser name recorded in each result and dropped the disturbed runs.
- Network: most of the first display is tile download, so it moves by hundreds of milliseconds with network conditions. For the MacBook I show the values re-measured with other work stopped.
Summary
If all you need is to show PLATEAU buildings, the choice of engine makes no big difference to first display or smoothness. What to look at is less the speed numbers than where each engine cuts corners, and what you want to use it for.
If you want smoothness even on weak devices, Cesium, which culls loading while moving, is the surest choice. If you need a map application with a data catalogue and sharing, TerriaJS sits on top of it. If you already have a 2D MapLibre map, overlaying deck.gl is easy, but on an M1 or an iPhone it cannot hold 60 fps in orbit. If you want to keep loading detail while moving and keep the main thread and memory free, ortho-earth; for drawing headroom and future growth, Navara. That is my reading.
Looking back, this test was a little too light. With only untextured buildings of Chiyoda ward, four engines hit the 60 fps ceiling and their underlying strength was hard to see. Nor did I measure what each engine is truly good at: terrain and base maps, Cesium's time-dynamic data, MapLibre's 2D cartography. Next I plan to widen the area to several central wards, use textured buildings, and give the main thread work of its own.
Once more, at the end: I am the developer of ortho-earth, so I may well be too kind to my own engine in places. Please do not take my assessment on trust; check it against the published pages and data, and judge fairly.
The pages and scripts used for the measurement, and the raw data and screens of every run, are published as plateau-bench (MIT). Follow its instructions and you can measure your own machines the same way. If you notice bias or errors, please tell me in an Issue.