SubShaders for different platforms

Hello,

I have just started learning about HLSL and ShaderLabs and I watched this crash course:

Around at 17:18 to 18:16, the presenter mentions that multiple Sub Shaders can be used for different platforms with different shading techniques. How would I do that exactly?

I’ve made a shader that uses URP’s Lighting.HLSL’s PBR lighting and I would like to use the same shader for low end devices to use Lighting.HLSL’s Phong lighting solution.
I think as the presenter mentions in the video, creating another Sub Shader would be a better choice but I’m not sure how to switch between them.

Can someone point me to the right direction please?

They’re referring to this:

and/or this

The general idea is Unity will use the first SubShader with passes that can be used on the current platform. Older Unity shaders used Shader LOD, and the Standard shader uses #pragma target 3.0 and #pragma target 2.0 in the passes for each of the two SubShaders.

Shader "Some Shader"
{
  SubShader
  {
    Pass {
      CGPROGRAM
      #pragma target 3.0
      // rest of the stuff
      ENDCG
    }
  }
  SubShader
  {
    Pass {
      CGPROGRAM
      #pragma target 2.0
      // rest of the stuff
      ENDCG
    }
  }
}

However #pragma target 3.0 is basically everything these days, there are almost no #pragma target 2.0 devices being sold to consumers, and very few still in use. The vast majority of modern mobile devices sold today are covered even with #pragma target 4.5, so it’s not super useful. Unity doesn’t even support desktop GPUs that are below 5.0 these days, so there’s not really a useful way to use those as a check for desktop GPU performance tiers.

The more modern way of doing this is using preprocessor macros.

That lets you do custom code for specific platforms with far more control than the SubShader setup.

1 Like

@bgolus Thanks so much for the quick response!

So the way i would use it would be something along the lines of this:?

Shader "Custom/MyShader"
{
     SubShader//for high-end devices
     {
        #pragma SHADER_API_D3D11
        #pragma SHADER_API_PS4
        #pragma SHADER_API_XBOXONE

       HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
SubShader//for low-end mobile devices
     {
        #pragma SHADER_API_MOBILE

       HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
}

Nope. Like this:

CGPROGRAM

half4 frag(INPUT i) : SV_Target
{
  #if defined(SHADER_API_D3D11) || defined(SHADER_API_PS4) || defined(SHADER_API_XBOXONE)
  // high end code
  #else if defined(SHADER_API_MOBILE)
  // mobile code
  #end
}

Though you can do something like that using #pragma only_renderers or #pragma exclude_renderers.

@bgolus Aaah i see.
So just so that i’ve understood this clearly it can be done two ways.
I apologize if my methods are completely wrong, I just want to be sure to understand how this works before I go back into my project and find out I’ve completely misunderstood this.

  1. Using "if defined(“Target Platform”). I’m guessing this can be used in either individual Passes, Sub Shaders or even the fragment shader/function itself.
Shader "Custom/MyShader"
{
     SubShader//for high-end devices
     {
      #if defined(SHADER_API_D3D11) || defined(SHADER_API_PS4) || defined(SHADER_API_XBOXONE)
HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
SubShader//for low-end mobile devices
     {
        #if defined (SHADER_API_MOBILE)
       HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
}

or

Shader "Custom/MyShader"
{
     SubShader//for high-end devices
     {
   
       HLSL
       half4 frag(INPUT i) : SV_Target
{
 #if defined(SHADER_API_D3D11) || defined(SHADER_API_PS4) || defined(SHADER_API_XBOXONE)
 // high end code
 #else if defined(SHADER_API_MOBILE)
// mobile code
}
       ENDHLSL
     }

}
  1. Using “pragma only_renderers d3d11” / “#pragma exclude_renderers” in individual Passes, Sub Shades or even fragment shaders itself.
Shader "Custom/MyShader"
{
     SubShader//for high-end devices
     {
     #pragma only_renderers d3d11

HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
SubShader//for low-end mobile devices
     {
        #pragma only_renderers glcore

       HLSL
       /----HLSL CODE HERE-----/
       ENDHLSL
     }
}

or

Shader "Custom/MyShader"
{
     SubShader//for high-end devices
     {
    

       HLSL
       Pass//high-end
{
 #pragma only_renderers d3d11
 // high end code

}
 Pass//low -end
{
 #pragma only_renderers glcore
 // high end code

}

       ENDHLSL
     }

}

You should not use passes that have different #pragma requirements within a single SubShader.

You can not use #if or #pragma outside of an HLSLPROGRAM ENDHLSL or CGPROGRAM ENDCG block.

You can use both #pragma and #if in a single pass for further control, but you should try to make sure all possible code paths are still valid with or without the #if conditions.

So in your above options for 1, only the second example is valid. In the options for 2 only the first example is valid*.

  • Unless you’re manually rendering specific materials passes from code and know which pass is valid for the current platform, like for post processing or via manual DrawMesh commands.
Shader "Valid Options"
{
    Properties {
        _Color ("Color", Color) = (1,1,1,1) // color
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma fragment frag
            #pragma vertex vert

            #pragma target 3.0

            // exclude low end android gles 2.0 devices
            #pragma exclude_renderers gles

            struct appdata {
                float4 vertex : POSITION;
            };

            half4 _Color;

            float4 vert (appdata v) : SV_POSITION
            {
                return UnityObjectToClipPos(v.vertex);
            }

            half4 frag () : SV_Target
            {
                half4 color = _Color;
                #if SHADER_TARGET > 30
                // if we're a higher end gpu better than just "target 3.0", do expensive thing
                color.rgb *= sqrt(color.a); // nonsense
                #else // assume SHADER_TARGET == 30
                // we're on a lower end gpu, do cheap thing
                color.rgb *= 0.5; // nonsense
                #endif

                return color;
            }
            ENDCG
        }
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma fragment frag
            #pragma vertex vert

            // only low end devices
            #pragma target 2.0

            struct appdata {
                float4 vertex : POSITION;
            };

            half4 _Color;

            float4 vert (appdata v) : SV_POSITION
            {
                return UnityObjectToClipPos(v.vertex);
            }

            half4 frag () : SV_Target
            {
                // do cheapest possible thing
                return _Color;
            }
            ENDCG
        }
    }
}