Search Unity

Preprocessor Directive for state of texture (and other properties?)

Discussion in 'Shaders' started by Thaina, Feb 16, 2020.

  1. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,163
    Sometimes I see a set of shaders that almost identical except there is a logic injected for normal mapping (or similar with any other kind of special mapping)

    Code (CSharp):
    1. // file 0
    2. Shader "0"
    3. {
    4.     Properties
    5.     {
    6.         /* Common properties */
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags
    12.         {
    13.             /* Common tags */
    14.         }
    15.  
    16.         Pass
    17.         {
    18. CGPROGRAM
    19.             /* Common program before */
    20.             /* Common program after */
    21. ENDCG
    22.         }
    23. }
    24.  
    25.  
    26. // file 1
    27. Shader "1"
    28. {
    29.     Properties
    30.     {
    31.         /* Common properties */
    32.         _NormalMapSampler ("Normal Map", 2D) = "" {}
    33.     }
    34.  
    35.     SubShader
    36.     {
    37.         Tags
    38.         {
    39.             /* Common tags */
    40.         }
    41.  
    42.         Pass
    43.         {
    44. CGPROGRAM
    45.             /* Common program before */
    46.             // Some normal map logic
    47.             /* Common program after */
    48. ENDCG
    49.         }
    50. }
    I wish that we could define
    multi_compile
    definition for texture instead. If the texture was set then it automatically use one branch, and then use another if not

    Something like this

    Code (CSharp):
    1.  
    2. // file both
    3. Shader "0"
    4. {
    5.     Properties
    6.     {
    7.         /* Common properties */
    8.         _NormalMapSampler ("Normal Map", 2D) = "" {}
    9.     }
    10.  
    11.     SubShader
    12.     {
    13.         Tags
    14.         {
    15.             /* Common tags */
    16.         }
    17.  
    18.         Pass
    19.         {
    20. CGPROGRAM
    21.             #pragma multi_compile_texture(_NormalMapSampler) // texture name
    22.             /* Common program before */
    23.         #ifdef texture(_NormalMapSampler) // will it possible to auto added #pragma ?
    24.             // Some normal map logic
    25.         #endif
    26.             /* Common program after */
    27. ENDCG
    28.         }
    29. }
    30.  
    Also, maybe, could it be allowed for any property ?

    Code (CSharp):
    1.  
    2. // file both
    3. Shader "0"
    4. {
    5.     Properties
    6.     {
    7.         /* Common properties */
    8.         _SpecularPower ("Specular Power", Float) = 0
    9.         _NormalMapSampler ("Normal Map", 2D) = "" {}
    10.     }
    11.  
    12.     SubShader
    13.     {
    14.         Tags
    15.         {
    16.             /* Common tags */
    17.         }
    18.  
    19.         Pass
    20.         {
    21. CGPROGRAM
    22.  
    23.             #pragma multi_compile_property_morethan(_SpecularPower,0) // split for _SpecularPower > 0 and _SpecularPower <= 0
    24.             #pragma multi_compile_texture(_NormalMapSampler) // texture name
    25.             /* Common program before */
    26.         #ifdef texture(_NormalMapSampler)
    27.             // Some normal map logic
    28.         #endif
    29.         #ifdef property_morethan(_SpecularPower,0)
    30.             // Some specular highlight logic
    31.         #endif
    32.             /* Common program after */
    33. ENDCG
    34.         }
    35. }
    36.  
    Is something like this already exist? If not then is it possible?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    There’s nothing built in that does this, but there are ways to add it with a little c#, or at least get close.

    The Standard shaders included with Unity use a custom material inspector to do this, and toggle a keyword on and off based on if a texture is set or not. Specifically for the normal map. You can look at how they do that by looking at the custom inspector code itself.
    https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/Editor/StandardShaderGUI.cs

    In the shader you use
    #pragma shader_feature MY_KEYWORD
    and
    #if defined(MY_KEYWORD)
    to tell Unity to compile different variants. If you want to skip using a custom material editor, you can add a dummy material property with a material property drawer like this:
    Code (csharp):
    1. [Toggle(MY_KEYWORD)] _MyKeywordProperty (“Toggle MY_KEYWORD”, Float) = 0.0
    If you’re feeling adventurous you could try writing your own custom material property drawer that toggles a keyword based on if a texture is set or not.
     
    Last edited: Feb 16, 2020
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    MaxMazu likes this.
  4. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,163
    Thank you. This is really great. I wish this feature should be officially supported
     
  5. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    Just a note in case you were planning to use this at runtime, these values will only be set on the materials if you assigned the textures in Unity Editor. These values will not get set for textures that you apply through code at runtime. So if you were planning to do some runtime creation of new materials or swapping of texture types then that would still be an issue (though could still be worked around)
     
    Thaina likes this.
  6. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,163
    @Invertex Yeah, that's also the reason I think this should be officially supported by the material system, not as an editor
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    I think @Invertex's comment might be a little misleading. You can absolutely set a texture and toggle the keyword at runtime, that's not a problem (as long as the appropriate shader variant is included in the build). You'd just need to do it manually with whatever script you're using to modify the material's properties to also set the keyword.

    It should also be noted that for modern desktop hardware, something like
    property_morethan(_SpecularPower,0)
    can be done with an
    if
    statement and have it be just as fast (and potentially faster) than when using an
    #if
    . That isn't the case for textures due to the fact that even if you don't have a texture assign, the shader still gets a default texture. Without one the GPU would crash. But you could still have a
    if (_UseTex != 0)
    to skip a texture. This is because on modern desktop GPUs they can do real dynamic branches and
    if
    statements that are checking material property values are nearly free.

    Now you could argue "but if it was automatic, then I could set a texture via a material property block and it'd toggle the keyword!" But you'd be wrong. Material property blocks explicitly cannot modify the render state or variant being used, so even if this was built in, setting the texture via a material property block probably wouldn't change the variant. But setting another property and using an
    if
    in the shader would work, which is the case where it could end up being faster.

    Granted, on mobile, you'll absolutely want to use a variant.
     
    Thaina likes this.
  8. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,163
    Thank you, that's new to me. I just think the material could did that but the fact that the block was differences is unexpected

    Still in conclusion, variant is safer bet