Setting static switches at packaging time to compile material sub-nodes on a per platform basis

Hey Epic,

We are planning to define our internal material authoring workflow. In doing so we’ve found one possible way of handling materials which is by creating one material function for each platform

target. We then use static switches to select the right material function given the platform target we want the material to be compiled for.

What we’d like to do is specify the static switches at packaging time (rather than authoring time on the material instance params). So, in this way, we can automate/streamline the process much better.

Do you think this approach can be feasible or is there another, much better way, of handling multiplatform material workflow? Plus, how other companies handle this?

It would be useful to be able to organize the master material with per platform material functions so we can compile just the sub-portion of shader code for a given platform excluding the others, resulting in a more maintainable material.

Last but not least: Another approach would be to have a master for each platform (rather than a master containing a material function for each platforrm selected via static switches).

Are we on the right path? What would you suggest? (we are pending toward the material functions approach)

Recap:

  • Per platforrm Material functions + static switches approach
  • A single master material for each platforrm

Looking forward to your reply,

Thanks in advance,

Fabio

Steps to Reproduce

Hello!

The solution that has worked for Fortnite, which ships on 10+ platforms, has been to have a set of materials for all platforms and use feature level and quality level switches inside the materials to control shading. We use Quality Switches down the Shading Path Switch outputs to direct things but also have some helper material functions that direct users (Tech Artists) a bit.

[Image Removed]

For example:

  1. Mobile Phones
    1. ShadingPath=Mobile
    2. Quality=LOW
  2. Console A
    1. ShadingPath=Mobile
    2. Quality=MEDIUM
  3. Console B
    1. ShadingPath=Deferred
    2. Quality=HIGH

In the example above, if you had multiple quality levels on mobile phones, you could have a Mobile Low and Mobile High, but differentiate from the Console B spec by using the Shading Path Switch node.

In our experience, going the per-platform material route or doing anything per-platform means you begin to splinter your tech, and that becomes pretty bad to maintain over time. Going the per-quality route limits permutations in general and can be mapped to platforms in the DeviceProfile.ini and Scalability.ini files.

You can provide additional platform overrides for quality levels in your platform engine .ini files like the Lyra project does for Android in AndroidEngine.ini

[ForwardShadingQuality_GLSL_ES3_1_ANDROID ShaderPlatformQualitySettings] QualityOverrides[0]=(bDiscardQualityDuringCook=False,bEnableOverride=True,bForceFullyRough=False,bForceNonMetal=False,bForceDisableLMDirectionality=False,bForceLQReflections=False,bForceDisablePreintegratedGF=False,bDisableMaterialNormalCalculation=False,MobileShadowQuality=PCF_3x3) QualityOverrides[1]=(bDiscardQualityDuringCook=False,bEnableOverride=True,bForceFullyRough=False,bForceNonMetal=False,bForceDisableLMDirectionality=False,bForceLQReflections=False,bForceDisablePreintegratedGF=False,bDisableMaterialNormalCalculation=False,MobileShadowQuality=PCF_3x3) QualityOverrides[2]=(bDiscardQualityDuringCook=False,bEnableOverride=True,bForceFullyRough=False,bForceNonMetal=False,bForceDisableLMDirectionality=False,bForceLQReflections=False,bForceDisablePreintegratedGF=False,bDisableMaterialNormalCalculation=False,MobileShadowQuality=PCF_3x3) QualityOverrides[3]=(bDiscardQualityDuringCook=False,bEnableOverride=False,bForceFullyRough=False,bForceNonMetal=False,bForceDisableLMDirectionality=False,bForceLQReflections=False,bForceDisablePreintegratedGF=False,bDisableMaterialNormalCalculation=False,MobileShadowQuality=PCF_3x3) Hope this helps!

Hi Alex,

and thanks for your reply.

Just few questions:

  • What happens if we choose the shading path in the project settings (like for example deferred) and we plug a node in the wrong pin in the Shading Path Switch node (e.g. forward while deferred is selected in the project settings)?
  • Is Forward+ the default shading path once we select forward shading as a shading path in the project settings?
  • What is the difference between mobile shading path in the node and Forward Shading path? I thought Forward was the standard shading path for mobile (I mean I can see Forward and Mobile). What happens if we select mobile? Will it default to Forward+ for flagship/mid tier phones?

Looking forward to your reply,

Thanks in advance,

Fabio

Hi! Apologies for the delay.

What happens if we choose the shading path in the project settings (like for example deferred) and we plug a node in the wrong pin in the Shading Path Switch node (e.g. forward while deferred is selected in the project settings)?

The “Default” input will be used if you leave the active shading path pin input disconnected. If you don’t provide an input for the “Default” you’ll get an error that looks like this:

[Image Removed]

Is Forward+ the default shading path once we select forward shading as a shading path in the project settings?

It depends on what you mean by default. The “Default” input on the Shading Path Switch node is only used when you don’t specify the input for the active shading path. If you enable forward rendering in your project settings but don’t hook up any input to the “Forward” input on the Shading Path Switch node, then the “Default” input will be used.

What is the difference between mobile shading path in the node and Forward Shading path? I thought Forward was the standard shading path for mobile (I mean I can see Forward and Mobile).

The Mobile input is used for any platform that doesn’t support shader model 5 or higher. It’s not very clear what the difference is between those inputs are, but looking at the code in UMaterialExpressionShadingPathSwitch::GetShadingPathToCompile() you see this logic (with added comments)

`ERHIShadingPath::Type UMaterialExpressionShadingPathSwitch::GetShadingPathToCompile(EShaderPlatform ShaderPlatform, ERHIFeatureLevel::Type FeatureLevel)
{
// default to the deferred input
ERHIShadingPath::Type ShadingPathToCompile = ERHIShadingPath::Deferred;
int32 ShadingPathNodeOverride = GetMaterialShadingPathNodeOverride(ShaderPlatform);
if (ShadingPathNodeOverride != -1)
{
// choose the input based on platform override if one exists r.Material.ShadingPathNodeOverride
ShadingPathToCompile = ERHIShadingPath::Type(ShadingPathNodeOverride);
}
else
{
if (IsForwardShadingEnabled(ShaderPlatform))
{
// use forward shading input if shader model is SM5 or higher
ShadingPathToCompile = ERHIShadingPath::Forward;
}
else if (FeatureLevel < ERHIFeatureLevel::SM5)
{
// use mobile input for anything below shading model 5
ShadingPathToCompile = ERHIShadingPath::Mobile;
}
}

return ShadingPathToCompile;
}`

What happens if we select mobile? Will it default to Forward+ for flagship/mid tier phones?

Can you clarify how you are selecting mobile? The default for mobile platforms is Forward shading for flagship/mid tier phones unless you set Mobile > Mobile Shading to Deferred in Project Settings (r.Mobile.ShadingPath). However, even if you select Deferred rendering the engine will fall back to Forward shading on platforms that don’t support shader model 5 or higher (see GetFeatureLevelShadingPath()).