A 180,000-triangle sofa in a furniture AR app runs at 60 fps on the artist’s iPhone 16 Pro and 21 fps on the Galaxy A54 that a third of the users are holding. The artist’s response is to cut the sofa to 90,000 triangles. Frame rate goes to 24 fps. The triangles were never the problem — the app was issuing 340 draw calls and rendering four 4K texture sets, and no amount of decimation was going to fix that.
Polycount budgets are still worth setting, but only if you set them against the right thing. A per-asset number in isolation means nothing. What matters is what the GPU has to chew through in a single frame, and how many separate commands it takes to get there.
Budget the frame, then divide it up
Start from the platform’s per-frame ceiling and work backwards. These are conservative working numbers for a 60 fps target with a normal amount of shading work, not synthetic maximums:
| Platform | Tris per frame | Draw calls | Texture memory |
|---|---|---|---|
| Low-end Android (Mali-G52, Adreno 610) | 120k–250k | 50–80 | 250 MB |
| Mid Android / iPhone 12 | 350k–700k | 80–150 | 500 MB |
| iPhone 15 Pro and up | 900k–1.6M | 150–250 | 1.2 GB |
| Quest 3 (per eye, 72–90 Hz) | 300k–500k | 100–180 | 1.5 GB |
| Browser / WebGL on a laptop | 500k–1.5M | 100–200 | 400 MB |
| Mid PC (RTX 3060) | 4M–8M | 1,500–3,000 | 6 GB |
| PS5 / Xbox Series X | 8M–15M | 3,000–6,000 | 10 GB |
Notice how much faster the draw-call column scales than the triangle column between mobile and PC. That is the real story of the last decade of hardware: triangle throughput grew enormously, per-draw CPU overhead did not. On mobile you will hit the draw-call wall long before the triangle wall, which is why batching, atlasing and instancing beat decimation almost every time.
Per-asset numbers that survive review
Once you know the frame budget, allocate it. A mobile scene with 250,000 triangles to spend and 40 visible objects gives you an average of 6,000 each — but averages are the wrong tool. Spend heavily on what fills the screen and ruthlessly on what does not.
| Asset type | Mobile / WebGL | PC / console | Texture set |
|---|---|---|---|
| Background prop (crate, bottle) | 200–900 | 1,500–5,000 | 512² shared atlas |
| Interactive prop (chest, terminal) | 1,500–4,000 | 8,000–25,000 | 1024² |
| Hero prop (weapon, artifact) | 5,000–12,000 | 25,000–60,000 | 2048² |
| First-person weapon | 8,000–15,000 | 40,000–90,000 | 2048² ×2 |
| NPC character | 3,000–9,000 | 20,000–50,000 | 1024²–2048² |
| Player character | 12,000–22,000 | 60,000–120,000 | 2048² ×2–3 |
| Vehicle (drivable) | 10,000–20,000 | 80,000–180,000 | 2048² ×2 |
| Modular wall / floor piece | 50–400 | 300–2,000 | Trim sheet, 2048² |
| Product viewer on the web | 40,000–120,000 | — | 2048², under 8 MB total |
That last row surprises people. A single web product viewer can afford six figures of triangles precisely because it is the only thing on screen; there is no frame to share. The binding constraint moves from GPU time to download size, and a 100,000-triangle mesh quantized and Draco-compressed lands around 1.4 MB — perfectly reasonable.
Triangles smaller than eight pixels are wasted money
GPUs shade in 2×2 pixel quads. A triangle covering a single pixel still causes a full 2×2 quad to be shaded, so three of the four samples are thrown away. This is quad overdraw, and it means a 500,000-triangle model displayed at 400 pixels tall can cost more pixel-shading time than a 50,000-triangle version of the same model, while looking identical.
The practical rule: no triangle should be smaller than roughly 8 to 16 pixels at its intended viewing distance. A 2-metre prop viewed from 10 metres at a 60-degree vertical FOV on a 1080p display covers about 200 pixels of height. At 8 pixels per triangle across a rough 200×150 pixel footprint, you can justify something like 900 triangles for the visible surface — call it 1,800 including the back faces you cannot see. Anything beyond that is invisible detail you are paying full price for.
This is also the argument for LODs that people skip because “the model is only 8k”. Eight thousand triangles at 40 pixels on screen is pure waste, regardless of how small 8k sounds.
LOD ratios that work
Halving triangles per level is the convention, and it holds up. Switch on screen coverage rather than distance so the chain behaves the same at every FOV and resolution.
| Level | Triangle ratio | Screen height | Example (30k hero prop) |
|---|---|---|---|
| LOD0 | 100% | > 45% | 30,000 |
| LOD1 | 50% | 20–45% | 15,000 |
| LOD2 | 22% | 9–20% | 6,600 |
| LOD3 | 9% | 3–9% | 2,700 |
| Cull / impostor | — | < 3% | 0–2 quads |
Auto-generated LODs are good enough for props and bad for characters, where a naive decimator will collapse fingers and eyelids into mush. Generate LOD1 and LOD2 automatically, hand-check LOD3, and always author LOD0 yourself.
Transparency will eat the budget you just saved
Alpha-blended geometry is the most expensive thing you can put in a mobile scene, and it has almost nothing to do with polycount. Every blended pixel must be read, shaded and written back in draw order, with no early depth rejection. Layer four foliage cards over each other across a full 1080p screen and you have shaded roughly 8.3 million pixels to produce 2 million.
Mobile GPUs are tile-based and handle opaque overdraw well, because hidden-surface removal happens on-chip before shading. Blending defeats that entirely. The practical guidance: use alpha-test (cutout) rather than alpha-blend wherever the material allows it, keep average overdraw under 2.5× on mobile and under 4× on PC, and build foliage as tight fitted geometry rather than large rectangles. A leaf card trimmed to an 8-triangle outline instead of 2 triangles costs six extra triangles and saves 40 percent of its pixel cost — an obviously good trade that contradicts the instinct to minimise triangles.
When frame time is bad and you do not know why, render the scene with an overdraw visualiser before touching a single mesh. Unity, Unreal and most WebGL inspectors have one. Red areas are where your milliseconds went.
Vertex count is what memory cares about
Vertex buffer memory is driven by vertex count, and vertex count is driven by UV seams and hard edges rather than by triangles. A full glTF vertex — position (12 bytes), normal (12), UV (8), tangent (16) — is 48 bytes uncompressed, or 20 to 24 bytes with standard quantization. A 30,000-triangle prop that welds to 17,000 vertices needs about 816 KB of vertex data at 48 bytes each, plus 180 KB of 16-bit indices. Add a second UV set for lightmaps and you are at 950 KB.
Two practical consequences. Keep vertex counts under 65,536 per mesh so the renderer can use 16-bit indices, which halves index buffer size and is faster on mobile. And be deliberate about hard edges — every hard edge you add duplicates vertices along it, and a model with hard edges everywhere can carry 60 percent more vertices than the same triangle count with smoothing groups.
Do and don’t
- Do profile a real scene on the worst device you support before setting any budget. Numbers from an article, including this one, are a starting hypothesis.
- Do merge static meshes that share a material; 40 objects at 500 triangles each is worse than one object at 20,000.
- Do set the target polycount at generation time when you can. MeshyFlix’s target polycount setting produces a mesh at your budget directly, which avoids a decimation pass that damages UVs.
- Do check triangle density visually with a wireframe overlay at gameplay distance, not in a close-up turntable.
- Don’t decimate as a first response to a frame-rate problem. Check draw calls, overdraw and texture memory first.
- Don’t ship geometry that is never visible — interior faces, hidden undersides, the bottoms of objects fixed to the floor. Deleting them costs nothing and is free performance.
- Don’t model detail smaller than a few pixels. Bake it to a normal map.
- Don’t assume virtualized geometry (Nanite and equivalents) removes the budget. It changes the currency to instance counts, material complexity and overdraw, and it does not exist on mobile or on the web at all.
Where to go from here
Open your heaviest scene on your minimum-spec device and capture a single frame — RenderDoc for PC and Android, Xcode’s GPU capture on iOS, Spector.js or the renderer’s info object for WebGL. Write down four numbers: triangles submitted, draw calls, texture memory resident, and GPU frame time.
Then cut your worst offender in half and capture again. If frame time barely moves, triangles were not your bottleneck and you have just learned where to spend the rest of the week. That single before-and-after capture is worth more than any budget table, including the ones above.