Unity WebGL Performance and Memory Optimisation
Strategies and tooling for reducing Unity WebGL build size, improving load time and managing memory constraints — based on real production deployments.
Introduction
Unity WebGL deployment presents unique challenges compared to native platforms. Build size directly impacts load time, memory constraints are tighter than desktop, and browser compatibility requires careful attention. This article shares strategies we have used in production to reduce WebGL build size by 70%+, improve load times by 5x, and maintain stable performance across browsers.
The techniques described here have been tested on real projects including training simulators with 850+ certified users, industrial metaverse platforms, and educational games. They are practical, not theoretical—these are what actually work in production.
Build Size Reduction
Build size is the most critical factor for WebGL load time. Every megabyte adds seconds to load time, especially on mobile connections. We attack build size from multiple angles.
Code Stripping
Unity code stripping removes unused code from the build. We use the highest stripping level (High) and enable managed code stripping. This requires careful attention to reflection usage—code accessed only through reflection may be incorrectly stripped. We use the [Preserve] attribute on classes and methods that must be retained.
For projects with heavy reflection requirements, we create link.xml files to explicitly preserve necessary assemblies. The link.xml approach is more maintainable than scattering [Preserve] attributes throughout the codebase.
Asset Bundle Strategy
We move non-essential assets out of the main build into asset bundles. The main build contains only critical assets needed for the initial scene. All other assets are loaded on demand from asset bundles hosted on a CDN. This reduces the initial download size significantly.
Asset bundles are organised by scene or feature to enable efficient loading. We use Addressables for asset bundle management as it provides a clean API and handles dependency management automatically. Addressables also supports remote catalog updates, enabling content updates without rebuilding the entire application.
Texture Compression
Textures are often the largest contributors to build size. We use texture compression formats appropriate for WebGL: ASTC for mobile browsers, BC7 for desktop browsers. We configure texture compression in the import settings and set max texture size based on actual usage—many textures do not need 4K resolution.
We also use texture atlases to combine small textures into larger ones. This reduces draw calls and improves compression efficiency. UI elements are particularly good candidates for atlasing.
Audio Optimisation
Audio files are compressed using appropriate formats. For music and ambient sounds, we use Ogg Vorbis with reasonable bit rates (typically 96-128 kbps). For short sound effects, we use compressed formats. We avoid uncompressed WAV files in production builds.
We also implement audio streaming for long audio files. Streaming loads audio progressively rather than loading the entire file into memory, reducing memory pressure and initial load time.
Remove Unused Assets
Over time, projects accumulate unused assets. We regularly audit the project for assets that are not referenced. Tools like the Unity Asset Usage Detector help identify unused assets. Removing these assets reduces build size and clutter.
Load Time Optimisation
Even with reduced build size, load time can be improved through smart loading strategies.
Loading Screen Design
A well-designed loading screen makes load times feel shorter. We show progress indicators that reflect actual loading progress, not fake progress bars. We display tips or educational content during loading to engage users. For training applications, we show learning objectives or safety reminders during the loading screen.
Progressive Loading
We load assets progressively rather than all at once. The initial scene loads with minimal assets, then additional assets load in the background. This gets users into the experience faster even if the full experience is not immediately available.
For example, in a training simulator, we load the training environment first, then load detailed models and textures in the background. Users can start reading instructions or reviewing objectives while the full environment loads.
Compression Level
Unity WebGL builds use gzip compression by default. We enable Brotli compression as well for better compression ratios. Brotli typically provides 15-20% better compression than gzip. The trade-off is slightly slower compression at build time, but this is a one-time cost.
CDN Hosting
Hosting WebGL builds on a CDN with edge locations worldwide dramatically improves load times for global users. We use CloudFront or similar CDNs with appropriate cache headers. The build files are cached aggressively with long cache times, while the index.html and configuration files have shorter cache times to enable updates.
Memory Management
WebGL has strict memory limits compared to native platforms. Browser tabs typically have memory limits of 2-4GB on desktop and less on mobile. Unity WebGL adds overhead, so usable memory is even less. Careful memory management is essential.
Memory Profiling
We profile memory usage extensively using the Unity Profiler and browser memory tools. We track both managed memory (C# objects) and native memory (textures, meshes, audio). We identify memory leaks by monitoring memory over time—if memory grows monotonically during normal usage, there is a leak.
Object Pooling
Object pooling reduces garbage collection overhead by reusing objects instead of creating and destroying them. We pool frequently instantiated objects like projectiles, particles, and UI elements. The pool pre-allocates a reasonable number of objects and expands as needed.
For WebGL, pooling is particularly important because garbage collection can cause noticeable frame rate drops. By pooling objects, we reduce GC frequency and improve frame time stability.
Texture Memory Management
Textures consume significant memory. We implement texture streaming where appropriate—load lower resolution textures first, then load higher resolutions when needed. We also unload textures that are not currently visible. For scenes with many textures, we implement a texture budget and unload least-recently-used textures when the budget is exceeded.
Asset Unloading
We explicitly unload assets when they are no longer needed. Using Addressables, we call Release() on asset references when done. For scenes, we use SceneManager.UnloadSceneAsync with the UnloadUnusedAssets option to clean up memory after scene transitions.
We also implement manual garbage collection triggers at appropriate points—after scene loads, after intensive asset loading, and during natural pauses in the experience. This gives the GC time to run without impacting critical moments.
Wasm Heap Size
Unity WebGL uses a WebAssembly heap for managed memory. The heap size is configurable in Player Settings. We set the initial heap size based on actual usage profiling—too small causes frequent resizing, too large wastes memory. We enable the exception handling option only during development as it adds significant overhead.
Runtime Performance
WebGL performance is lower than native platforms. Optimisation is necessary for acceptable frame rates.
Draw Call Reduction
Draw calls are expensive on WebGL. We combine meshes where possible, use static batching for geometry that does not move, and use dynamic batching for small moving objects. We also use GPU instancing for repeated objects like trees or environmental elements.
We also implement frustum culling aggressively—objects outside the camera view should not be rendered. Unity handles this automatically for most objects, but we ensure that objects are properly marked as static when appropriate.
Shader Optimisation
Complex shaders are expensive on WebGL. We use the Standard Shader only when necessary—simplified shaders are often sufficient for many objects. We avoid expensive operations like real-time shadows on mobile WebGL. We use lightmaps instead of real-time lighting where possible.
We also implement shader LOD—use simpler shaders for distant objects. The visual difference is often imperceptible, but the performance savings are significant.
Physics Optimisation
Physics simulation is CPU-intensive. We reduce physics complexity where possible—use simplified colliders instead of mesh colliders, reduce fixed timestep if appropriate, and limit the number of physics objects. For training simulators, we often use kinematic objects instead of full physics where realistic physics is not required.
Script Optimisation
We profile C# code performance using the Unity Profiler. Common optimisations include caching component references instead of calling GetComponent repeatedly, avoiding allocations in Update loops, using object pools instead of instantiation, and minimising string concatenation.
We also use the Job System and Burst Compiler for parallelisable workloads. This can provide significant performance improvements for calculations that can be parallelised.
Browser Compatibility
WebGL applications must work across different browsers with varying capabilities.
Browser Testing
We test on all major browsers—Chrome, Firefox, Safari, Edge—on both desktop and mobile. Each browser has different WebGL implementations and performance characteristics. Safari on iOS is particularly constrained and requires careful optimisation.
Feature Detection
We implement feature detection to adapt to browser capabilities. For example, we detect available texture compression formats and fall back to uncompressed textures if necessary. We detect WebGL version and adjust quality settings accordingly.
Mobile Considerations
Mobile browsers have tighter constraints. We implement quality settings that automatically adjust based on device capabilities—lower texture resolution, reduced shadow quality, simplified shaders on mobile devices. We also provide manual quality settings for users who want to prioritise performance or quality.
Debugging and Monitoring
WebGL debugging is more challenging than native platforms. We use specific tools and techniques.
Browser Developer Tools
Browser developer tools are essential for WebGL debugging. We use the Performance tab to profile frame times, the Memory tab to track memory usage, and the Network tab to analyse load times. The Console shows Unity debug messages and errors.
Remote Debugging
For mobile testing, we use remote debugging through Chrome DevTools. This allows debugging mobile WebGL builds from a desktop browser. We also use browserstack or similar services for testing on devices we do not have physically.
Analytics
We implement analytics to monitor real-world performance. We track metrics like load time, frame rate, memory usage, and error rates. This data helps identify issues that do not appear in testing but affect real users. We also track browser and device information to understand the user base.
Conclusion
Unity WebGL optimisation requires attention across multiple areas—build size, load time, memory management, runtime performance, and browser compatibility. The key is to profile extensively, optimise systematically, and test on real devices and browsers.
The results are worth the effort—WebGL builds that load quickly, run smoothly, and work reliably across browsers. This enables reaching users anywhere with a web browser without requiring app installation or platform-specific development.
