Quick answer: A Shader Graph that looks correct in the editor but renders pink, black, or with wrong colors in a built player is almost always a stripped variant problem. The editor compiles shaders on demand; the build process strips anything it cannot statically prove is referenced. Add the graph to Always Included Shaders, capture runtime keywords into a ShaderVariantCollection, warm it up at load, and switch toggle keywords from shader_feature to multi_compile.
Here is how to fix Unity Shader Graph not updating in a built player. You author a Shader Graph that looks great in Play Mode. You assign it to materials, you tune it for hours, you ship a build to your testers, and the screen comes back pink. Or worse — the shader renders, but it looks like the default state of every keyword regardless of what your code is setting. The Shader Graph asset itself is fine; the problem is that Unity’s build pipeline did not include the variant your runtime needs, and the shader silently falls back to whatever it can find.
The Symptom
You can recognize this bug in three flavors, and each one points at a slightly different root cause:
Solid pink (or magenta) materials. The shader is missing entirely from the build. Unity uses the pink “error” material whenever a material references a shader that did not ship. This is the most obvious form of the bug because pink is hard to miss, but it is also the easiest to fix — you just need to get the shader into the build.
Shader renders, but keywords look stuck. The shader is in the build, but the specific variant you toggled at runtime was stripped. The material renders with whatever default keyword combination shipped, ignoring the booleans and enums you set from script. This one is sneaky because the render is not obviously broken — it just looks like the wrong feature flag.
Custom Function nodes return zero or pink. A Custom Function node references an HLSL file that is fine in the editor but not included in the player. Often this is because the HLSL file lives outside an Assets folder, or it includes editor-only headers that do not exist at build time.
What Causes This
Aggressive variant stripping. Unity computes every shader variant by enumerating the cross product of every keyword. A Shader Graph with five Boolean keywords has 32 variants; with eight booleans you are at 256. To keep build sizes reasonable, the build pipeline strips variants it does not believe are reachable. If the only place a keyword is set is from script via Material.EnableKeyword, the static analyzer cannot detect that path and the variant gets cut.
shader_feature vs. multi_compile. Shader Graph defaults Boolean keywords to Shader Feature, which means “only ship variants that some material in the build statically uses.” If no material asset has the keyword enabled at build time, the “on” variant is stripped even if your script enables it later. Multi Compile forces every variant to ship.
Always Included Shaders is empty. If you load shaders by name (Shader.Find) or instantiate materials at runtime, the build process has no way to know those shaders are needed. Anything not assigned to a referenced material in a scene gets stripped.
Graphics tier mismatch. URP and HDRP compile different variants per quality tier. If your editor runs on Tier 3 and your build defaults to Tier 1, the variant you tested may not exist in the player at all.
The Fix
Step 1: Add the shader to Always Included Shaders. Open Edit > Project Settings > Graphics. Scroll to the Always Included Shaders list and drop your Shader Graph asset in. This guarantees the base variant ships even if no scene material references it. This is necessary for any shader you assign at runtime via script.
Step 2: Convert runtime keywords to multi_compile. Open the Shader Graph. In the Blackboard, select each Boolean keyword that your code toggles at runtime. In the inspector, change Definition from Shader Feature to Multi Compile. Save the graph. This forces every combination of that keyword to compile and ship.
// Toggling a Shader Graph keyword from script
using UnityEngine;
public class ShaderToggle : MonoBehaviour
{
[SerializeField] private Renderer targetRenderer;
[SerializeField] private string keywordName = "_USE_RIM";
public void SetRimEnabled(bool enabled)
{
Material mat = targetRenderer.material;
// Use the LocalKeyword API in 2021.2+ for stable behavior
LocalKeyword keyword = new LocalKeyword(mat.shader, keywordName);
mat.SetKeyword(keyword, enabled);
// In a build, this only works if _USE_RIM was declared
// as multi_compile in the Shader Graph blackboard.
}
}
Step 3: Build a ShaderVariantCollection. Open Project Settings > Graphics. Press Play in the editor and walk through every code path that toggles shader keywords — menu screens, gameplay states, quality settings. Then under Currently tracked click Save to asset. This produces a .shadervariants asset that captures the exact keyword combinations you used.
// Warming up shader variants during a loading screen
using UnityEngine;
using System.Collections;
public class ShaderWarmup : MonoBehaviour
{
[SerializeField] private ShaderVariantCollection variants;
public IEnumerator WarmUpAsync()
{
if (variants == null) yield break;
// WarmUp compiles every variant in the collection.
// Do this on a loading screen, not mid-gameplay.
if (!variants.IsWarmedUp())
{
variants.WarmUp();
Debug.Log($"Warmed up {variants.shaderCount} shaders, " +
$"{variants.variantCount} variants");
}
yield return null;
}
}
Custom Function Nodes
If your Shader Graph uses a Custom Function node pointing at an HLSL file, two things have to be true for the build to work. First, the HLSL file must live inside the Assets folder so it gets packaged. Second, the file must not include any editor-only headers like Packages/com.unity.shadergraph/Editor/... — those paths only exist in the editor and will fail to compile in the player.
A common trap is the IsBuildOnly branch in URP’s shader includes. If your custom function does #if UNITY_EDITOR around an entire function body, the player build will see an empty function and your shader will sample undefined values. Always provide a real implementation in the #else branch, even if it just returns zero.
Verifying the Fix
After making the changes above, do a clean build (delete the Library/ShaderCache folder) and check the player log on first launch. Unity prints a line like Compiled shader variants: N. Compare that count to your editor variant count — they should match. If the player count is much lower, you still have stripped variants somewhere.
You can also enable Log Shader Compilation in Project Settings > Graphics to print every variant compile to the player log. In a release build, the log will tell you exactly which variant your material is requesting and whether the runtime had it ready or had to fall back.
“Editor builds compile shaders on demand. Player builds compile only what they shipped. The gap between those two behaviors is where every shipped-with-pink-shaders bug lives.”
Related Issues
If your Shader Graph compiles fine but the preview shows a black square in the editor, see Shader Graph Preview Black Screen. If a Custom Function node fails to compile at all, check Custom Function Not Compiling for HLSL include path debugging.
Always Included Shaders + multi_compile + ShaderVariantCollection — the holy trinity of player builds that match the editor.