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.
14 keywords across the Standard shader and URP Lit. Expand any row for the symptom and the full code.
Standard and URP
_NORMALMAPgates _BumpMap, _BumpScale
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.
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_ONgates _Cutoff
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.
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:
Add a Shader Variant Collection containing the variant and set it to preload. This keeps build size controlled and is the usual answer.
Keep one material in the build that already uses the combination, even if it is only referenced by a prefab you never spawn.
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.
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.
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.