WebGL Performance Optimization for Modern Portfolio Sites

WebGL Performance Optimization

Rich, interactive 3D is now standard for a premium agency website. A WebGL scene that dazzles on a workstation can still crawl on a mid-range phone, and performance is what separates a portfolio that feels effortless from one that drains the battery and stutters on scroll.

Table of contents:

Why WebGL performance is a budget problem

Every frame you render has a fixed time budget. At 60fps that is about 16.7 milliseconds, and half of that if you are targeting 120Hz displays. Inside that window the GPU runs your vertex and fragment shaders, the CPU prepares draw calls, and the browser composites and paints the rest of the page. Treating performance as a budget from the outset changes how you design a scene. You decide up front how many milliseconds each effect is allowed to cost.

The most common cause of jank is the number of draw calls rather than raw polygon count. Each unique material and geometry combination the CPU issues separately adds overhead, and hundreds of small objects will choke the main thread while the GPU sits idle. Profiling with the browser frame timeline, and Spector.js for the GL command stream, shows whether you are CPU-bound or GPU-bound. Those two conditions call for very different fixes.

Cut draw calls with instancing and merging

When you need many copies of the same object, such as trees, particles, product tiles or repeated cards, geometry instancing lets the GPU draw all of them in a single call. You upload one geometry and a buffer of per-instance transforms instead of issuing ten thousand separate draws. In Three.js this is InstancedMesh. In raw WebGL2 it is drawArraysInstanced. The saving is dramatic, and we routinely take scenes from 4,000 draw calls to under 50.

For static scenery that never moves relative to itself, merge meshes at build time so the whole set becomes one buffer. Combine that with a texture atlas so the merged mesh shares a single material, and a complex environment collapses into a handful of draw calls the browser issues in well under a millisecond.

Shrink textures before they reach the GPU

Textures are usually the biggest memory cost in a WebGL scene. A 4096 by 4096 PNG that looks fine on disk expands to tens of megabytes once decoded into GPU memory. Ship compressed GPU texture formats instead, such as KTX2 with Basis Universal transcoding, so the image stays compressed all the way into video memory. You get high-resolution assets that load faster and occupy a fraction of the space.

  • Generate mipmaps so distant surfaces sample smaller textures and avoid shimmering.
  • Cap texture resolution to what the device can actually display, since mobile rarely benefits from 4K maps.
  • Lazy-load secondary textures after first paint so the hero renders immediately.

Render only what the camera can see

If a triangle sits off screen, behind the camera, or fully occluded, spending GPU cycles on it is wasted work. Frustum culling skips objects outside the camera view, and level of detail swaps in cheaper, lower-poly models as objects recede into the distance. The viewer never notices the substitution, and the GPU renders a small fraction of the geometry.

On heavy scenes we also cap the device pixel ratio. Rendering a full 3x buffer on a modern phone quadruples the fragment work for almost no perceptible gain. Clamping the pixel ratio to around 2 is often the single biggest mobile win available.

Use post-processing with care

Bloom, depth of field, ambient occlusion and colour grading give a scene its cinematic quality, and each one adds a full-screen render pass that reads and writes the entire framebuffer. On a high-resolution display those passes add up quickly. Combine effects into a single pass where the library allows it, render the heaviest effects at half resolution and upscale the result, and choose cheaper approximations such as screen-space ambient occlusion tuned to a modest sample count.

Always measure a post-processing stack on your target hardware before committing to it. An effect that costs two milliseconds on a desktop GPU can cost ten on an integrated mobile chip, which is most of a frame budget gone before the scene itself has drawn. When a device cannot afford the full stack, detect it and fall back to a lighter preset rather than shipping one heavy configuration to everyone.

Shaders deserve the same scrutiny. Move work from the fragment shader to the vertex shader where you can, since fragment shaders run once per pixel and vertex shaders run once per vertex. Precompute values on the CPU when they do not change every frame, and keep texture lookups inside a shader to the minimum the effect truly needs.

A pragmatic shipping checklist

Performance work compounds, so we sequence it. Measure first, fix the biggest offender, then measure again. In practice that means profiling on a real mid-range Android device rather than a fast laptop, because that is where problems actually surface.

  • Instance or merge anything that repeats, and hunt down stray high draw-call counts.
  • Compress textures to KTX2 and mipmap them, then lazy-load the non-critical ones.
  • Enable frustum culling and level of detail, and clamp the device pixel ratio on mobile.
  • Dispose of geometries, materials and textures you no longer need to avoid GPU memory leaks.
  • Budget every effect in milliseconds and hold the line on 60fps.

This is the discipline we bring to client builds. For an immersive, animation-heavy site that still loads fast and ranks well, explore our custom software and web engineering work, or start a project with the studio.

Pick a time

Thirty minutes.
Your project, your questions.

Pick a time that suits you and we will walk you through the work live: how we build it, what a full project looks like, and what it costs. No deck, no hard sell.

A call with the team that does the work

30 minutes, at your time

Prefer email first? The briefing form is right below

Contact

Let's talk.

A direct line to the team behind the work. No account managers, no briefing relay between departments. Tell us about your next project and we'll reply within 24 hours with concrete next steps.

Response Within 24 hours, direct from the team

Available  •  Remote-first, worldwide

Briefing

Send us a short briefing.