Game Dev Cheat Sheet

Unity Shader Keywords

Why setting a texture from code does nothing, and the keyword that fixes it. Standard and URP keywords with the property each one gates.

Unity shaders gate whole blocks of their code behind keywords. When you drag a normal map into a material slot in the Inspector, Unity's material editor quietly enables the matching keyword for you. Setting the same texture from C# bypasses that editor entirely, so the property is assigned, the keyword stays off, and the shader never runs the code that reads it. Nothing errors and nothing appears in the console. This page lists the keywords, what each one gates, and the symptom you see when it is missing.

Select material
Inspector
Debug mode
Shader Keywords

The one-line version

A property is a value the shader reads. A keyword is a switch that decides whether the shader contains the code to read it. Setting the property without the keyword is writing to a variable nothing looks at.

Does nothing

material.SetTexture("_BumpMap", normalMap);

Works

material.SetTexture("_BumpMap", normalMap);
material.EnableKeyword("_NORMALMAP");

Keyword Reference

14 keywords across the Standard shader and URP Lit. Expand any row for the symptom and the full code.

Standard and URP

_NORMALMAP

Enables tangent-space normal mapping. The Standard shader compiles normal mapping into a separate variant, so the sampler is skipped entirely when the keyword is off.

Symptom when missing: You assign a normal map with SetTexture and the surface stays completely flat, with no lighting change at all.

Gates: _BumpMap, _BumpScale

material.SetTexture("_BumpMap", normalMap);
material.SetFloat("_BumpScale", 1f);
material.EnableKeyword("_NORMALMAP");
_EMISSION

Enables the emission pass. Also required for emissive surfaces to contribute to baked global illumination, alongside the material's global illumination flags.

Symptom when missing: Emission colour is set to a bright HDR value but the object does not glow and bloom does not pick it up.

Gates: _EmissionColor, _EmissionMap

material.SetColor("_EmissionColor", Color.cyan * 3f);
material.EnableKeyword("_EMISSION");

// For emissive surfaces to affect baked GI:
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive;
_ALPHATEST_ON

Cutout rendering. Fragments below the _Cutoff threshold are discarded. Needs the render queue set to AlphaTest (2450) to sort correctly.

Symptom when missing: Foliage or chain-link textures render as opaque quads, with the transparent regions showing as solid colour.

Gates: _Cutoff

material.SetFloat("_Mode", 1f);              // Cutout
material.SetFloat("_Cutoff", 0.5f);
material.EnableKeyword("_ALPHATEST_ON");
material.renderQueue = 2450;

Standard shader only

_METALLICGLOSSMAP

Switches the metallic workflow from the scalar _Metallic and _Glossiness sliders to sampling a metallic/smoothness texture.

Symptom when missing: The metallic map is assigned but the material keeps using the slider values, so the whole surface has one uniform metalness.

Gates: _MetallicGlossMap

material.SetTexture("_MetallicGlossMap", metallicMap);
material.EnableKeyword("_METALLICGLOSSMAP");
_SPECGLOSSMAP

The specular workflow equivalent of _METALLICGLOSSMAP. Only relevant on Standard (Specular setup) materials.

Symptom when missing: A specular map is assigned but the shader keeps reading the flat _SpecColor value.

Gates: _SpecGlossMap

material.SetTexture("_SpecGlossMap", specMap);
material.EnableKeyword("_SPECGLOSSMAP");
_PARALLAXMAP

Enables height-based parallax offset. Requires a normal map as well, since parallax is calculated in tangent space.

Symptom when missing: The height map is assigned but the surface shows no depth or occlusion as the camera moves.

Gates: _ParallaxMap, _Parallax

material.SetTexture("_ParallaxMap", heightMap);
material.SetFloat("_Parallax", 0.02f);
material.EnableKeyword("_PARALLAXMAP");
_DETAIL_MULX2

Enables the second detail UV set, blended over the base maps at double intensity. Used for close-up surface detail such as fabric weave or skin pores.

Symptom when missing: Detail maps are assigned but nothing changes at any viewing distance.

Gates: _DetailAlbedoMap, _DetailNormalMap, _DetailMask

material.SetTexture("_DetailAlbedoMap", detailAlbedo);
material.SetTexture("_DetailNormalMap", detailNormal);
material.EnableKeyword("_DETAIL_MULX2");
_ALPHABLEND_ON

Fade mode. Alpha fades the whole surface including its specular highlights, so the object disappears completely at zero alpha.

Symptom when missing: Lowering the albedo alpha has no effect and the object stays fully opaque.

Gates: Fade rendering mode

material.SetFloat("_Mode", 2f);              // Fade
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
_ALPHAPREMULTIPLY_ON

Transparent mode. Keeps specular highlights and reflections at full strength as alpha drops, which is what you want for glass and water.

Symptom when missing: Glass fades out entirely instead of keeping its highlights, because Fade was used where Transparent was needed.

Gates: Transparent rendering mode

material.SetFloat("_Mode", 3f);              // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;

URP Lit only

_METALLICSPECGLOSSMAP

URP's combined replacement for the two Standard map keywords. URP Lit picks metallic or specular based on _SPECULAR_SETUP.

Symptom when missing: A URP Lit material samples the metallic slider instead of the assigned map.

Gates: _MetallicGlossMap or _SpecGlossMap

material.SetTexture("_MetallicGlossMap", metallicMap);
material.EnableKeyword("_METALLICSPECGLOSSMAP");
_OCCLUSIONMAP

Enables ambient occlusion map sampling on URP Lit. Standard samples the occlusion map without a dedicated keyword.

Symptom when missing: The occlusion map is assigned on a URP material but ambient light is not darkened in crevices.

Gates: _OcclusionMap, _OcclusionStrength

material.SetTexture("_OcclusionMap", aoMap);
material.SetFloat("_OcclusionStrength", 1f);
material.EnableKeyword("_OCCLUSIONMAP");
_SPECULAR_SETUP

Switches URP Lit from the metallic workflow to the specular workflow. Off by default, so URP materials are metallic unless told otherwise.

Symptom when missing: _SpecColor is set on a URP material and ignored, because the material is still in metallic mode.

Gates: Specular vs metallic workflow

material.SetFloat("_WorkflowMode", 0f);      // 0 = Specular, 1 = Metallic
material.EnableKeyword("_SPECULAR_SETUP");
_RECEIVE_SHADOWS_OFF

Inverted keyword: enabling it stops the surface receiving shadows. Useful for unlit-looking props and some stylised work.

Symptom when missing: Toggling a 'receive shadows' bool in code appears backwards, because the keyword expresses the negative case.

Gates: Shadow receiving

// Enabling the keyword DISABLES shadow receiving.
material.EnableKeyword("_RECEIVE_SHADOWS_OFF");   // stops receiving
material.DisableKeyword("_RECEIVE_SHADOWS_OFF");  // resumes receiving
_SURFACE_TYPE_TRANSPARENT

Marks a URP Lit material as transparent. Set alongside the blend modes, ZWrite and render queue, exactly as on Standard.

Symptom when missing: A URP material stays opaque after setting alpha, because the surface type was never switched.

Gates: Transparent surface type

material.SetFloat("_Surface", 1f);           // 0 = Opaque, 1 = Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
material.renderQueue = 3000;

Why EnableKeyword works in the Editor but not in a build

This is the second most common shader keyword problem, and it only appears after you ship. Keywords declared with shader_feature are stripped at build time unless a material included in the build already uses that variant. In the Editor every variant is compiled on demand, so your code works. In a player the variant was never compiled, so the call silently does nothing.

Stripped unless used

#pragma shader_feature _NORMALMAP

Always compiled

#pragma multi_compile _NORMALMAP

Three fixes, in the order most projects should try them:

  1. Add a Shader Variant Collection containing the variant and set it to preload. This keeps build size controlled and is the usual answer.
  2. Keep one material in the build that already uses the combination, even if it is only referenced by a prefab you never spawn.
  3. Change the shader declaration to multi_compile. Always works, but compiles every variant and grows build size and load time quickly, because variants multiply.

Checking and scoping keywords

Read the current state

if (!material.IsKeywordEnabled("_EMISSION"))
{
    material.EnableKeyword("_EMISSION");
}

Avoid string lookups in hot code (Unity 2021.2+)

LocalKeyword resolves the name against the shader once, so per-frame toggles do not pay for a string comparison every call.

using UnityEngine.Rendering;

private LocalKeyword emissionKeyword;

void Awake()
{
    emissionKeyword = new LocalKeyword(material.shader, "_EMISSION");
}

void SetGlow(bool on)
{
    material.SetKeyword(emissionKeyword, on);
}

Per-material versus project-wide

Material.EnableKeyword affects one material. Shader.EnableKeyword sets a global keyword affecting every shader at once, which suits quality or debug modes but is easy to leave switched on.

material.EnableKeyword("_EMISSION");   // this material only
Shader.EnableKeyword("MY_DEBUG_MODE");  // every shader in the project

Looking for the property names?

This page covers the switches. For the values themselves, including every Standard, URP and HDRP property name with get and set code, see the Unity shader properties reference. The two pages are meant to be used together: find the property there, check here whether it needs a keyword.

Frequently asked questions

Why does material.SetTexture("_BumpMap", tex) do nothing?
Because the _NORMALMAP keyword is not enabled. The Standard shader compiles its normal mapping code into a separate variant, and that variant only runs when the keyword is on. The Inspector's material editor enables it automatically when you drag a texture into the slot, but setting the texture from code bypasses that editor entirely. Call material.EnableKeyword("_NORMALMAP") after SetTexture and the map appears.
What is the difference between a shader property and a shader keyword?
A property is a value the shader reads, such as a colour, float, or texture, and you set it with SetColor, SetFloat, or SetTexture. A keyword switches an entire branch of the shader on or off at compile time, and you toggle it with EnableKeyword and DisableKeyword. Many properties are inert unless their keyword is also enabled, which is the source of most confusion.
Why does EnableKeyword work in the Editor but not in a build?
Keywords declared with shader_feature are stripped at build time unless some material in the build already uses that variant. In the Editor every variant is available, so it works; in a player the variant was never compiled. Fix it by adding a Shader Variant Collection that includes the variant and preloading it, by keeping one material in the build that uses the combination, or by changing the shader declaration from shader_feature to multi_compile, which always keeps every variant at the cost of build size.
How do I check whether a keyword is currently enabled?
Use material.IsKeywordEnabled("_EMISSION"), which returns a bool. In Unity 2021.2 and later you can also use the LocalKeyword and GlobalKeyword structs, which validate the keyword name against the shader once instead of doing a string lookup on every call, and are the better choice in code that runs every frame.
What is the difference between Material.EnableKeyword and Shader.EnableKeyword?
Material.EnableKeyword affects that one material instance. Shader.EnableKeyword sets a global keyword that applies to every shader in the project at once, which suits project-wide toggles such as a quality or debug mode. Use the material version for per-object variation; a global keyword changed at runtime affects everything and is easy to leave switched on by accident.

Last updated: