0
\$\begingroup\$

I've added a toggle to my shader:

[Toggle(ENABLE_COLOR_BLEND)] _EnableColorBlend ("Enable Color Blend", Int) = 0

In the Subshader:

#pragma shader_feature ENABLE_COLOR_BLEND

And in the fragment shader:

#ifdef ENABLE_COLOR_BLEND
    color = lerp(_DryColor, _WetColor, _ColorBlend);
#endif

And there it is in the inspector, and when I toggle it, color blending is turned on and the material is rendered as it should be. if I turn it off, it's off.

But if I try to set it like this in code:

Renderer.material.SetInt("_EnableColorBlend", 1);

The material is rendered like if EnableColorBlend is off.

In the inspector it's ticked, and as soon as I turn it off, it gets ticked again. (I set it in an Update) But for some reason, it's still rendered like color blending is off.

And if I remove the SetInt above, and set it by hand, everything works fine.

Why?

Idea: If I set it by hand, there is a smooth transition (don't know why) instead of an instant change in shade. Maybe by setting it constantly to 1, it always just starts the transition, but can't finish it?

EDIT: Just checked it with a guard, and it still behaves the same.

if (Renderer.material.GetInt("_EnableColorBlend") == 0) Renderer.material.SetInt("_EnableColorBlend", 1);
\$\endgroup\$
1
\$\begingroup\$

By setting up your shader toggle like this, you've forced it to be a compile-time constant.

When Unity loads your material, it selects or compiles the appropriate version of the shader for its toggled settings.

That includes evaluating all the preprocessor directives like #ifdef: the code wrapped in those blocks is either compiled into your shader program, or it is not. You don't get a section of "ghost code" that's there-but-skipped. If the #ifdef is not satisfied, the code is simply omitted from the compiled program and the GPU never sees it. That's the whole point, to take unneeded complexity out of the shader when you don't plan to use a particular feature.

So changing the toggle variable anytime after that moment is "closing the barn door after the horses have left" — the code is already there or not there, the preprocessor directives have done their job and are no longer present in your compiled shader program, so you have nothing in the compiled program on the GPU that reads your toggle and uses it to change behaviour.

You need to do one of three things instead:

  • Compile two materials, one with the toggle on, one off. When you want to swap the toggle, swap to the other material whose shader was compiled in the state you want.

    This lets you keep the benefit of having no dynamic branching inside the shader, but coordinating the other material properties might be more juggling.

  • Tell Unity that you're not just changing a variable, but changing a compiler directive, using material.EnableKeyword("_EnableColorBlend") or DisableKeyword("_EnableColorBlend").

    Rather than just changing a variable, this swaps out the whole shader program, for one compiled with the appropriate setting. Note that you'll need to have each variant present in the assets in your build (as in the point above) to ensure Unity has a matching variant to load when it looks for one.

  • Don't use preprocessor directives for something you want to change dynamically. Just use a regular if (toggleVariable > 0) { ... }

    This will be evaluated every time the shader executes, so you can change which path is taken from frame to frame without changing compiled shader programs, or even from one fragment to another. But correspondingly, it has a cost to do this check and potentially branch execution paths within a warp.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.