mirror of
https://github.com/Eragon-Brisingr/UShaderLab.git
synced 2026-09-15 14:54:36 +00:00
Support Legacy shader mode
This commit is contained in:
@@ -41,89 +41,36 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// A single Substrate Slab BSDF layer, filled by an SL_SLAB() body in a multi-slab shader. These 18 fields
|
||||
// mirror the pins of UMaterialExpressionSubstrateSlabBSDF one-for-one (name/order/type). A slab is only a
|
||||
// BSDF layer — material-level outputs (Opacity/Refraction/...) are NOT slab concerns: set them once per
|
||||
// material via an SL_MATERIAL() block (FShaderLabMaterialOutput). Defaults mirror the node's pin defaults.
|
||||
struct FShaderLabSlab
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float3 F90;
|
||||
float Roughness;
|
||||
float Anisotropy;
|
||||
float3 Normal;
|
||||
float3 Tangent;
|
||||
float3 SSSMFP;
|
||||
float SSSMFPScale;
|
||||
float SSSPhaseAnisotropy;
|
||||
float3 EmissiveColor;
|
||||
float SecondRoughness;
|
||||
float SecondRoughnessWeight;
|
||||
float FuzzRoughness;
|
||||
float FuzzAmount;
|
||||
float3 FuzzColor;
|
||||
float GlintValue;
|
||||
float2 GlintUV;
|
||||
};
|
||||
// ---------------------------------------------------------------------------
|
||||
// SHADERLAB_SUBSTRATE — the project-level rendering scheme, derived from the engine's own SUBSTRATE_ENABLED
|
||||
// shader define (set from r.Substrate at material-shader-environment build time, ShaderCompiler.cpp). This
|
||||
// selects which surface-struct parameterization the SL_SURFACE body sees. It is a compile-time constant for
|
||||
// the whole material (Substrate is project-wide, not per-material), so it needs no per-permutation leak.
|
||||
// The C++ graph builder branches on the same source (Substrate::IsSubstrateEnabled()), keeping the two in sync.
|
||||
// ---------------------------------------------------------------------------
|
||||
#ifndef SHADERLAB_SUBSTRATE
|
||||
#ifdef SHADERLAB_IDE
|
||||
#define SHADERLAB_SUBSTRATE 1 // IDE default (Substrate authoring); override in shader-validator defines to check legacy.
|
||||
#elif defined(SUBSTRATE_ENABLED)
|
||||
#define SHADERLAB_SUBSTRATE SUBSTRATE_ENABLED
|
||||
#else
|
||||
#define SHADERLAB_SUBSTRATE 0 // Fallback if the engine define is somehow absent: bias to the legacy
|
||||
// (deletable) path. In practice SUBSTRATE_ENABLED is always set for
|
||||
// material shaders, so this branch is not expected to be taken.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Material-level (whole-material) pixel-stage outputs, filled by an SL_MATERIAL() block in a multi-slab
|
||||
// shader. Each field maps to a main-material-node pin (NOT a slab pin); which are active depends on the
|
||||
// material Domain/BlendMode (validated by the graph builder mirroring the engine's IsPropertyActive):
|
||||
// Opacity — translucency coverage (routed through a Substrate Weight over the material)
|
||||
// OpacityMask — Masked blend cutout
|
||||
// Refraction — scalar IOR (RefractionMethod = IndexOfRefraction); 1.0 = no bending
|
||||
// PixelDepthOffset — world-unit depth push toward camera
|
||||
// AmbientOcclusion — ambient occlusion (Lit)
|
||||
// SurfaceThickness — thin-surface thickness in cm (only when the material is a thin surface)
|
||||
struct FShaderLabMaterialOutput
|
||||
{
|
||||
float Opacity;
|
||||
float OpacityMask;
|
||||
float Refraction;
|
||||
float PixelDepthOffset;
|
||||
float AmbientOcclusion;
|
||||
float SurfaceThickness;
|
||||
};
|
||||
// Mode-specific surface/BSDF structs live in split files so the legacy scheme is deletable wholesale.
|
||||
#if SHADERLAB_SUBSTRATE
|
||||
#include "/Plugin/ShaderLab/Private/Surface_Substrate.ush"
|
||||
#else
|
||||
#include "/Plugin/ShaderLab/Private/Surface_Legacy.ush"
|
||||
#endif
|
||||
|
||||
// Simple single-entry surface, filled by the SL_SURFACE(...) body. It is the one-stop path: the 18 Slab
|
||||
// shading fields (== FShaderLabSlab) PLUS the material-level outputs (== FShaderLabMaterialOutput), so a
|
||||
// simple opaque/masked/translucent material can be written in one block. Multi-slab shaders instead split
|
||||
// these across SL_SLAB (FShaderLabSlab) + SL_MATERIAL (FShaderLabMaterialOutput). Slab-field defaults
|
||||
// mirror the SubstrateSlabBSDF pin defaults; material-output defaults mirror the main-node pin defaults.
|
||||
struct FShaderLabSurface
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float3 F90;
|
||||
float Roughness;
|
||||
float Anisotropy;
|
||||
float3 Normal;
|
||||
float3 Tangent;
|
||||
float3 SSSMFP;
|
||||
float SSSMFPScale;
|
||||
float SSSPhaseAnisotropy;
|
||||
float3 EmissiveColor;
|
||||
float SecondRoughness;
|
||||
float SecondRoughnessWeight;
|
||||
float FuzzRoughness;
|
||||
float FuzzAmount;
|
||||
float3 FuzzColor;
|
||||
float GlintValue;
|
||||
float2 GlintUV;
|
||||
// Material-level outputs (single-Surface sugar). See FShaderLabMaterialOutput for pin semantics.
|
||||
float Opacity;
|
||||
float OpacityMask;
|
||||
float Refraction;
|
||||
float PixelDepthOffset;
|
||||
float AmbientOcclusion;
|
||||
float SurfaceThickness;
|
||||
};
|
||||
// --- Mode-independent output structs (identical fields/semantics in both schemes). ---
|
||||
|
||||
// --- Additional Substrate BSDF output structs. Each mirrors the pins of its engine BSDF node; the graph
|
||||
// builder wires only the fields a body writes, leaving the rest at the node's native default. ---
|
||||
|
||||
// Unlit BSDF (SL_UNLIT): pure emissive / transmittance, no lighting.
|
||||
// Unlit BSDF (SL_UNLIT): pure emissive / transmittance, no lighting. Under Substrate -> UnlitBSDF;
|
||||
// under legacy -> MSM_Unlit (only EmissiveColor feeds MP_EmissiveColor). Struct is shared either way.
|
||||
struct FShaderLabUnlit
|
||||
{
|
||||
float3 EmissiveColor;
|
||||
@@ -131,89 +78,6 @@ struct FShaderLabUnlit
|
||||
float3 Normal;
|
||||
};
|
||||
|
||||
// Hair BSDF (SL_HAIR): hair-fiber shading.
|
||||
struct FShaderLabHair
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Scatter;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Backlit;
|
||||
float3 Tangent;
|
||||
float3 EmissiveColor;
|
||||
};
|
||||
|
||||
// Eye BSDF (SL_EYE): cornea / iris / sclera shading.
|
||||
struct FShaderLabEye
|
||||
{
|
||||
float3 DiffuseColor;
|
||||
float Roughness;
|
||||
float3 CorneaNormal;
|
||||
float3 IrisNormal;
|
||||
float3 IrisPlaneNormal;
|
||||
float IrisMask;
|
||||
float IrisDistance;
|
||||
float3 EmissiveColor;
|
||||
};
|
||||
|
||||
// Single Layer Water BSDF (SL_WATER): water surface + underwater scattering.
|
||||
struct FShaderLabWater
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Metallic;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float3 TopMaterialOpacity;
|
||||
float3 WaterAlbedo;
|
||||
float3 WaterExtinction;
|
||||
float WaterPhaseG;
|
||||
float3 ColorScaleBehindWater;
|
||||
};
|
||||
|
||||
// Volumetric-Fog-Cloud BSDF (SL_VOLUME, Domain = Volume): participating media.
|
||||
struct FShaderLabVolume
|
||||
{
|
||||
float3 Albedo;
|
||||
float3 Extinction;
|
||||
float3 EmissiveColor;
|
||||
float AmbientOcclusion;
|
||||
};
|
||||
|
||||
// Simple Clear Coat BSDF (SL_CLEARCOAT): bottom layer + clear coat top.
|
||||
struct FShaderLabClearCoat
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float Roughness;
|
||||
float ClearCoatCoverage;
|
||||
float ClearCoatRoughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float3 BottomNormal;
|
||||
};
|
||||
|
||||
// Toon BSDF (SL_TOON, experimental): stylized shading.
|
||||
struct FShaderLabToon
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Metallic;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float2 PatternUVs;
|
||||
float Anisotropy;
|
||||
float3 Tangent;
|
||||
};
|
||||
|
||||
// Light Function (SL_LIGHTFUNCTION, Domain = LightFunction): per-light modulation color.
|
||||
struct FShaderLabLightFunction
|
||||
{
|
||||
float3 Color;
|
||||
};
|
||||
|
||||
FShaderLabUnlit ShaderLabDefaultUnlit()
|
||||
{
|
||||
FShaderLabUnlit U;
|
||||
@@ -223,96 +87,6 @@ FShaderLabUnlit ShaderLabDefaultUnlit()
|
||||
return U;
|
||||
}
|
||||
|
||||
FShaderLabHair ShaderLabDefaultHair()
|
||||
{
|
||||
FShaderLabHair H;
|
||||
H.BaseColor = float3(0, 0, 0);
|
||||
H.Scatter = 0;
|
||||
H.Specular = 0.5;
|
||||
H.Roughness = 0.5;
|
||||
H.Backlit = float3(0, 0, 0);
|
||||
H.Tangent = float3(1, 0, 0);
|
||||
H.EmissiveColor = float3(0, 0, 0);
|
||||
return H;
|
||||
}
|
||||
|
||||
FShaderLabEye ShaderLabDefaultEye()
|
||||
{
|
||||
FShaderLabEye E;
|
||||
E.DiffuseColor = float3(0, 0, 0);
|
||||
E.Roughness = 0.5;
|
||||
E.CorneaNormal = float3(0, 0, 1);
|
||||
E.IrisNormal = float3(0, 0, 1);
|
||||
E.IrisPlaneNormal = float3(0, 0, 1);
|
||||
E.IrisMask = 0;
|
||||
E.IrisDistance = 0;
|
||||
E.EmissiveColor = float3(0, 0, 0);
|
||||
return E;
|
||||
}
|
||||
|
||||
FShaderLabWater ShaderLabDefaultWater()
|
||||
{
|
||||
FShaderLabWater W;
|
||||
W.BaseColor = float3(0, 0, 0);
|
||||
W.Metallic = 0;
|
||||
W.Specular = 0.5;
|
||||
W.Roughness = 0.5;
|
||||
W.Normal = float3(0, 0, 1);
|
||||
W.EmissiveColor = float3(0, 0, 0);
|
||||
W.TopMaterialOpacity = float3(0, 0, 0);
|
||||
W.WaterAlbedo = float3(0, 0, 0);
|
||||
W.WaterExtinction = float3(0, 0, 0);
|
||||
W.WaterPhaseG = 0;
|
||||
W.ColorScaleBehindWater = float3(1, 1, 1);
|
||||
return W;
|
||||
}
|
||||
|
||||
FShaderLabVolume ShaderLabDefaultVolume()
|
||||
{
|
||||
FShaderLabVolume V;
|
||||
V.Albedo = float3(0, 0, 0);
|
||||
V.Extinction = float3(0, 0, 0);
|
||||
V.EmissiveColor = float3(0, 0, 0);
|
||||
V.AmbientOcclusion = 1;
|
||||
return V;
|
||||
}
|
||||
|
||||
FShaderLabClearCoat ShaderLabDefaultClearCoat()
|
||||
{
|
||||
FShaderLabClearCoat C;
|
||||
C.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
C.F0 = float3(0.04, 0.04, 0.04);
|
||||
C.Roughness = 0.5;
|
||||
C.ClearCoatCoverage = 0.5;
|
||||
C.ClearCoatRoughness = 0.5;
|
||||
C.Normal = float3(0, 0, 1);
|
||||
C.EmissiveColor = float3(0, 0, 0);
|
||||
C.BottomNormal = float3(0, 0, 1);
|
||||
return C;
|
||||
}
|
||||
|
||||
FShaderLabToon ShaderLabDefaultToon()
|
||||
{
|
||||
FShaderLabToon T;
|
||||
T.BaseColor = float3(0.18, 0.18, 0.18);
|
||||
T.Metallic = 0;
|
||||
T.Specular = 0.5;
|
||||
T.Roughness = 0.5;
|
||||
T.Normal = float3(0, 0, 1);
|
||||
T.EmissiveColor = float3(0, 0, 0);
|
||||
T.PatternUVs = float2(0, 0);
|
||||
T.Anisotropy = 0;
|
||||
T.Tangent = float3(1, 0, 0);
|
||||
return T;
|
||||
}
|
||||
|
||||
FShaderLabLightFunction ShaderLabDefaultLightFunction()
|
||||
{
|
||||
FShaderLabLightFunction L;
|
||||
L.Color = float3(1, 1, 1);
|
||||
return L;
|
||||
}
|
||||
|
||||
// Output for the PostProcess(...) entry (Domain = PostProcess). Color feeds the material EmissiveColor.
|
||||
struct FShaderLabPostProcess
|
||||
{
|
||||
@@ -345,72 +119,6 @@ struct FShaderLabVertex
|
||||
float2 CustomizedUV7;
|
||||
};
|
||||
|
||||
FShaderLabSurface ShaderLabDefaultSurface()
|
||||
{
|
||||
FShaderLabSurface S;
|
||||
S.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
S.F0 = float3(0.04, 0.04, 0.04);
|
||||
S.F90 = float3(1, 1, 1);
|
||||
S.Roughness = 0.5;
|
||||
S.Anisotropy = 0;
|
||||
S.Normal = float3(0, 0, 1);
|
||||
S.Tangent = float3(1, 0, 0);
|
||||
S.SSSMFP = float3(0, 0, 0);
|
||||
S.SSSMFPScale = 1;
|
||||
S.SSSPhaseAnisotropy = 1;
|
||||
S.EmissiveColor = float3(0, 0, 0);
|
||||
S.SecondRoughness = 0.5;
|
||||
S.SecondRoughnessWeight = 0;
|
||||
S.FuzzRoughness = 0.5;
|
||||
S.FuzzAmount = 0;
|
||||
S.FuzzColor = float3(0, 0, 0);
|
||||
S.GlintValue = 1;
|
||||
S.GlintUV = float2(0, 0);
|
||||
S.Opacity = 1;
|
||||
S.OpacityMask = 1;
|
||||
S.Refraction = 1.0;
|
||||
S.PixelDepthOffset = 0.0;
|
||||
S.AmbientOcclusion = 1;
|
||||
S.SurfaceThickness = 0.01; // SUBSTRATE_LAYER_DEFAULT_THICKNESS_CM
|
||||
return S;
|
||||
}
|
||||
|
||||
FShaderLabSlab ShaderLabDefaultSlab()
|
||||
{
|
||||
FShaderLabSlab S;
|
||||
S.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
S.F0 = float3(0.04, 0.04, 0.04);
|
||||
S.F90 = float3(1, 1, 1);
|
||||
S.Roughness = 0.5;
|
||||
S.Anisotropy = 0;
|
||||
S.Normal = float3(0, 0, 1);
|
||||
S.Tangent = float3(1, 0, 0);
|
||||
S.SSSMFP = float3(0, 0, 0);
|
||||
S.SSSMFPScale = 1;
|
||||
S.SSSPhaseAnisotropy = 1;
|
||||
S.EmissiveColor = float3(0, 0, 0);
|
||||
S.SecondRoughness = 0.5;
|
||||
S.SecondRoughnessWeight = 0;
|
||||
S.FuzzRoughness = 0.5;
|
||||
S.FuzzAmount = 0;
|
||||
S.FuzzColor = float3(0, 0, 0);
|
||||
S.GlintValue = 1;
|
||||
S.GlintUV = float2(0, 0);
|
||||
return S;
|
||||
}
|
||||
|
||||
FShaderLabMaterialOutput ShaderLabDefaultMaterialOutput()
|
||||
{
|
||||
FShaderLabMaterialOutput O;
|
||||
O.Opacity = 1;
|
||||
O.OpacityMask = 1;
|
||||
O.Refraction = 1.0;
|
||||
O.PixelDepthOffset = 0.0;
|
||||
O.AmbientOcclusion = 1;
|
||||
O.SurfaceThickness = 0.01; // SUBSTRATE_LAYER_DEFAULT_THICKNESS_CM
|
||||
return O;
|
||||
}
|
||||
|
||||
FShaderLabPostProcess ShaderLabDefaultPostProcess()
|
||||
{
|
||||
FShaderLabPostProcess P;
|
||||
|
||||
55
Shaders/Private/Surface_Legacy.ush
Normal file
55
Shaders/Private/Surface_Legacy.ush
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright UShaderLab. All Rights Reserved.
|
||||
// Legacy (non-Substrate) surface output struct. Included by ShaderLabCommon.ush only when
|
||||
// SHADERLAB_SUBSTRATE == 0 (i.e. the project runs with r.Substrate=0). The Substrate counterpart is
|
||||
// Surface_Substrate.ush. This whole file — plus ShaderLabSurfaceEmitter_Legacy.* and the legacy branch
|
||||
// in ShaderLabGraphBuilder — is the deletable "old scheme" slice: once UE removes the non-Substrate
|
||||
// path, delete these and the #else in ShaderLabCommon.ush.
|
||||
//
|
||||
// SL_SURFACE stays the same concept across both modes, but the shading parameterization differs: Substrate
|
||||
// uses DiffuseAlbedo/F0/F90/... while legacy uses the classic Metallic/Roughness workflow (BaseColor/
|
||||
// Metallic/Specular). Common fields (Normal/Roughness/Anisotropy/Tangent/EmissiveColor and the material-
|
||||
// level outputs) carry the same names in both, so a body that only touches those needs no `#if`. A body
|
||||
// that touches the mode-specific shading fields guards them with `#if SHADERLAB_SUBSTRATE ... #else ...`.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Single-entry surface, filled by the SL_SURFACE(...) body. Legacy = classic Metallic/Roughness workflow
|
||||
// mapped to a MSM_DefaultLit material: shading fields feed MP_BaseColor/Metallic/Specular/Roughness/
|
||||
// Anisotropy/Normal/Tangent/EmissiveColor, and the material-level outputs feed the matching main pins.
|
||||
// (Substrate-only fields — F0/F90/SSSMFP/Fuzz/Glint/SurfaceThickness — do not exist here.)
|
||||
struct FShaderLabSurface
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Metallic;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float Anisotropy;
|
||||
float3 Normal;
|
||||
float3 Tangent;
|
||||
float3 EmissiveColor;
|
||||
// Material-level outputs. See the graph builder's activeness contract (Domain/BlendMode).
|
||||
float Opacity; // translucency coverage -> MP_Opacity (legacy wires it directly)
|
||||
float OpacityMask; // Masked blend cutout -> MP_OpacityMask
|
||||
float Refraction; // scalar IOR -> MP_Refraction
|
||||
float PixelDepthOffset; // world-unit depth push -> MP_PixelDepthOffset
|
||||
float AmbientOcclusion; // -> MP_AmbientOcclusion
|
||||
};
|
||||
|
||||
FShaderLabSurface ShaderLabDefaultSurface()
|
||||
{
|
||||
FShaderLabSurface S;
|
||||
S.BaseColor = float3(0.18, 0.18, 0.18);
|
||||
S.Metallic = 0;
|
||||
S.Specular = 0.5;
|
||||
S.Roughness = 0.5;
|
||||
S.Anisotropy = 0;
|
||||
S.Normal = float3(0, 0, 1);
|
||||
S.Tangent = float3(1, 0, 0);
|
||||
S.EmissiveColor = float3(0, 0, 0);
|
||||
S.Opacity = 1;
|
||||
S.OpacityMask = 1;
|
||||
S.Refraction = 1.0;
|
||||
S.PixelDepthOffset = 0.0;
|
||||
S.AmbientOcclusion = 1;
|
||||
return S;
|
||||
}
|
||||
328
Shaders/Private/Surface_Substrate.ush
Normal file
328
Shaders/Private/Surface_Substrate.ush
Normal file
@@ -0,0 +1,328 @@
|
||||
// Copyright UShaderLab. All Rights Reserved.
|
||||
// Substrate-mode surface/BSDF output structs. Included by ShaderLabCommon.ush only when
|
||||
// SHADERLAB_SUBSTRATE != 0 (i.e. the project runs with r.Substrate=1). The legacy (non-Substrate)
|
||||
// counterpart is Surface_Legacy.ush. Keeping the two schemes in separate files makes the legacy path
|
||||
// deletable wholesale once UE drops the non-Substrate rendering path.
|
||||
|
||||
#pragma once
|
||||
|
||||
// A single Substrate Slab BSDF layer, filled by an SL_SLAB() body in a multi-slab shader. These 18 fields
|
||||
// mirror the pins of UMaterialExpressionSubstrateSlabBSDF one-for-one (name/order/type). A slab is only a
|
||||
// BSDF layer — material-level outputs (Opacity/Refraction/...) are NOT slab concerns: set them once per
|
||||
// material via an SL_MATERIAL() block (FShaderLabMaterialOutput). Defaults mirror the node's pin defaults.
|
||||
struct FShaderLabSlab
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float3 F90;
|
||||
float Roughness;
|
||||
float Anisotropy;
|
||||
float3 Normal;
|
||||
float3 Tangent;
|
||||
float3 SSSMFP;
|
||||
float SSSMFPScale;
|
||||
float SSSPhaseAnisotropy;
|
||||
float3 EmissiveColor;
|
||||
float SecondRoughness;
|
||||
float SecondRoughnessWeight;
|
||||
float FuzzRoughness;
|
||||
float FuzzAmount;
|
||||
float3 FuzzColor;
|
||||
float GlintValue;
|
||||
float2 GlintUV;
|
||||
};
|
||||
|
||||
// Material-level (whole-material) pixel-stage outputs, filled by an SL_MATERIAL() block in a multi-slab
|
||||
// shader. Each field maps to a main-material-node pin (NOT a slab pin); which are active depends on the
|
||||
// material Domain/BlendMode (validated by the graph builder mirroring the engine's IsPropertyActive):
|
||||
// Opacity — translucency coverage (routed through a Substrate Weight over the material)
|
||||
// OpacityMask — Masked blend cutout
|
||||
// Refraction — scalar IOR (RefractionMethod = IndexOfRefraction); 1.0 = no bending
|
||||
// PixelDepthOffset — world-unit depth push toward camera
|
||||
// AmbientOcclusion — ambient occlusion (Lit)
|
||||
// SurfaceThickness — thin-surface thickness in cm (only when the material is a thin surface)
|
||||
struct FShaderLabMaterialOutput
|
||||
{
|
||||
float Opacity;
|
||||
float OpacityMask;
|
||||
float Refraction;
|
||||
float PixelDepthOffset;
|
||||
float AmbientOcclusion;
|
||||
float SurfaceThickness;
|
||||
};
|
||||
|
||||
// Simple single-entry surface, filled by the SL_SURFACE(...) body. It is the one-stop path: the 18 Slab
|
||||
// shading fields (== FShaderLabSlab) PLUS the material-level outputs (== FShaderLabMaterialOutput), so a
|
||||
// simple opaque/masked/translucent material can be written in one block. Multi-slab shaders instead split
|
||||
// these across SL_SLAB (FShaderLabSlab) + SL_MATERIAL (FShaderLabMaterialOutput). Slab-field defaults
|
||||
// mirror the SubstrateSlabBSDF pin defaults; material-output defaults mirror the main-node pin defaults.
|
||||
struct FShaderLabSurface
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float3 F90;
|
||||
float Roughness;
|
||||
float Anisotropy;
|
||||
float3 Normal;
|
||||
float3 Tangent;
|
||||
float3 SSSMFP;
|
||||
float SSSMFPScale;
|
||||
float SSSPhaseAnisotropy;
|
||||
float3 EmissiveColor;
|
||||
float SecondRoughness;
|
||||
float SecondRoughnessWeight;
|
||||
float FuzzRoughness;
|
||||
float FuzzAmount;
|
||||
float3 FuzzColor;
|
||||
float GlintValue;
|
||||
float2 GlintUV;
|
||||
// Material-level outputs (single-Surface sugar). See FShaderLabMaterialOutput for pin semantics.
|
||||
float Opacity;
|
||||
float OpacityMask;
|
||||
float Refraction;
|
||||
float PixelDepthOffset;
|
||||
float AmbientOcclusion;
|
||||
float SurfaceThickness;
|
||||
};
|
||||
|
||||
// --- Additional Substrate BSDF output structs. Each mirrors the pins of its engine BSDF node; the graph
|
||||
// builder wires only the fields a body writes, leaving the rest at the node's native default. ---
|
||||
|
||||
// Hair BSDF (SL_HAIR): hair-fiber shading.
|
||||
struct FShaderLabHair
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Scatter;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Backlit;
|
||||
float3 Tangent;
|
||||
float3 EmissiveColor;
|
||||
};
|
||||
|
||||
// Eye BSDF (SL_EYE): cornea / iris / sclera shading.
|
||||
struct FShaderLabEye
|
||||
{
|
||||
float3 DiffuseColor;
|
||||
float Roughness;
|
||||
float3 CorneaNormal;
|
||||
float3 IrisNormal;
|
||||
float3 IrisPlaneNormal;
|
||||
float IrisMask;
|
||||
float IrisDistance;
|
||||
float3 EmissiveColor;
|
||||
};
|
||||
|
||||
// Single Layer Water BSDF (SL_WATER): water surface + underwater scattering.
|
||||
struct FShaderLabWater
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Metallic;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float3 TopMaterialOpacity;
|
||||
float3 WaterAlbedo;
|
||||
float3 WaterExtinction;
|
||||
float WaterPhaseG;
|
||||
float3 ColorScaleBehindWater;
|
||||
};
|
||||
|
||||
// Volumetric-Fog-Cloud BSDF (SL_VOLUME, Domain = Volume): participating media.
|
||||
struct FShaderLabVolume
|
||||
{
|
||||
float3 Albedo;
|
||||
float3 Extinction;
|
||||
float3 EmissiveColor;
|
||||
float AmbientOcclusion;
|
||||
};
|
||||
|
||||
// Simple Clear Coat BSDF (SL_CLEARCOAT): bottom layer + clear coat top.
|
||||
struct FShaderLabClearCoat
|
||||
{
|
||||
float3 DiffuseAlbedo;
|
||||
float3 F0;
|
||||
float Roughness;
|
||||
float ClearCoatCoverage;
|
||||
float ClearCoatRoughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float3 BottomNormal;
|
||||
};
|
||||
|
||||
// Toon BSDF (SL_TOON, experimental): stylized shading.
|
||||
struct FShaderLabToon
|
||||
{
|
||||
float3 BaseColor;
|
||||
float Metallic;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float3 Normal;
|
||||
float3 EmissiveColor;
|
||||
float2 PatternUVs;
|
||||
float Anisotropy;
|
||||
float3 Tangent;
|
||||
};
|
||||
|
||||
// Light Function (SL_LIGHTFUNCTION, Domain = LightFunction): per-light modulation color.
|
||||
struct FShaderLabLightFunction
|
||||
{
|
||||
float3 Color;
|
||||
};
|
||||
|
||||
FShaderLabHair ShaderLabDefaultHair()
|
||||
{
|
||||
FShaderLabHair H;
|
||||
H.BaseColor = float3(0, 0, 0);
|
||||
H.Scatter = 0;
|
||||
H.Specular = 0.5;
|
||||
H.Roughness = 0.5;
|
||||
H.Backlit = float3(0, 0, 0);
|
||||
H.Tangent = float3(1, 0, 0);
|
||||
H.EmissiveColor = float3(0, 0, 0);
|
||||
return H;
|
||||
}
|
||||
|
||||
FShaderLabEye ShaderLabDefaultEye()
|
||||
{
|
||||
FShaderLabEye E;
|
||||
E.DiffuseColor = float3(0, 0, 0);
|
||||
E.Roughness = 0.5;
|
||||
E.CorneaNormal = float3(0, 0, 1);
|
||||
E.IrisNormal = float3(0, 0, 1);
|
||||
E.IrisPlaneNormal = float3(0, 0, 1);
|
||||
E.IrisMask = 0;
|
||||
E.IrisDistance = 0;
|
||||
E.EmissiveColor = float3(0, 0, 0);
|
||||
return E;
|
||||
}
|
||||
|
||||
FShaderLabWater ShaderLabDefaultWater()
|
||||
{
|
||||
FShaderLabWater W;
|
||||
W.BaseColor = float3(0, 0, 0);
|
||||
W.Metallic = 0;
|
||||
W.Specular = 0.5;
|
||||
W.Roughness = 0.5;
|
||||
W.Normal = float3(0, 0, 1);
|
||||
W.EmissiveColor = float3(0, 0, 0);
|
||||
W.TopMaterialOpacity = float3(0, 0, 0);
|
||||
W.WaterAlbedo = float3(0, 0, 0);
|
||||
W.WaterExtinction = float3(0, 0, 0);
|
||||
W.WaterPhaseG = 0;
|
||||
W.ColorScaleBehindWater = float3(1, 1, 1);
|
||||
return W;
|
||||
}
|
||||
|
||||
FShaderLabVolume ShaderLabDefaultVolume()
|
||||
{
|
||||
FShaderLabVolume V;
|
||||
V.Albedo = float3(0, 0, 0);
|
||||
V.Extinction = float3(0, 0, 0);
|
||||
V.EmissiveColor = float3(0, 0, 0);
|
||||
V.AmbientOcclusion = 1;
|
||||
return V;
|
||||
}
|
||||
|
||||
FShaderLabClearCoat ShaderLabDefaultClearCoat()
|
||||
{
|
||||
FShaderLabClearCoat C;
|
||||
C.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
C.F0 = float3(0.04, 0.04, 0.04);
|
||||
C.Roughness = 0.5;
|
||||
C.ClearCoatCoverage = 0.5;
|
||||
C.ClearCoatRoughness = 0.5;
|
||||
C.Normal = float3(0, 0, 1);
|
||||
C.EmissiveColor = float3(0, 0, 0);
|
||||
C.BottomNormal = float3(0, 0, 1);
|
||||
return C;
|
||||
}
|
||||
|
||||
FShaderLabToon ShaderLabDefaultToon()
|
||||
{
|
||||
FShaderLabToon T;
|
||||
T.BaseColor = float3(0.18, 0.18, 0.18);
|
||||
T.Metallic = 0;
|
||||
T.Specular = 0.5;
|
||||
T.Roughness = 0.5;
|
||||
T.Normal = float3(0, 0, 1);
|
||||
T.EmissiveColor = float3(0, 0, 0);
|
||||
T.PatternUVs = float2(0, 0);
|
||||
T.Anisotropy = 0;
|
||||
T.Tangent = float3(1, 0, 0);
|
||||
return T;
|
||||
}
|
||||
|
||||
FShaderLabLightFunction ShaderLabDefaultLightFunction()
|
||||
{
|
||||
FShaderLabLightFunction L;
|
||||
L.Color = float3(1, 1, 1);
|
||||
return L;
|
||||
}
|
||||
|
||||
FShaderLabSurface ShaderLabDefaultSurface()
|
||||
{
|
||||
FShaderLabSurface S;
|
||||
S.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
S.F0 = float3(0.04, 0.04, 0.04);
|
||||
S.F90 = float3(1, 1, 1);
|
||||
S.Roughness = 0.5;
|
||||
S.Anisotropy = 0;
|
||||
S.Normal = float3(0, 0, 1);
|
||||
S.Tangent = float3(1, 0, 0);
|
||||
S.SSSMFP = float3(0, 0, 0);
|
||||
S.SSSMFPScale = 1;
|
||||
S.SSSPhaseAnisotropy = 1;
|
||||
S.EmissiveColor = float3(0, 0, 0);
|
||||
S.SecondRoughness = 0.5;
|
||||
S.SecondRoughnessWeight = 0;
|
||||
S.FuzzRoughness = 0.5;
|
||||
S.FuzzAmount = 0;
|
||||
S.FuzzColor = float3(0, 0, 0);
|
||||
S.GlintValue = 1;
|
||||
S.GlintUV = float2(0, 0);
|
||||
S.Opacity = 1;
|
||||
S.OpacityMask = 1;
|
||||
S.Refraction = 1.0;
|
||||
S.PixelDepthOffset = 0.0;
|
||||
S.AmbientOcclusion = 1;
|
||||
S.SurfaceThickness = 0.01; // SUBSTRATE_LAYER_DEFAULT_THICKNESS_CM
|
||||
return S;
|
||||
}
|
||||
|
||||
FShaderLabSlab ShaderLabDefaultSlab()
|
||||
{
|
||||
FShaderLabSlab S;
|
||||
S.DiffuseAlbedo = float3(0.18, 0.18, 0.18);
|
||||
S.F0 = float3(0.04, 0.04, 0.04);
|
||||
S.F90 = float3(1, 1, 1);
|
||||
S.Roughness = 0.5;
|
||||
S.Anisotropy = 0;
|
||||
S.Normal = float3(0, 0, 1);
|
||||
S.Tangent = float3(1, 0, 0);
|
||||
S.SSSMFP = float3(0, 0, 0);
|
||||
S.SSSMFPScale = 1;
|
||||
S.SSSPhaseAnisotropy = 1;
|
||||
S.EmissiveColor = float3(0, 0, 0);
|
||||
S.SecondRoughness = 0.5;
|
||||
S.SecondRoughnessWeight = 0;
|
||||
S.FuzzRoughness = 0.5;
|
||||
S.FuzzAmount = 0;
|
||||
S.FuzzColor = float3(0, 0, 0);
|
||||
S.GlintValue = 1;
|
||||
S.GlintUV = float2(0, 0);
|
||||
return S;
|
||||
}
|
||||
|
||||
FShaderLabMaterialOutput ShaderLabDefaultMaterialOutput()
|
||||
{
|
||||
FShaderLabMaterialOutput O;
|
||||
O.Opacity = 1;
|
||||
O.OpacityMask = 1;
|
||||
O.Refraction = 1.0;
|
||||
O.PixelDepthOffset = 0.0;
|
||||
O.AmbientOcclusion = 1;
|
||||
O.SurfaceThickness = 0.01; // SUBSTRATE_LAYER_DEFAULT_THICKNESS_CM
|
||||
return O;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "MaterialTypes.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "RenderUtils.h" // Substrate::IsSubstrateEnabled()
|
||||
#include "ShaderLabModel.h"
|
||||
#include "ShaderLabSettingsApplier.h"
|
||||
|
||||
@@ -44,6 +45,7 @@ namespace ShaderLabRuntime_Private
|
||||
static const FFieldToProperty GFieldToProperty[] = {
|
||||
{ TEXT(".DiffuseAlbedo"), MP_DiffuseColor }, // Substrate Slab albedo/F0 parameterization
|
||||
{ TEXT(".F0"), MP_SpecularColor },
|
||||
{ TEXT(".BaseColor"), MP_BaseColor }, // legacy (non-Substrate) DefaultLit parameterization
|
||||
{ TEXT(".Normal"), MP_Normal },
|
||||
{ TEXT(".Roughness"), MP_Roughness },
|
||||
{ TEXT(".Metallic"), MP_Metallic },
|
||||
@@ -78,7 +80,8 @@ namespace ShaderLabRuntime_Private
|
||||
// mask for the Decal domain even if field detection missed everything (e.g. body writes via a helper).
|
||||
if (!bAny && Model.Settings.Domain == EShaderLabDomain::Decal)
|
||||
{
|
||||
Data.SetPropertyConnected(MP_DiffuseColor);
|
||||
// Base-color channel differs by scheme: Substrate uses MP_DiffuseColor, legacy MP_BaseColor.
|
||||
Data.SetPropertyConnected(Substrate::IsSubstrateEnabled() ? MP_DiffuseColor : MP_BaseColor);
|
||||
Data.SetPropertyConnected(MP_Normal);
|
||||
}
|
||||
}
|
||||
@@ -112,6 +115,19 @@ void FShaderLabRuntimeBuilder::ApplySettings(UMaterial& Material, const FShaderL
|
||||
|
||||
Material.TwoSided = Model.Settings.bTwoSided ? 1 : 0;
|
||||
|
||||
// Legacy (non-Substrate): set the shell's shading model explicitly. A MIC with no ShadingModel override
|
||||
// derives its ShadingModels from Parent->GetShadingModels() (UMaterialInstance::UpdateOverridableBaseProperties,
|
||||
// MaterialInstance.cpp), so a shell left at the default MSM_DefaultLit would make an SL_UNLIT material's
|
||||
// instance render lit and mismatch its baked Unlit shader map. Mirror the editor graph builder's choice
|
||||
// (ShaderLabSurfaceEmitter_Legacy.inl). Under Substrate the shading model is driven by the FrontMaterial
|
||||
// graph (MSM_FromMaterialExpression), so leave it untouched — matching the Substrate shell's existing behavior.
|
||||
if (!Substrate::IsSubstrateEnabled() && Model.Settings.Domain == EShaderLabDomain::Surface)
|
||||
{
|
||||
const bool bUnlit = !Model.bHasSurface && Model.Slabs.Num() == 1
|
||||
&& Model.Slabs[0].BsdfType == EShaderLabBsdfType::Unlit;
|
||||
Material.SetShadingModel(bUnlit ? MSM_Unlit : MSM_DefaultLit);
|
||||
}
|
||||
|
||||
// Reflected long-tail settings. A failure here means a malformed .usl shipped in the build, so
|
||||
// log it loudly. (No usage flags: the base shell is a never-rendered template; usage lives on
|
||||
// the instances.)
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "ShaderCore.h"
|
||||
#include "SceneTypes.h"
|
||||
#include "RenderUtils.h" // Substrate::IsSubstrateEnabled()
|
||||
|
||||
#define SHADERLAB_COMMON_INCLUDE TEXT("/Plugin/ShaderLab/Private/ShaderLabCommon.ush")
|
||||
#define SHADERLAB_FUNCTIONS_INCLUDE TEXT("/Plugin/ShaderLab/Private/ShaderLabUEFunctions.ush")
|
||||
@@ -2521,6 +2522,10 @@ namespace ShaderLabGraph
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy (non-Substrate) pixel-stage emitter. Pulled into this TU (inside namespace ShaderLabGraph) so it
|
||||
// reuses the helpers above without a shared header. Deletable wholesale with the non-Substrate scheme.
|
||||
#include "ShaderLabSurfaceEmitter_Legacy.inl"
|
||||
}
|
||||
|
||||
bool FShaderLabGraphBuilder::ResolveVirtualShaderFile(const FString& VirtualPath, FString& OutDiskPath)
|
||||
@@ -3099,6 +3104,17 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!Substrate::IsSubstrateEnabled())
|
||||
{
|
||||
// Legacy (non-Substrate) scheme: PostProcess/UI already handled above (mode-independent). Everything
|
||||
// else (SL_SURFACE -> DefaultLit, single SL_UNLIT -> Unlit) wires to the standard material pins; the
|
||||
// rest is rejected with a .usl error. See ShaderLabSurfaceEmitter_Legacy.inl.
|
||||
if (!EmitLegacyPixelStage(Material, *EditorOnly, Model, Program, Emit, PropertyNodes,
|
||||
InterpByName, UsedInterps, Samples, SurfaceOutParam, ParamY, OutErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Model.bHasSurface)
|
||||
{
|
||||
// Single-Surface sugar: one slab straight to FrontMaterial, with the FShaderLabSurface material-level
|
||||
@@ -3526,9 +3542,22 @@ void FShaderLabGraphBuilder::BuildPoisonInto(UMaterial& Material, const TArray<F
|
||||
*SrcPath, *Combined);
|
||||
Custom->RebuildOutputs();
|
||||
|
||||
UMaterialExpressionSubstrateSlabBSDF* Slab = NewExpr<UMaterialExpressionSubstrateSlabBSDF>(Material, IoY, 0);
|
||||
Slab->DiffuseAlbedo.Connect(0, Custom);
|
||||
EditorOnly->FrontMaterial.Connect(0, Slab);
|
||||
if (Substrate::IsSubstrateEnabled())
|
||||
{
|
||||
// Substrate: Custom -> Slab.DiffuseAlbedo -> FrontMaterial (DiffuseAlbedo is a core BSDF pin, always
|
||||
// translated, so the #error is reached).
|
||||
UMaterialExpressionSubstrateSlabBSDF* Slab = NewExpr<UMaterialExpressionSubstrateSlabBSDF>(Material, IoY, 0);
|
||||
Slab->DiffuseAlbedo.Connect(0, Custom);
|
||||
EditorOnly->FrontMaterial.Connect(0, Slab);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy (non-Substrate): FrontMaterial is inactive, so the Slab->FrontMaterial poison would be
|
||||
// dead-stripped and the #error never compiled. Wire the Custom to MP_BaseColor (a core DefaultLit pin
|
||||
// that is always translated) so the #error still aborts the compile with our diagnostics.
|
||||
Material.SetShadingModel(MSM_DefaultLit);
|
||||
EditorOnly->BaseColor.Connect(0, Custom);
|
||||
}
|
||||
|
||||
Material.UpdateCachedExpressionData();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "RenderUtils.h" // Substrate::IsSubstrateEnabled()
|
||||
#include "ShaderCore.h"
|
||||
#include "ShaderLabIntrinsicRegistry.h"
|
||||
|
||||
@@ -405,6 +406,20 @@ namespace ShaderLabIDEPrepare_Private
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
// SHADERLAB_SUBSTRATE picks the surface-struct parameterization (Substrate Slab fields vs legacy
|
||||
// Metallic/Roughness) the IDE checks against. It mirrors the project's r.Substrate so authoring on
|
||||
// the legacy (r.Substrate=0) branch sees the legacy FShaderLabSurface (S.BaseColor/Metallic/...) and
|
||||
// does not red-underline it as "no member". The real compiler derives this from the engine's own
|
||||
// SUBSTRATE_ENABLED; this only affects the editor's shader-validator.
|
||||
const TCHAR* SubstrateValue = Substrate::IsSubstrateEnabled() ? TEXT("1") : TEXT("0");
|
||||
FString SubstrateDefine;
|
||||
if (!Defines->TryGetStringField(TEXT("SHADERLAB_SUBSTRATE"), SubstrateDefine) || SubstrateDefine != SubstrateValue)
|
||||
{
|
||||
Defines->SetStringField(TEXT("SHADERLAB_SUBSTRATE"), SubstrateValue);
|
||||
Settings->SetObjectField(TEXT("shader-validator.defines"), Defines);
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
if (!bChanged)
|
||||
{
|
||||
UE_LOG(LogShaderLabIDE, Log, TEXT(" unchanged: %s"), *SettingsPath);
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright UShaderLab. All Rights Reserved.
|
||||
//
|
||||
// Legacy (non-Substrate) pixel-stage emitter. This file is #included into ShaderLabGraphBuilder.cpp's
|
||||
// translation unit (inside namespace ShaderLabGraph) so it can reuse the mode-independent body-emission
|
||||
// helpers (PrepareBody / WirePropInputs / AddIncludes / NewExpr / WrapBodyWithLineMapping) directly,
|
||||
// without extracting a shared base or duplicating them. It is a plain #include (not a compiled .cpp), so
|
||||
// UBT does not build it standalone.
|
||||
//
|
||||
// It is the deletable "old scheme" slice on the C++ side: once UE removes the non-Substrate rendering path,
|
||||
// delete this file, its #include in ShaderLabGraphBuilder.cpp, the !IsSubstrateEnabled() dispatch branch,
|
||||
// and Surface_Legacy.ush. The Substrate path stays untouched.
|
||||
//
|
||||
// Scope (first cut): SL_SURFACE -> MSM_DefaultLit (classic Metallic/Roughness workflow) and a single
|
||||
// SL_UNLIT block -> MSM_Unlit. PostProcess/UI entries are mode-independent and handled by the shared
|
||||
// BuildEmissiveEntry, so they never reach here. Multi-slab topology, SL_SLAB, and the other Substrate
|
||||
// BSDFs (Hair/Eye/Water/ClearCoat/Toon/Volume/LightFunction) have no legacy equivalent and are rejected
|
||||
// with a .usl-mapped error (contract style, no silent fallback).
|
||||
|
||||
// A legacy DefaultLit surface shading field -> its main-material-node pin. Fields mirror the legacy
|
||||
// FShaderLabSurface (Surface_Legacy.ush) shading section; names differ from the Substrate parameterization.
|
||||
struct FLegacyFieldDef { const TCHAR* Field; ECustomMaterialOutputType OutType; };
|
||||
|
||||
static const FLegacyFieldDef GLegacySurfaceShadingFields[] = {
|
||||
{ TEXT("BaseColor"), CMOT_Float3 },
|
||||
{ TEXT("Metallic"), CMOT_Float1 },
|
||||
{ TEXT("Specular"), CMOT_Float1 },
|
||||
{ TEXT("Roughness"), CMOT_Float1 },
|
||||
{ TEXT("Anisotropy"), CMOT_Float1 },
|
||||
{ TEXT("Normal"), CMOT_Float3 },
|
||||
{ TEXT("Tangent"), CMOT_Float3 },
|
||||
{ TEXT("EmissiveColor"), CMOT_Float3 },
|
||||
};
|
||||
|
||||
// Legacy material-level outputs (the Substrate FShaderLabMaterialOutput minus SurfaceThickness, which has no
|
||||
// legacy pin). Opacity here wires straight to MP_Opacity (no Substrate coverage-Weight indirection).
|
||||
static const FMatOutFieldDef GLegacyMatOutFields[] = {
|
||||
{ TEXT("Opacity"), CMOT_Float1 },
|
||||
{ TEXT("OpacityMask"), CMOT_Float1 },
|
||||
{ TEXT("Refraction"), CMOT_Float1 },
|
||||
{ TEXT("PixelDepthOffset"), CMOT_Float1 },
|
||||
{ TEXT("AmbientOcclusion"), CMOT_Float1 },
|
||||
};
|
||||
|
||||
/** Legacy DefaultLit shading field -> its editor-only main-node pin. */
|
||||
static FExpressionInput* GetLegacySurfacePin(UMaterialEditorOnlyData& E, const FString& F)
|
||||
{
|
||||
if (F == TEXT("BaseColor")) return &E.BaseColor;
|
||||
if (F == TEXT("Metallic")) return &E.Metallic;
|
||||
if (F == TEXT("Specular")) return &E.Specular;
|
||||
if (F == TEXT("Roughness")) return &E.Roughness;
|
||||
if (F == TEXT("Anisotropy")) return &E.Anisotropy;
|
||||
if (F == TEXT("Normal")) return &E.Normal;
|
||||
if (F == TEXT("Tangent")) return &E.Tangent;
|
||||
if (F == TEXT("EmissiveColor")) return &E.EmissiveColor;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Legacy material-level output field -> its editor-only main-node pin (Opacity included, wired directly). */
|
||||
static FExpressionInput* GetLegacyMatOutPin(UMaterialEditorOnlyData& E, const FString& F)
|
||||
{
|
||||
if (F == TEXT("Opacity")) return &E.Opacity;
|
||||
if (F == TEXT("OpacityMask")) return &E.OpacityMask;
|
||||
if (F == TEXT("Refraction")) return &E.Refraction;
|
||||
if (F == TEXT("PixelDepthOffset")) return &E.PixelDepthOffset;
|
||||
if (F == TEXT("AmbientOcclusion")) return &E.AmbientOcclusion;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a legacy pixel-stage body as a single Custom node whose written struct fields are exposed as SLO_<F>
|
||||
* additional outputs, then connect each output to the given main-node pin. Mirrors the Custom-node middle of
|
||||
* BuildBsdf (shared machinery: intrinsics, property inputs, includes, #line mapping) but wires to fixed
|
||||
* material pins instead of a Substrate BSDF node. `ShadingFields`/`GetShadingPin` describe the BSDF-style
|
||||
* shading fields; `bAllowMatOut` additionally emits the legacy material-level outputs (Surface only).
|
||||
* Returns false on a contract violation (with .usl-mapped errors).
|
||||
*/
|
||||
static bool BuildLegacyCustomToPins(
|
||||
UMaterial& Material, UMaterialEditorOnlyData& EditorOnly, const FShaderLabModel& Model,
|
||||
const FShaderLabResolvedProgram& Program, const FLibraryEmit& Emit,
|
||||
const TMap<FName, FParamNode>& PropertyNodes,
|
||||
const TMap<FName, UMaterialExpression*>& InterpByName, TSet<FName>& UsedInterps,
|
||||
const FSampleNodes& Samples,
|
||||
const TCHAR* StructName, const TCHAR* DefaultFn, const FString& OutParamName,
|
||||
const FString& InBody, int32 BodyLine,
|
||||
const FLegacyFieldDef* ShadingFields, int32 NumShadingFields,
|
||||
FExpressionInput* (*GetShadingPin)(UMaterialEditorOnlyData&, const FString&),
|
||||
bool bAllowMatOut,
|
||||
int32& IoY, TArray<FString>& OutErrors)
|
||||
{
|
||||
TArray<const FLegacyFieldDef*> UsedShading;
|
||||
for (int32 i = 0; i < NumShadingFields; ++i)
|
||||
{
|
||||
if (ReferencesToken(InBody, OutParamName + TEXT(".") + ShadingFields[i].Field))
|
||||
{
|
||||
UsedShading.Add(&ShadingFields[i]);
|
||||
}
|
||||
}
|
||||
TArray<const FMatOutFieldDef*> UsedMatOut;
|
||||
if (bAllowMatOut)
|
||||
{
|
||||
for (const FMatOutFieldDef& F : GLegacyMatOutFields)
|
||||
{
|
||||
if (ReferencesToken(InBody, OutParamName + TEXT(".") + F.Field))
|
||||
{
|
||||
UsedMatOut.Add(&F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedShading.Num() == 0 && UsedMatOut.Num() == 0)
|
||||
{
|
||||
// A single-Surface body (bAllowMatOut) that touches OutParam.<something> but matched no known legacy
|
||||
// field is almost certainly writing a Substrate-only field (DiffuseAlbedo/F0/SSSMFP/...) that the legacy
|
||||
// FShaderLabSurface does not have. Reject with a .usl-mapped error rather than silently emitting a flat
|
||||
// default-gray material (the body would never be compiled, so DXC's "no member" never fires). Unlit is
|
||||
// exempt: its unwired fields (Normal/TransmittanceColor) are real struct members, legal-but-ignored.
|
||||
if (bAllowMatOut && InBody.Contains(OutParamName + TEXT(".")))
|
||||
{
|
||||
OutErrors.Add(FString::Printf(
|
||||
TEXT("%s(%d): SL_SURFACE body writes no field of the legacy %s. The non-Substrate scheme supports "
|
||||
"BaseColor/Metallic/Specular/Roughness/Anisotropy/Normal/Tangent/EmissiveColor + material "
|
||||
"outputs (Opacity/OpacityMask/Refraction/PixelDepthOffset/AmbientOcclusion). Substrate-only "
|
||||
"fields (DiffuseAlbedo/F0/SSSMFP/Fuzz/Glint/SurfaceThickness) require r.Substrate=1."),
|
||||
*Model.SourceFilePath, BodyLine, StructName));
|
||||
return false;
|
||||
}
|
||||
// Genuinely empty body: leave all pins unconnected (the material uses its pin defaults). Still valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
UMaterialExpressionCustom* Custom = NewExpr<UMaterialExpressionCustom>(Material, IoY, -300);
|
||||
Custom->Description = TEXT("ShaderLab Surface");
|
||||
Custom->OutputType = CMOT_Float1;
|
||||
AddIncludes(*Custom, Model);
|
||||
|
||||
FString Body = InBody;
|
||||
TArray<FIntrinsicWire> Wires;
|
||||
TSet<FName> ReqProps;
|
||||
if (!PrepareBody(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, BodyLine,
|
||||
MakeLineDirectivePath(Model.SourceFilePath), Emit, Wires, ReqProps, InterpByName, UsedInterps, Samples, OutErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (const FIntrinsicWire& Wire : Wires)
|
||||
{
|
||||
FCustomInput In;
|
||||
In.InputName = Wire.InputName;
|
||||
In.Input.Connect(Wire.OutputIndex, Wire.Expr);
|
||||
Custom->Inputs.Add(In);
|
||||
}
|
||||
WirePropInputs(*Custom, Program, PropertyNodes, InBody, ReqProps, Model.Collections);
|
||||
|
||||
const FString SrcPath = MakeLineDirectivePath(Model.SourceFilePath);
|
||||
FString Code = FString::Printf(TEXT("%s %s = %s();\n{\n%s}\n"),
|
||||
StructName, *OutParamName, DefaultFn, *WrapBodyWithLineMapping(Body, BodyLine, SrcPath));
|
||||
|
||||
int32 OutputIndex = 1; // index 0 is the (unused) main return
|
||||
TArray<TPair<const FLegacyFieldDef*, int32>> ShadingOutputs;
|
||||
for (const FLegacyFieldDef* F : UsedShading)
|
||||
{
|
||||
FCustomOutput Out;
|
||||
Out.OutputName = FName(*(FString(TEXT("SLO_")) + F->Field));
|
||||
Out.OutputType = F->OutType;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_%s = %s.%s;\n"), F->Field, *OutParamName, F->Field);
|
||||
ShadingOutputs.Add(TPair<const FLegacyFieldDef*, int32>(F, OutputIndex));
|
||||
++OutputIndex;
|
||||
}
|
||||
TArray<TPair<FString, int32>> MatOutputs;
|
||||
for (const FMatOutFieldDef* F : UsedMatOut)
|
||||
{
|
||||
FCustomOutput Out;
|
||||
Out.OutputName = FName(*(FString(TEXT("SLO_")) + F->Field));
|
||||
Out.OutputType = F->OutType;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_%s = %s.%s;\n"), F->Field, *OutParamName, F->Field);
|
||||
MatOutputs.Add(TPair<FString, int32>(F->Field, OutputIndex));
|
||||
++OutputIndex;
|
||||
}
|
||||
Code += TEXT("return 0.0f;\n");
|
||||
Custom->Code = Code;
|
||||
Custom->RebuildOutputs();
|
||||
|
||||
for (const TPair<const FLegacyFieldDef*, int32>& Pair : ShadingOutputs)
|
||||
{
|
||||
if (FExpressionInput* Pin = GetShadingPin(EditorOnly, Pair.Key->Field))
|
||||
{
|
||||
Pin->Connect(Pair.Value, Custom);
|
||||
}
|
||||
}
|
||||
for (const TPair<FString, int32>& Out : MatOutputs)
|
||||
{
|
||||
// Same active-for-blend/domain contract as the Substrate path (reused verbatim); wiring differs
|
||||
// (legacy connects Opacity straight to MP_Opacity, no coverage-Weight).
|
||||
if (!ValidateMaterialOutputActive(Model, Out.Key, BodyLine, OutErrors)) { return false; }
|
||||
if (FExpressionInput* Pin = GetLegacyMatOutPin(EditorOnly, Out.Key))
|
||||
{
|
||||
Pin->Connect(Out.Value, Custom);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy scheme entry: build the Surface / single-Unlit pixel stage and wire it to the standard material
|
||||
* pins + shading model. Returns false (with .usl errors) for any construct with no legacy equivalent.
|
||||
* Called only when Substrate is disabled and the entry is neither PostProcess nor UI.
|
||||
*/
|
||||
static bool EmitLegacyPixelStage(
|
||||
UMaterial& Material, UMaterialEditorOnlyData& EditorOnly, const FShaderLabModel& Model,
|
||||
const FShaderLabResolvedProgram& Program, const FLibraryEmit& Emit,
|
||||
const TMap<FName, FParamNode>& PropertyNodes,
|
||||
const TMap<FName, UMaterialExpression*>& InterpByName, TSet<FName>& UsedInterps,
|
||||
const FSampleNodes& Samples, const FShaderLabEntryParam* SurfaceOutParam,
|
||||
int32& IoY, TArray<FString>& OutErrors)
|
||||
{
|
||||
auto Reject = [&](const FString& Why) { OutErrors.Add(FString::Printf(TEXT("%s: %s"), *Model.SourceFilePath, *Why)); return false; };
|
||||
|
||||
if (Model.bHasSurface)
|
||||
{
|
||||
// SL_SURFACE -> MSM_DefaultLit. (PostProcess/UI never reach here.)
|
||||
check(Model.SurfaceEntry == EShaderLabEntry::Surface);
|
||||
check(SurfaceOutParam);
|
||||
if (!Model.SurfaceModifiers.SubsurfaceProfilePath.IsEmpty()
|
||||
|| !Model.SurfaceModifiers.SpecularProfilePath.IsEmpty()
|
||||
|| !Model.SurfaceModifiers.SubSurfaceType.IsEmpty()
|
||||
|| !Model.SurfaceModifiers.ToonProfilePath.IsEmpty())
|
||||
{
|
||||
return Reject(TEXT("SL_SURFACE BSDF modifiers (SubsurfaceProfile/SpecularProfile/SubSurfaceType/ToonProfile) require Substrate (r.Substrate=1)"));
|
||||
}
|
||||
Material.SetShadingModel(MSM_DefaultLit);
|
||||
return BuildLegacyCustomToPins(
|
||||
Material, EditorOnly, Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples,
|
||||
TEXT("FShaderLabSurface"), TEXT("ShaderLabDefaultSurface"), SurfaceOutParam->Name,
|
||||
Model.SurfaceBody, Model.SurfaceBodyLine,
|
||||
GLegacySurfaceShadingFields, UE_ARRAY_COUNT(GLegacySurfaceShadingFields), &GetLegacySurfacePin,
|
||||
/*bAllowMatOut*/ true, IoY, OutErrors);
|
||||
}
|
||||
|
||||
// Multi-BSDF path. Legacy only supports a single SL_UNLIT block auto-rooted to a lone SlabRef.
|
||||
if (Model.bHasMaterialOutput)
|
||||
{
|
||||
return Reject(TEXT("SL_MATERIAL (multi-slab material outputs) requires Substrate (r.Substrate=1); use SL_SURFACE in the legacy scheme"));
|
||||
}
|
||||
if (Model.Slabs.Num() != 1 || Model.Slabs[0].BsdfType != EShaderLabBsdfType::Unlit)
|
||||
{
|
||||
return Reject(TEXT("the legacy (non-Substrate) scheme supports only SL_SURFACE (DefaultLit) and a single SL_UNLIT block; multi-slab topology and the Slab/Hair/Eye/Water/ClearCoat/Toon/Volume/LightFunction BSDFs require Substrate (r.Substrate=1)"));
|
||||
}
|
||||
|
||||
// Single Unlit -> MSM_Unlit: only EmissiveColor is meaningful (Normal/TransmittanceColor are ignored by
|
||||
// the Unlit shading model). Wire EmissiveColor -> MP_EmissiveColor.
|
||||
const FShaderLabSlab& Unlit = Model.Slabs[0];
|
||||
static const FLegacyFieldDef GLegacyUnlitFields[] = { { TEXT("EmissiveColor"), CMOT_Float3 } };
|
||||
auto GetUnlitPin = [](UMaterialEditorOnlyData& E, const FString& F) -> FExpressionInput*
|
||||
{
|
||||
return F == TEXT("EmissiveColor") ? &E.EmissiveColor : nullptr;
|
||||
};
|
||||
Material.SetShadingModel(MSM_Unlit);
|
||||
return BuildLegacyCustomToPins(
|
||||
Material, EditorOnly, Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples,
|
||||
TEXT("FShaderLabUnlit"), TEXT("ShaderLabDefaultUnlit"), Unlit.OutParamName,
|
||||
Unlit.Body, Unlit.BodyLine,
|
||||
GLegacyUnlitFields, UE_ARRAY_COUNT(GLegacyUnlitFields), +GetUnlitPin,
|
||||
/*bAllowMatOut*/ false, IoY, OutErrors);
|
||||
}
|
||||
Reference in New Issue
Block a user