Performance Guide
WebGL inevitably raises one question: what about performance? Expressive graphics are only interesting if they run smoothly. Rather than obscuring performance costs, Unicorn Studio makes them visible — so performance becomes part of the design process, not an afterthought.
How Scenes Render
Unicorn Studio builds scenes by assembling layers sequentially. Each layer renders by default as its own shader pass. When a scene runs in the SDK, those shaders are stitched together using render targets. That flexibility comes with cost — more layers mean more draw calls, texture reads, and GPU work. Each layer adds overhead: texture reading/writing, full screen quads, GL context switching, and additional draw calls.
What Makes a Layer Expensive?
Some effects perform heavy math in loops. Aurora, for example, runs multiple iterative trigonometric functions for every pixel it draws. That's powerful — but costly. Stacking multiple heavy effects will slow your scene down.
Effects that tend to be on the expensive side:
- Aurora
- 3D Shape
- Bokeh blur
- Noise blur
- Nebula
- Bloom
- FBM
- Godrays
- Mouse trail
- Mouse ripple
- Light draw
Before adding a child effect, consider whether you can achieve the same result by adding it to the whole scene. You can also use pre-rendered images for complex static effects.
Performance Estimator
Press F to open the Performance Estimate panel. You'll see:
- A cost score from 0–100
- A general estimate of how expensive the scene will be
This gives you immediate feedback on performance impact as you build.
The Test Page
The test page is accessible from the Export window. It gives you a live, interactive environment for profiling and tuning your scene before it goes to production. Press C or ⌘/Ctrl + . to toggle the controls.
Preview Controls
These let you simulate different embedding scenarios without leaving the page:
- Background — switch between dark and light to see how your scene looks against different page backgrounds.
- Scene size — render at 0.25x, 0.5x, or 1x to preview your scene as a small module versus fullscreen.
- Scroll Mode — choose None, Scroll, or Fixed to test how your scene behaves in a scrollable page or as a fixed-position element.
Settings
These map directly to the SDK embed parameters (data-us-scale, data-us-dpi, data-us-fps) and let you experiment with different values in real time:
- Scale — canvas rendering scale (0.25, 0.5, 0.75, 1). Lower values reduce the number of pixels the GPU has to process.
- DPI — scene resolution (1, 1.5, 2). Higher values look sharper on retina displays but cost more.
- FPS — target frame rate (24, 30, 60, 120). Lowering this can free up significant GPU budget.
When you change Scale, DPI, FPS, or per-layer downsampling, a toast will appear at the bottom asking if you'd like to publish the optimizations. You must republish for these changes to take effect in your live embed.
Render Stats
The right panel shows real-time rendering metrics:
- Performance — an overall label (Excellent, Good, Strained, or Poor) based on how well the scene is hitting its target frame rate.
- Draw calls / frame — how many individual draw operations happen per frame. Fewer is better.
- FPS (avg) — the measured average frames per second.
- Dropped frames — frames that took longer than the budget and were skipped.
- Frame time (avg) — average milliseconds per frame.
- Frame budget — the maximum time allowed per frame at your target FPS (e.g. 16.67ms at 60fps).
- Budget usage — frame time as a percentage of the budget. Under 100% is good; over 100% means you're dropping frames.
- Budget trend — a sparkline chart showing budget usage over time, so you can see if performance is stable or fluctuating.
Memory
Below the render stats you'll see memory usage broken down into:
- Textures — memory used by image textures loaded onto the GPU.
- Render targets — memory used by intermediate framebuffers (the render targets that stitch layers together).
Pipeline Panel
The left panel shows your layer stack rendered bottom → top, mirroring the order layers are drawn. For each layer you can see:
- Layer name and whether it's actively drawing.
- Downsample control — adjust per-layer downsampling right here (0.25, 0.5, 0.75, 1). This is often the single biggest performance win.
- Plane dimensions — the actual pixel size each layer is rendering at, so you can see the impact of downsampling.
- Flattened layers — if Flatten is enabled, merged layers appear as a single entry with an info tooltip listing the sublayers inside.
The output layer (topmost in the pipeline) uses the global Scale setting and can't be individually downsampled.
Flatten (Beta)
Flatten changes everything. Instead of stitching many shaders together at runtime, Flatten:
- Merges compatible layers into one intelligently optimized shader
- Reuses shared functions
- Culls unnecessary calculations
- Hoists distortions
- Reduces draw calls and overhead
In short — you get one smarter, smaller shader instead of many chained together with FBO's. When you toggle Flatten on, you'll see the cost score drop significantly.
Early Exit via Culling
Flatten also enables something new. If you use a mask (like a vignette, or a masked shape), Flatten stops processing heavy effects outside the visible mask area. This means you can use powerful underlying effects without "paying" for pixels that aren't visible.
What Can Be Flattened
- Images
- Shapes
- Text
- Most single-pass effects
- Distortions
- 3D models (non-glass, no background environg map)
What Can't Be Flattened
- Multi-pass effects (like 90s VHS) — these require multiple shader passes and must remain separate.
- Ping-pong effects (mouse ripple, trail, and light draw) — these don't have multiple passes in the main pipeline but use render targets on the side.
Think of your layer stack like this:
| Position | Layer Type |
|---|---|
| Top | Post-processing effects |
| Middle | Distortions |
| Bottom | Elements (images, shapes) |
If you place multi-pass effects in the middle of the stack, they break the flatten chain. Move them to the top whenever possible so everything beneath them can merge.
Common Gotcha: Blend & Background Sampling
Some properties prevent flattening even if the effect is normally flattenable. For example, if a projection or polar distortion uses blend, it needs to sample the background — but in a flattened shader, everything is compiled into one pass and can't sample the "previous" layer the same way.
Turning blend to zero may suddenly allow flattening and unlock large savings.
Other properties that can break flattening:
- Chromatic dispersion
- Certain stretch/liquify settings
- Anything that samples background data
Always check these properties if a layer isn't flattening as expected.
Downsampling
Each layer has a downsample control, and this is often the single biggest performance win.
The math matters here: if you downsample by 50%, you reduce the width by half and the height by half — that's 75% fewer pixels total (half × half = one quarter). Anything that doesn't require sharp edges (text, 3D shapes) can often be downsampled safely with no visible quality loss.
You must republish your scene after changing downsampling for changes to take effect.
SDK & Embed Parameters
Several parameters let you balance quality and performance globally:
| Parameter | Description | Recommended Range |
|---|---|---|
data-us-scale |
Canvas rendering scale | 0.25–1.0 |
data-us-dpi |
Scene resolution | 1.0–1.5 |
data-us-fps |
Frame rate | 30–60 |
Start with lower values and increase until you find the right balance for your use case.
Scene Size
Smaller scenes (modules) perform better than fullscreen scenes. Consider breaking large scenes into smaller components.
Page Implementation
Loading the SDK
Include the library in the <head> tag of your site if your scene is in the initial viewport.
- If you're using the Framer component, it works out of the box but will load faster if you include the library in the head tag with Custom Code. The same applies to Webflow and other site builders that offer custom head tag code.
- You may need to add
asyncordeferto the script tag so as not to block the page load.
Some older versions (<1.3.2) of the library are missing key optimizations.
Multiple Scenes
Having multiple scenes on a single page increases initial load time and compounds memory usage. Unicorn has built-in logic to efficiently manage multiple scenes, but it is not recommended to have more than 10 scenes on a single page. WebGL has a maximum limit of 16 contexts.
Lazy Loading
Enable lazy loading so scenes only initialize when they enter the viewport:
Production Mode
Enable production mode for optimal performance — it serves scene data from CDN, reduces initial load time, and improves caching:
You can also get the same benefits by hosting your own scene JSON and serving it from a CDN.
Built-in Optimizations
Combined with a lightweight runtime (~29kb gzipped), Unicorn Studio handles a number of optimizations automatically:
At publish:
- Compiles and optimizes shader code for each layer
At runtime:
- Compresses static layers into a single texture (starting from the background and working up)
- Pauses scene rendering when out of the viewport
- Throttles and pauses expensive operations when possible
Quick Reference
- Watch the performance estimator — press F to check your cost score as you build
- Use Flatten — merge compatible layers into one optimized shader
- Downsample strategically — 50% downsample = 75% fewer pixels
- Be intentional with layer counts — fewer layers means fewer draw calls
- Group compatible layers — keep flattenable layers together
- Move post-processing to the top — don't break the flatten chain
- Check blend & background sampling — these properties can silently prevent flattening
- Enable lazy loading —
data-us-lazyload="true" - Use production mode —
data-us-production="true" - Adjust scale/DPI/FPS —
data-us-scale="0.5"data-us-dpi="1.0"data-us-fps="30"
Always monitor your scene's performance across different devices and browsers.