How to Build a Reliable Shader Variant Collection and Reduce Unity Build-Time Compilation?
Shader Variant management is one of the least predictable performance risks in large Unity projects. As rendering features scale, keyword combinations grow exponentially, leading to longer build times, unstable runtime behavior, and inconsistent AssetBundle outputs.
In production pipelines such as those supported by GameOptim and its tooling suite (including Gears and GOT Online), shader diagnostics often become a key bottleneck category because issues are rarely visible until late-stage integration or device deployment.
This article formalizes a complete approach: from variant understanding, collection strategy, dependency reconstruction, to compile-time stripping optimization.
About GameOptim GameOptim helps Unity developers identify memory issues, rendering bottlenecks, and performance regressions through automated profiling and cloud based performance analysis. Explore more: 🌐 Website: www.gameoptim.com https://www.gameoptim.com/?fopt=blog 📘 Blog: www.gameoptim.com/blog/ https://www.gameoptim.com/blog/ 💼 LinkedIn: www.linkedin.com/company/gameoptim/ https://www.linkedin.com/company/gameoptim/ 🎥 YouTube: GO.PerformanceLab https://www.youtube.com/@GO.PerformanceLab 💬 Discord: GameOptim https://discord.gg/4Jh6hj9gRw ⭐ GitHub: GameOptim https://github.com/GameOptim/unity mobile performance guide 💻 Dev: GameOptim https://dev.to/gameoptim Summary Shader Variant explosion is caused by combinatorial keyword systems and fragmented asset packaging. Unity’s ShaderVariantCollection only addresses runtime warmup, not build time correctness. A robust solution requires: Deterministic runtime variant collection Per shader decomposition of global collections Dependency reconstruction for UsePass based shaders Compile time filtering using IPreprocessShaders Conclusion: Build time shader stability requires both collection accuracy and compiler level control. Core Concepts 1. What is a Shader Variant From the official docs: In Unity, many shaders internally have multiple "variants", to account for different light modes, lightmaps, shadows and so on. These variants are indentified by a shader pass type, and a set of shader keywords. A shader is not only GPU code, but a combination of render states, property definitions, and multiple pipeline paths. In Unity, variant generation is driven by directives such as: These mechanisms allow a single shader skeleton to expand into many runtime behaviors. However, they also introduce exponential growth in variant count, which must be actively controlled in production systems like those analyzed in Gears. 2. Why Collect Shader Variants At runtime startup, Unity typically preloads shaders to avoid compilation spikes. APIs like compile all variants upfront. However, as rendering complexity increases, this becomes inefficient and leads to long startup times. Unity introduced to define a subset of required variants . Key documentation insight: This is used for shader preloading "warmup" , so that a game can make sure "actually required" shader variants are loaded at startup... This means: Collection ≠ full variant space It only represents observed runtime usage It does not control build time compilation 3. Additional Notes AssetBundle Problem ShaderVariantCollection does not address: Build time compilation scope AssetBundle variant filtering Cross asset dependency inference In production builds, missing variants often appear due to AssetBundle separation. When shaders are isolated into independent bundles, Unity loses full dependency visibility across: Materials Scenes Lighting setups This leads to unpredictable rendering fallback behavior on devices, where missing variants cannot be directly diagnosed. Common solutions: 1 Bundle shaders with dependent materials 2 Scene wide Editor scanning to generate global variant sets Unity exposes this mechanism in Project Settings: 1 https://uwa ducument img.oss cn beijing.aliyuncs.com/GameOptim/Shader Variant/1.png Limitations: Method 1: misses lighting driven variant dependencies Method 2: produces monolithic collections, reducing bundle modularity Best Practices 4. My Approach 4.1 Classifying Rendering Assets Rendering assets are divided into: 1. Scenes 2. Dynamic runtime assets prefabs, characters, VFX 3. UI systems UI UGUI + multi compile is typically stable and low volume, so it is excluded from optimization scope. Focus areas: Scene rendering Dynamic asset rendering This classification aligns with real world profiling outputs in Gears, where shader cost is usually dominated by runtime rendered objects rather than UI. 4.2 Automated Shader Variant Collector Steps: 1. Collect all build included asset paths 2. Resolve dependency graph materials, prefabs 3. Create controlled lighting test scene 4. Call via reflection 5. Spawn camera and proxy geometry 6. Render material batches on controlled meshes 7. Traverse scenes and repeat rendering capture 8. Save collection via This pipeline is often paired with analysis workflows in GOT Online to verify whether collected variants match real device execution paths. 4.3 Is the Variant Collection Enough? No. A key limitation is based shaders: Some shaders do not exist in material graphs Internal shader dependencies are hidden Artists never directly reference them Example: In this architecture: ABC acts as a composition layer Internal shaders contain actual implementation Unity only collects visible usage paths Result: Hidden variants are not collected AssetBundle export becomes incomplete Therefore: Global collection must be decomposed per shader Dependency graph must be reconstructed Variant sets must be normalized at shader level 4.4 Variant Reconstruction Strategy Before processing: 1. Call → Extract keyword space 2. Read ShaderVariantCollection via reflection 3. Cache parsed keyword structures for reuse This enables deterministic reconstruction of shader level variant sets and dependency correction for UsePass chains. Pseudocode Original Logic Preserved 5. Compile Time Optimization Even with accurate collection, Unity still generates excess shader variants during build. This indicates a separation: ShaderVariantCollection → runtime warmup subset Build compiler → independent variant expansion stage To control compilation, Unity provides: This enables per variant filtering during compilation. Example baseline: Extended filtering logic: Key Takeaways Shader Variant explosion is structural, not accidental ShaderVariantCollection only solves runtime warmup, not build correctness AssetBundle separation breaks Unity’s dependency inference UsePass based shaders require explicit reconstruction Compile time stripping is required for scalable builds Production pipelines benefit from combining collection + compiler filtering In real world workflows using GameOptim systems and tools like GOT Online and Gears, shader optimization is typically validated through both build time metrics and device side GPU profiling. FAQ Why do missing variants only appear on devices? Because editor collection does not simulate full runtime lighting and dependency resolution. Why doesn’t ShaderVariantCollection reduce build time? It only affects warmup, not shader compilation. Why do variants explode exponentially? Because multi compile creates full combinatorial keyword space. How to detect hidden shader cost? Use GPU level profiling tools such as Gears and correlate results in GOT Online.