The most common pattern I see in failed browser-based 3D projects: the desktop demo is brilliant. The client is delighted. The dev team checks it on an iPhone — works fine, maybe a stutter or two, ship it. Six weeks after launch, the marketing team gets emails from customers complaining about overheating phones. Analytics show 70% of mobile sessions abandoning the page within ten seconds. The project is quietly removed from the homepage.
This isn't a freak failure mode. It's the default outcome for browser 3D projects that don't take mobile seriously from day one. Mobile WebGL is the constraint that most decides whether your interactive 3D web feature actually reaches your users — and most teams under-budget for it.
This article is the mobile performance playbook I work through on every browser 3D project. The numbers below are real and they will save your project from the death-by-mobile failure mode.
Why desktop confidence is misleading
A modern desktop GPU is two orders of magnitude more capable than the GPU in a mid-range Android phone. A scene that runs at 120 FPS on a developer's M1 MacBook can hit 12 FPS on the device half your users own. The desktop demo proves nothing about whether the project will ship to mobile.
This is the structural fact most teams underweight. The desktop developer's experience of "smooth" is unrelated to the mobile user's experience. If mobile isn't tested on real devices throughout development, it isn't tested.
Specifically, the constraints that compress hardest on mobile:
- GPU compute — mobile GPUs have a fraction of the desktop equivalent's shader throughput
- Memory bandwidth — texture sampling becomes the bottleneck faster on mobile
- Texture memory — total VRAM is often 1–2 GB, shared with system memory
- Thermal throttling — sustained high load causes the GPU to underclock, dropping performance progressively over a session
- Battery — a 3D experience that runs at 30 FPS but kills the phone's battery in fifteen minutes will be uninstalled or never returned to
- Network — mobile connections carry every megabyte you ship
Each constraint has a number worth knowing.
The mobile performance budget
Here's the budget I use for browser-based 3D projects targeting mid-range mobile devices (iPhone 11/12 era, Pixel 5/6 era, equivalent Android). Newer flagships have more headroom; older devices have less.
Frame budget: 16ms
Targeting 60 FPS means 16 milliseconds per frame for everything — render, JavaScript, layout, asset streaming. Mobile phones throttle aggressively when you blow this budget repeatedly, so going over even occasionally compounds.
For most mobile WebGL projects, a 30 FPS target is more honest than 60 FPS. That gives 33ms per frame, which is actually achievable. The user experience difference between 30 and 60 FPS for a configurator or product viewer is small. The thermal and battery difference is enormous.
Draw call budget: 50–100 per frame
A draw call is a single instruction to the GPU to render something. Mobile GPUs handle far fewer per frame than desktop equivalents.
For mobile WebGL, I budget 50–100 draw calls per frame as the safe ceiling. Beyond that, performance falls off rapidly. Above 200 draw calls, most mid-range mobile devices are unusable.
Reducing draw calls is one of the highest-leverage optimization moves. Techniques: instanced meshes for repeated geometry, atlased textures so multiple objects share a material, baked geometry where multiple static meshes are merged into one.
Triangle budget: 100,000–200,000 visible
Modern mobile GPUs can render more than this — but the budget isn't about rendering. It's about ensuring you have headroom for shader complexity, post-processing, and the occasional spike. Stay under 200K visible triangles per frame on mobile and most mid-range devices stay smooth.
For a configurator scene, that means: hero product 50K triangles, supporting environment 30K, UI billboards a few thousand, total well under the budget.
Texture memory budget: 50–80 MB
Total texture memory across all loaded assets, after compression. For mobile, I budget 50–80 megabytes of compressed texture data.
This translates to: maybe 4–6 hero textures at 2048² with KTX2 compression, plus a dozen secondary textures at 1024² or smaller. A scene with 20 unique 2048² PNGs on mobile is unshippable; the same scene with KTX2-compressed textures at 1024² is fine.
Initial download budget: 2–3 MB JavaScript, 3–5 MB assets
The page is supposed to be useful on a 4G connection. A reasonable initial-load budget for the 3D feature on a marketing or product page is 2–3 MB of JavaScript and 3–5 MB of 3D assets.
That sounds tight, and it is. It's also the realistic ceiling for shipping to mobile users without losing them in the first ten seconds. Above 8 MB total, mobile bounce rates get ugly.
For larger projects, asset streaming after initial interaction is the way out — load the minimum needed to show something, stream the rest as the user explores.
The optimization stack
In order of leverage, the things I do to bring mobile performance into budget.
1. KTX2 textures
Already covered in detail in the asset pipeline article — but worth repeating because it's the single biggest mobile win. A 2048² PNG is 8 MB. The same image as KTX2 is 1.5 MB. Mobile texture memory pressure goes away when KTX2 is the standard.
2. Aggressive geometry simplification for mobile
A hero product asset can be 50,000 triangles for desktop. The same asset for mobile should be 20,000 — usually achieved by running the desktop asset through a decimation pass, or having the modeler produce a separate LOD.
The visual quality difference is subtle at typical mobile viewing distances. The performance difference is significant.
3. Capping device pixel ratio
Modern phones have 3x device pixel ratios. Rendering at native resolution means 9x the pixels of a logical-resolution render — a massive GPU cost for marginal visual improvement.
Cap pixel ratio at 1.5 or 2 on mobile. Three.js: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)). The visual difference is barely perceptible. The performance difference is night and day.
4. Reducing dynamic lights
Mobile fragment shaders are expensive per-pixel. Each dynamic light multiplies that cost. Five lights is a lot for mobile. Two is comfortable. Bake static lighting into textures wherever possible.
For a product configurator, the lighting is usually solved with two or three carefully placed lights plus an environment map for reflections. Adding more lights doesn't visibly improve the result and tanks mobile performance.
5. Disabling expensive post-processing on mobile
Bloom, depth of field, screen-space ambient occlusion — all gorgeous on desktop, all very expensive on mobile. For mobile, I default to disabling all post-processing and adding back only what the design genuinely requires.
A clean, well-lit scene without bloom looks substantially better than a bloomed scene running at 18 FPS. The user notices the framerate before they notice the absence of post-processing.
6. Frustum culling and occlusion culling
Three.js does frustum culling automatically — meshes outside the camera's view don't render. Verify this is working. Make sure your meshes have correct bounding boxes; otherwise they render even when off-screen.
Occlusion culling — skipping meshes hidden behind other meshes — isn't built into Three.js. For complex scenes, implementing manual occlusion checks based on camera position can be worth the effort.
Testing on real devices
The single most valuable thing in mobile WebGL development is a drawer of real test devices. Simulators lie about thermal throttling, GPU limits, memory pressure, and touch input.
A working test setup includes:
- An iPhone from 2–3 years ago. Not the latest iPhone — the phone that represents the median of your iOS audience.
- A mid-range Android. Pixel A-series or equivalent. Not a flagship Samsung Galaxy.
- A budget Android. A device from the $200–$300 price range. This is what a meaningful percentage of your audience actually uses.
- A way to remote-debug on each. Chrome DevTools for Android, Safari Web Inspector for iOS.
I test on these throughout development, not at the end. Catching a mobile performance issue in week three is a half-day fix. Catching the same issue in week eight is a project-threatening rebuild.
What this means for the engagement
Mobile performance is the hardest thing to retrofit and the easiest thing to budget for upfront. A project that's mobile-first from day one ships smoothly. A project that's "we'll optimize for mobile later" ships either late, ugly, or not at all.
When I scope a webgl development service engagement, mobile is a primary constraint in the discovery conversation. The device share question — what percentage of your traffic is mobile — usually decides whether a project is two months or four. Sites with majority-mobile traffic require fundamentally different architecture choices than sites with majority-desktop traffic.
For an in-house team without mobile WebGL experience, this is one of the higher-leverage places to bring in an outside specialist. The optimization techniques are learnable, but the budget intuition — knowing instinctively that 200 draw calls is a problem before the dev team has profiled — comes only from shipping mobile WebGL projects. Hiring someone who's done it once before is faster than developing the intuition from scratch.
If you have a 3D feature that's working on desktop and falling apart on mobile, the discovery call is where we figure out which of the budget items above is the binding constraint. Most projects are blown by one or two specific issues, not a hundred small ones. Identifying which two saves weeks of unfocused optimization work.