Search Unity

Expose shader culling option in Material Editor

Discussion in 'Shaders' started by ak_wf, Sep 12, 2013.

  1. ak_wf

    ak_wf

    Joined:
    Mar 30, 2013
    Posts:
    3
    I have a shader that needs to be used with both backface culling on and off in separate occasions. Is there a way to edit the parameter passed with the Cull keyword ('Back', 'Off') in the material editor window for the shader?

    Code (csharp):
    1.  
    2. Pass {    
    3.  
    4.          //Need to modify the following prameter(s?) through the material editor:
    5.          Cull Off
    6.          ZWrite On
    7.          Blend SrcAlpha One
    8.          ZTest LEqual
    9.          ColorMask RGBA
    10.        
    11.          CGPROGRAM
    12.                   #pragma vertex vert
    13.                   #pragma fragment frag
    14.  
    15.                   #include "UnityCG.cginc"
    16.  
    17.                   //shader code
    18.                   //custom material editors area of effect?
    19.  
    20.          ENDCG
    21.  
    22. }
    23.  
    I tried experimenting with custom material editors but it looks like they can only be used within the #CGPROGRAM #ENDCG block and the Cull option is defined outside of it.

    PS: Creating a different shader just for this feature is not an option :/
     
  2. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    I'm curious to know why this is the case.
     
  3. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Indeed. You can just switch the shader on the Material using a custom Material inspector.
     
  4. ak_wf

    ak_wf

    Joined:
    Mar 30, 2013
    Posts:
    3
    We currently have a roster of shaders previously created and they are all waiting in the pipeline to be optimized, so we don't want to increase the backlog by adding more shaders as of now. I know, it's a lame, political excuse but I can push for adding a new shader if that is the best way of doing it.

    If all I really need to do is change the culling option in the shader and nothing else, is adding a new shader the most elegant option? Also, How much does adding new shaders affect the performance of the game?
     
  5. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Short of actually duplicating/flipping the geometry, that's about the only thing you can do.

    But once you've optimised the shader you want to be double sided, can't you just copy/paste it and then edit the line that says Cull?

    Or you could potentially set up some LOD levels for subshaders and toggle the shader LOD level, assuming you're not using that for anything else? (Personally, I'd just duplicate the geometry, or add a new shader).
     
  6. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    A shader is just a text asset. Set up an Editor script to duplicate the old ones, and just change the single line of source code.
    As it stands.
    No.
     
  7. ak_wf

    ak_wf

    Joined:
    Mar 30, 2013
    Posts:
    3
    I did think of this earlier, but we already have a lot of assets in game using the shader with Cull Off and we don't have enough time to go back and fix those.

    Anyhow, new shader it is, thanks for clearing things out all!
     
  8. glitchers

    glitchers

    Joined:
    Apr 29, 2014
    Posts:
    64
    Came across this post when searching for a solution. Here is the solution for others. Add the cull to your properties and change your cull line to use the variable.

    This same method works for blend modes, z-write etc.

    Code (CSharp):
    1.  
    2.     Shader "Example/CullControl" {
    3.         Properties
    4.         {
    5.             [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0
    6.         }
    7.         SubShader
    8.         {
    9.             Cull [_Cull]
    10.         }
    11.     }
    12.  
     
  9. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    How much I hate when you want to do something and everybody keeps telling you "why would you want to do that?" instead of helping... :mad::mad::mad:
    THANK YOU VERY VERY MUCH!

    BTW, for Z-Test, use the CompareFunction enum
     
    Last edited: Jul 23, 2020
    amisner2k, Thygrrr and Lobolopez like this.
  10. cmbliu

    cmbliu

    Joined:
    Jun 4, 2017
    Posts:
    14
    This is exactly the solution I am looking for, Thank you so very much, glitchers. You are the real Hero!!!
     
  11. ASGS_DumbFox

    ASGS_DumbFox

    Joined:
    Sep 23, 2017
    Posts:
    22
    @glitchers Thanks! You saved me a lot of time and energy on this. I found the API info on CullMode but there is no explanation on how to use it. (https://docs.unity3d.com/ScriptReference/Rendering.CullMode.html)

    For anyone else looking into this, you can also change the Cull mode at runtime.

    To demonstrate I used the example above to add a parameter to the shader and then in script created a togglable bool that changes between backface culling and double-sided.

    Code (CSharp):
    1.     public void SetShaderBackfaces(bool isBackfaced)
    2.     {
    3.         if (isBackfaced)
    4.         {
    5.             if (renderers != null)
    6.             {
    7.                 foreach (Renderer rend in renderers)
    8.                 {
    9.                     rend.material.SetFloat("_Cull", 2);
    10.                 }
    11.             }
    12.         }
    13.         else
    14.         {
    15.             if (renderers != null)
    16.             {
    17.                 foreach (Renderer rend in renderers)
    18.                 {
    19.                     rend.material.SetFloat("_Cull", 0);
    20.                 }
    21.             }
    22.         }
    23.     }
    The important line to note is this:
    Code (CSharp):
    1. rend.material.SetFloat("_Cull", 0);
    The other bits just make sure that I have a referenced renderer before I start making changes.
     
    amisner2k and atomicjoe like this.
  12. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    Oh I didn't know you could change that on the fly!
    I guess it should work with the ztest compare too... mmm...
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Any render state you can set with a material property (which is basically all of them) you can change at runtime.
    https://github.com/supyrb/ConfigurableShaders

    The only caveat is you actually have to change the material's properties from the inspector or a custom script. You cannot modify them with a material property block or Unity animation (which internally uses a material property block).
     
  14. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    Man... I wish I had seen this a few months ago! It would have saved me so much time when I was doing my ubershader... I had to learn it the hard way :rolleyes:
    All of this is kinda undocumented.
     
    amisner2k and ASGS_DumbFox like this.
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    It is actually documented. It's just Unity's Shader documentation is sprawling, disjointed, and often not terribly in depth for how much of it there is.
    https://docs.unity3d.com/Manual/SL-Properties.html
    https://docs.unity3d.com/ScriptReference/MaterialPropertyDrawer.html
    Of course, that second quote is, while not technically wrong, very confusing. You can only specify up to 7 name / value pairs in the shader, but you can use enums with any number of elements. It was extra confusing because the Unity documentation for some of the enums were either missing, or if they were included in the documentation were not in the order they are in the actual enum. That made trying to add enums with a limited number of options from the longer list required a lot of guess and check. That's luckily been fixed by now.
     
    ASGS_DumbFox likes this.
  16. amisner2k

    amisner2k

    Joined:
    Jan 9, 2017
    Posts:
    43
    Yeah for me, I found and read in the docs about being able to use the property names in square brackets but I was under the mistaken impression that I couldn't use a float value in place of the Cull values.

    I saw the valid values in the docs as: Off, Back, and Front.....and I didn't realize that internally those values are simply replaced with float values by the compiler (I should've known -- :rolleyes: ). Also, I didn't have the confidence that the numeric representations of Off, Back, and Front mapped exactly to the UnityEngine.Rendering.CullMode enum values.

    Either way, many thanks to @glitchers for the revelation.

    To tack on to @ASGS_DumbFox 's comment, I'll just point out that you can cast the enum value to an int when you go to set it on the material or material property block like so:

    Code (CSharp):
    1. materialPropertyBlock.SetInteger("_Cull", (int)UnityEngine.Rendering.CullMode.Front);
    Also remember good practice is to cache the shader property to an int and use that instead of a string. I created a ShaderProperty static class that sets these up in its static constructor like so:

    Code (CSharp):
    1. public static class ShaderProperty {
    2.     public static readonly int Color;
    3.     public static readonly int MainTex;
    4.     public static readonly int Smoothness;
    5.     public static readonly int Metallic;
    6.     public static readonly int EmissionTex;
    7.     public static readonly int EmissionTint;
    8.     public static readonly int EmissionIntensity;
    9.     public static readonly int Cull;
    10.  
    11.     static ShaderProperty() {
    12.         Color = Shader.PropertyToID("_Color");
    13.         MainTex = Shader.PropertyToID("_MainTex");
    14.         Smoothness = Shader.PropertyToID("_Smoothness");
    15.         Metallic = Shader.PropertyToID("_Metallic");
    16.         EmissionTex = Shader.PropertyToID("_EmissionTex");
    17.         EmissionTint = Shader.PropertyToID("_EmissionTint");
    18.         EmissionIntensity = Shader.PropertyToID("_EmissionIntensity");
    19.         Cull = Shader.PropertyToID("_Cull");
    20.     }
    21. }
    Now I can use those properties in code instead of the string values (also nice for readability and always knowing the correct property key instead of accidentally typing the incorrect key name as text, etc.

    Code (CSharp):
    1. using UnityEngine.Rendering;
    2. materialPropertyBlock.SetInteger(ShaderProperty.Cull, (int)CullMode.Front);
     
    AshwinMods likes this.
  17. ZoroMC

    ZoroMC

    Joined:
    Jul 22, 2014
    Posts:
    4
    Is Cull a property that can be modified through a material property block in code? I've tried setting a cull property through a material property block but it doesn't seem to be having an effect. Setting the cull property through code by accessing the Renderer's material's SetFloat works on the other hand.
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Nope.

    You can fake it using
    VFACE
    and
    discard
    to find out which side of the triangle you're looking at and not render it, though this is not as cheap as using
    Cull
    .
     
  19. ZoroMC

    ZoroMC

    Joined:
    Jul 22, 2014
    Posts:
    4
    Thanks for the reply. Is there a reason behind this? Or any docs I can read to learn more about which properties can/cannot be set by material property blocks?
     
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Anything inside of the
    CGPROGRAM
    or
    HLSLPROGRAM
    block of code can be modified by the property blocks. Anything outside of that cannot be.

    The reason why is property blocks only affect the data passed to GPU for the shader passes on the GPU, where as the stuff outside of those blocks, things like
    Cull
    or
    Blend
    , etc. are setting the render state for the draw call. There is a cost, both on the CPU and GPU, to render state changes, so material property blocks were explicitly designed to not affect those, presumably so the internal code knows it can apply them safely without needing to worry about that overhead.


    Though it should be noted that if you're using the URP or HDRP you shouldn't use property blocks as they've heavily optimized for unique materials in the SRP and property blocks are actually slower that directly modifying the materials.
     
    AshwinMods likes this.