Shader LOD with #define

In my shader I have different quality levels, that enable certain features:

#if defined (QUALITY_FASTEST)
#elif defined(QUALITY_FAST)
	#define REFLECTIONS_ON
#elif defined(QUALITY_SIMPLE)
	#define REFLECTIONS_ON
	#define DEPTHMAP_ON
#elif defined(QUALITY_GOOD) 
	#define REFLECTIONS_ON
	#define DEPTHMAP_ON
	#define FRESNEL_ON
	#define CALCULATE_NORMALS_ON
	#define NORMALMAP_REFLECTIONS_ON
	#define PERPIXEL_SPECULARITY_ON
	#define DIFFUSE_LIGHTING_ON
#endif

Is it possible to create different shader LODs with the quality settings? I.e.:

SubShader {
    LOD 200
    Pass {
   		CGPROGRAM
    	#define QUALITY_FAST
    	#include "MyShaderBase.cging"
    	ENDCG
    }
}

SubShader {
    LOD 100
    Pass {
    	CGPROGRAM
    	#define QUALITY_FASTEST
    	#include "MyShaderBase.cging"
    	ENDCG
    }
}

So basically I want to toggle different parts of my shader, based on the LOD. Can something like this be achieved without the use of keywords?

Thanks!

Wondering the same… did you ever figure out the answer to this? :wink:

This is about the cleanest way you can achieve it:

CGINCLUDE
    #if defined (QUALITY_FASTEST)
    #elif defined(QUALITY_FAST)
        #define REFLECTIONS_ON
    #elif defined(QUALITY_SIMPLE)
        #define REFLECTIONS_ON
        #define DEPTHMAP_ON
    #elif defined(QUALITY_GOOD) 
        #define REFLECTIONS_ON
        #define DEPTHMAP_ON
        #define FRESNEL_ON
        #define CALCULATE_NORMALS_ON
        #define NORMALMAP_REFLECTIONS_ON
        #define PERPIXEL_SPECULARITY_ON
        #define DIFFUSE_LIGHTING_ON
    #endif
    
    // Code here...
ENDCG

SubShader {
    LOD 200
    Pass {
        CGPROGRAM
        #define QUALITY_FAST
        ENDCG
    }
}
 
SubShader {
    LOD 100
    Pass {
        CGPROGRAM
        #define QUALITY_FASTEST
        ENDCG
    }
}

Now you can control the quality by simply adjusting the LoD in editor.

I’m not entirely sure if this works or not, though… If the cginclude block is inserted in front of the define, the keyword won’t register. In that case, use what OP posted.

Looks good, will give that a try, thanks much!

has anyone ever tested this ?

my shader code always goes inside the subshader
and unity uses the first subshader it can use

so how can you use this set up to control your defines without also duplicating all shader code inside all subshader passes

i would assume you d have to use a
UsePass “Shader/Name”

ok when putting my code in the CGINCLUDE i can share code between subshaders however toggling the keywords doesnt work.

The example using CGINCLUDE won’t actually work, but the #include method will. It’s an order of operations issue. The order in which preprocessor #define and #if lines exist matter, and the #define needs to happen before the #if otherwise it isn’t yet defined. Unfortunately for this case CGINCLUDE is always added at the start of the shader. I’ve even tried putting the CGINCLUDE at the end of the shader and I don’t remember it working then either.

However using an #include file is also kind of the answer to your other question. You can put almost all of your shader code into the #include and just the quality #define and #pragma lines.

1 Like

how would i include the file ?

#include “MyShaderBase.cging”

do i need to provide a path to the cging file?

The #include for custom cginc files needs to be a relative path. The easiest way to do that is just put the .shader and .cginc file in the same folder so you don’t have to worry about it.

1 Like

seems unity is doing both btw, example from their terrain shader

they have CGINCLUDE

and in the cginclude they have a
#include “TerrainSplatmapCommon.cginc”

and then they have
CGPROGRAM
#pragma target 3.0
#pragma multi_compile __ _TERRAIN_NORMAL_MAP
ENDCG

from what i tried before i would assume this has the same “order of operations issue”, however it works.
Wondering what i am overlooking

...
    CGINCLUDE
        #pragma surface surf Lambert vertex:SplatmapVert finalcolor:SplatmapFinalColor finalprepass:SplatmapFinalPrepass finalgbuffer:SplatmapFinalGBuffer
        #pragma multi_compile_fog
        #include "TerrainSplatmapCommon.cginc"

        void surf(Input IN, inout SurfaceOutput o)
        {
            half4 splat_control;
            half weight;
            fixed4 mixedDiffuse;
            SplatmapMix(IN, splat_control, weight, mixedDiffuse, o.Normal);
            o.Albedo = mixedDiffuse.rgb;
            o.Alpha = weight;
        }
    ENDCG

    Category {
        Tags {
            "SplatCount" = "4"
            "Queue" = "Geometry-99"
            "RenderType" = "Opaque"
        }
        // TODO: Seems like "#pragma target 3.0 _TERRAIN_NORMAL_MAP" can't fallback correctly on less capable devices?
        // Use two sub-shaders to simulate different features for different targets and still fallback correctly.
        SubShader { // for sm3.0+ targets
            CGPROGRAM
                #pragma target 3.0
                #pragma multi_compile __ _TERRAIN_NORMAL_MAP
            ENDCG
        }
...