Quick answer: Pink materials in a build mean the shader is missing at runtime. Unity's build pipeline strips shader variants it considers unused to reduce build size.
Here is how to fix Unity URP shader not rendering build. Your custom URP shader looks perfect in the editor — lighting reacts correctly, the material properties all work, and the Scene view renders exactly what you expect. Then you build the project and every object using that shader turns pink. The shader is gone, stripped out of the build by Unity’s shader compilation pipeline. This is one of the most frustrating URP issues because there is zero indication in the editor that anything is wrong. Here is how to track it down and fix it permanently.
The Symptom
You have written a custom URP shader — either in HLSL as a hand-coded .shader file or assembled in Shader Graph — and it renders correctly in the Unity editor. Play mode looks fine. The Scene view looks fine. Materials using the shader display the expected properties in the Inspector.
Then you create a build. On your target platform (Windows, Android, iOS, WebGL, or console), some or all objects using that shader appear as solid magenta/pink. This is Unity’s fallback color for materials whose shader could not be found or compiled. In the Player log, you may see messages like Shader 'Custom/MyURPShader' is not found or Compiled shader variant not found, but these logs are easy to miss on device.
The problem may be intermittent or partial. Sometimes only certain features of the shader are broken — for example, the base pass renders but the shadow caster pass is missing, resulting in objects that render but cast no shadows. Or the shader works on one platform but fails on another. This inconsistency makes the issue especially difficult to diagnose without understanding what the build pipeline is doing to your shader behind the scenes.
What Causes This
1. Shader variant stripping. Unity’s build pipeline analyzes which shader variants are actually used by materials in your scenes and strips everything else. This is normally a good thing — a complex shader can have thousands or millions of keyword permutations, and including all of them would balloon your build size. But the heuristic is imperfect. If a material that uses your shader is only loaded at runtime (from an AssetBundle, Addressables, or instantiated via script), the build pipeline does not see it during the analysis phase. Those variants get stripped.
The shader_feature pragma is the primary culprit. Unlike multi_compile, which always includes all keyword variants, shader_feature only includes variants for keywords that are enabled on materials the build pipeline can find. If you declared a keyword with #pragma shader_feature _EMISSION and the only material that enables _EMISSION is loaded from an AssetBundle, the emission variant is gone from the build.
2. Shader not referenced in any scene or Resources folder. If your shader is only referenced by materials that live in AssetBundles, Addressables, or are created entirely at runtime via new Material(Shader.Find("Custom/MyShader")), Unity has no way to know the shader is needed. It is not included in the build at all — not even the base variant. In the editor, Shader.Find can locate any shader in the project, but in a build, it can only find shaders that were explicitly included.
3. Missing Renderer Features in the URP asset. Some custom shaders depend on specific URP Renderer Features to function. For example, a shader that reads from _CameraNormalsTexture requires the DepthNormals pass, which is only available if the Renderer asset has the appropriate feature configured. In the editor, Unity may generate these textures opportunistically, but in a build with a stripped-down Renderer asset, the pass is never executed and the shader reads a null or incorrect texture.
4. Platform-specific shader compilation failures. Your shader might compile successfully on the editor’s graphics API (usually DirectX 11 or Metal) but fail on the target platform. Mobile GPUs have stricter limits on instruction counts, texture samplers, varying interpolators, and half-precision math. If a variant fails to compile for a specific graphics API, Unity silently excludes it from the build. The editor never attempts compilation for that API unless you explicitly switch to it.
The Fix
Step 1: Add the shader to Always Included Shaders. This is the fastest and most reliable fix. Go to Edit > Project Settings > Graphics and find the Always Included Shaders list. Add your custom shader here. This forces Unity to include the shader and all its multi_compile variants in every build, regardless of whether any material in a scene references it.
// Editor script to programmatically add a shader to Always Included Shaders
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
public static class ShaderIncludeHelper
{
[MenuItem("Tools/Ensure Custom Shaders Included")]
public static void EnsureShadersIncluded()
{
var graphicsSettings = AssetDatabase.LoadAssetAtPath<GraphicsSettings>(
"ProjectSettings/GraphicsSettings.asset");
var so = new SerializedObject(graphicsSettings);
var arrayProp = so.FindProperty("m_AlwaysIncludedShaders");
string[] shadersToInclude = new string[]
{
"Custom/MyURPLitShader",
"Custom/MyURPUnlitShader",
};
foreach (var shaderName in shadersToInclude)
{
var shader = Shader.Find(shaderName);
if (shader == null)
{
Debug.LogWarning($"Shader '{shaderName}' not found in project.");
continue;
}
bool alreadyIncluded = false;
for (int i = 0; i < arrayProp.arraySize; i++)
{
if (arrayProp.GetArrayElementAtIndex(i).objectReferenceValue == shader)
{
alreadyIncluded = true;
break;
}
}
if (!alreadyIncluded)
{
arrayProp.arraySize++;
arrayProp.GetArrayElementAtIndex(arrayProp.arraySize - 1)
.objectReferenceValue = shader;
Debug.Log($"Added '{shaderName}' to Always Included Shaders.");
}
}
so.ApplyModifiedProperties();
}
}
Be aware that adding a shader to Always Included Shaders includes all of its multi_compile variants but still strips shader_feature variants that are not referenced by any material. This brings us to the next step.
Step 2: Audit your keyword declarations. Open your shader file and find every #pragma shader_feature and #pragma multi_compile line. Any keyword that must be available regardless of which materials are in your build scenes should use multi_compile. Keywords that are only used by materials baked into scenes can safely remain as shader_feature.
// In your .shader file, change this:
#pragma shader_feature _EMISSION
#pragma shader_feature _NORMALMAP
#pragma shader_feature _SPECULAR_SETUP
// To this, for keywords needed at runtime:
#pragma multi_compile _ _EMISSION
#pragma multi_compile _ _NORMALMAP
#pragma shader_feature_local _SPECULAR_SETUP // local if only per-material
// Or use shader_feature_local to reduce global keyword pressure
// Unity has a limit of 384 global keywords (256 in older versions)
// Local keywords do not count toward this limit
The tradeoff is build size. Every multi_compile keyword doubles the number of variants (roughly). Three multi_compile keywords with two states each produce 8 variants. Ten produce 1024. Use multi_compile surgically — only for keywords that genuinely need to be available without a material reference at build time.
Step 3: Verify your URP Renderer asset configuration. Open your URP Renderer asset (usually found at Assets/Settings/UniversalRenderer.asset or similar). Check the Renderer Features list. If your shader reads from camera textures like _CameraDepthTexture, _CameraNormalsTexture, or _CameraOpaqueTexture, the corresponding options must be enabled in the URP Pipeline Asset.
// Runtime check for required URP pipeline features
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class URPFeatureValidator : MonoBehaviour
{
void Start()
{
var urpAsset = UniversalRenderPipeline.asset;
if (urpAsset == null)
{
Debug.LogError("No URP Pipeline Asset assigned in Graphics Settings.");
return;
}
Debug.Log($"Depth Texture: {urpAsset.supportsCameraDepthTexture}");
Debug.Log($"Opaque Texture: {urpAsset.supportsCameraOpaqueTexture}");
// Check if our shader can find itself
var shader = Shader.Find("Custom/MyURPShader");
if (shader == null)
Debug.LogError("Custom/MyURPShader not found in build. Add it to Always Included Shaders.");
else
Debug.Log($"Shader found. Pass count: {shader.passCount}");
}
}
Also check the Filtering section of the URP Renderer. If Opaque Layer Mask or Transparent Layer Mask does not include the layer your objects are on, they will not be rendered by that Renderer — a separate issue from shader stripping, but one that produces the same visual result of objects being invisible.
For Shader Graph shaders specifically, open the graph, click on the gear icon on the master node, and verify the Target is set to Universal. If the target is accidentally set to Built-in or HDRP, the shader will compile for the wrong pipeline and produce pink materials in a URP build.
Related Issues
If your shader renders but with incorrect lighting or missing shadows, the issue may be that the ShadowCaster pass was stripped. URP shaders need explicit LightMode tags on each pass — UniversalForward, ShadowCaster, DepthOnly, and DepthNormals. If any of these tags are missing or misspelled, URP does not recognize the pass and may strip it or skip it during rendering.
For shaders that work on PC but fail on mobile, check the Editor.log after a build for shader compilation errors. Mobile GPUs (OpenGL ES 3.0, Vulkan on Android, Metal on iOS) have stricter limits on texture samplers — typically 16 per fragment shader. If your shader samples more textures than the hardware supports, the variant fails to compile and is silently excluded.
If you are using Addressables or AssetBundles and shaders keep getting stripped despite being in Always Included Shaders, verify that the Addressables build and the player build are using the same URP Pipeline Asset. Different pipeline assets can trigger different stripping decisions, causing shader variants to be present in the player but missing from the Addressable bundles, or vice versa.
Pink means stripped. Check Always Included Shaders first.