mirror of
https://github.com/Eragon-Brisingr/UShaderLab.git
synced 2026-09-15 14:54:36 +00:00
266 lines
12 KiB
C++
266 lines
12 KiB
C++
// 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);
|
|
}
|