mirror of
https://github.com/Eragon-Brisingr/UShaderLab.git
synced 2026-09-15 23:04:37 +00:00
Merge output data for reduce duplicate calculate
This commit is contained in:
@@ -119,6 +119,10 @@ namespace ShaderLabGraph
|
||||
{ TEXT("CustomizedUV1"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV2"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV3"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV4"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV5"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV6"), CMOT_Float2 },
|
||||
{ TEXT("CustomizedUV7"), CMOT_Float2 },
|
||||
};
|
||||
|
||||
/** Absolute, forward-slashed path for use inside an HLSL `#line N "path"` directive. */
|
||||
@@ -535,7 +539,7 @@ namespace ShaderLabGraph
|
||||
}
|
||||
|
||||
static const FBsdfDesc GBsdfDescs[] = {
|
||||
{ EShaderLabBsdfType::Slab, TEXT("ShaderLab Surface"), TEXT("FShaderLabSurface"), TEXT("ShaderLabDefaultSurface"),
|
||||
{ EShaderLabBsdfType::Slab, TEXT("ShaderLab Surface"), TEXT("FShaderLabSlab"), TEXT("ShaderLabDefaultSlab"),
|
||||
GSlabFields, UE_ARRAY_COUNT(GSlabFields), &MakeSlabNode, &GetSlabPinGeneric },
|
||||
{ EShaderLabBsdfType::Unlit, TEXT("ShaderLab Unlit"), TEXT("FShaderLabUnlit"), TEXT("ShaderLabDefaultUnlit"),
|
||||
GUnlitFields, UE_ARRAY_COUNT(GUnlitFields), &MakeUnlitNode, &GetUnlitPin },
|
||||
@@ -1827,11 +1831,131 @@ namespace ShaderLabGraph
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Material-level (whole-material) pixel-stage outputs (FShaderLabMaterialOutput / the S.* material fields
|
||||
// of FShaderLabSurface). Shared by the single-entry Surface path and the multi-slab SL_MATERIAL block. Each
|
||||
// field maps to a main-material-node pin, EXCEPT Opacity which is coverage: under Substrate MP_Opacity is only
|
||||
// active for AlphaComposite (Material.cpp IsPropertyActive), so translucent/Decal coverage is applied by
|
||||
// wrapping the FrontMaterial root in a SubstrateWeight node (the correct Substrate coverage mechanism). ---
|
||||
struct FMatOutFieldDef { const TCHAR* Field; ECustomMaterialOutputType OutType; };
|
||||
static const FMatOutFieldDef GMatOutFields[] = {
|
||||
{ TEXT("Opacity"), CMOT_Float1 },
|
||||
{ TEXT("OpacityMask"), CMOT_Float1 },
|
||||
{ TEXT("Refraction"), CMOT_Float1 },
|
||||
{ TEXT("PixelDepthOffset"), CMOT_Float1 },
|
||||
{ TEXT("AmbientOcclusion"), CMOT_Float1 },
|
||||
{ TEXT("SurfaceThickness"), CMOT_Float1 },
|
||||
};
|
||||
|
||||
/** Non-Opacity material-output field -> its main-node pin. Opacity returns nullptr (routed via Weight). */
|
||||
static FExpressionInput* GetMaterialOutputPin(UMaterialEditorOnlyData& E, const FString& F)
|
||||
{
|
||||
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;
|
||||
if (F == TEXT("SurfaceThickness")) return &E.SurfaceThickness;
|
||||
return nullptr; // Opacity
|
||||
}
|
||||
|
||||
static bool RawSettingIsTrue(const FShaderLabModel& M, const TCHAR* Key)
|
||||
{
|
||||
for (const TPair<FString, FString>& P : M.RawSettings)
|
||||
{
|
||||
if (P.Key == Key) { const FString V = P.Value.TrimStartAndEnd(); return V == TEXT("true") || V == TEXT("1"); }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract: is a material-level output active for this material's Domain/BlendMode? Mirrors the engine's
|
||||
* Substrate IsPropertyActive (Material.cpp). Writing an inactive output would be silently ignored, so we
|
||||
* reject it with a .usl-line error instead. Validated for Surface/Decal domains (the only ones carrying
|
||||
* these fields). Returns true when active.
|
||||
*/
|
||||
static bool ValidateMaterialOutputActive(const FShaderLabModel& M, const FString& Field, int32 Line, TArray<FString>& OutErrors)
|
||||
{
|
||||
const EShaderLabDomain Domain = M.Settings.Domain;
|
||||
const EShaderLabBlendMode Blend = M.Settings.BlendMode;
|
||||
const bool bTranslucentFamily = Blend != EShaderLabBlendMode::Opaque && Blend != EShaderLabBlendMode::Masked;
|
||||
auto Fail = [&](const FString& Why) { OutErrors.Add(FString::Printf(TEXT("%s(%d): %s"), *M.SourceFilePath, Line, *Why)); return false; };
|
||||
|
||||
if (Field == TEXT("Opacity"))
|
||||
{
|
||||
// Coverage: meaningful for translucent-family blends (via Weight) or Decal domain. AlphaComposite uses MP_Opacity.
|
||||
if (Domain != EShaderLabDomain::Decal && !bTranslucentFamily)
|
||||
{
|
||||
return Fail(TEXT("O.Opacity (coverage) requires a translucent BlendMode (Translucent/Additive/Modulate/AlphaComposite/AlphaHoldout) or Domain = Decal"));
|
||||
}
|
||||
}
|
||||
else if (Field == TEXT("OpacityMask"))
|
||||
{
|
||||
if (Blend != EShaderLabBlendMode::Masked)
|
||||
{
|
||||
return Fail(TEXT("O.OpacityMask requires BlendMode = Masked"));
|
||||
}
|
||||
}
|
||||
else if (Field == TEXT("Refraction"))
|
||||
{
|
||||
if (!bTranslucentFamily || Blend == EShaderLabBlendMode::Modulate || Blend == EShaderLabBlendMode::AlphaHoldout)
|
||||
{
|
||||
return Fail(TEXT("O.Refraction requires a translucent BlendMode (Translucent/Additive/AlphaComposite) with RefractionMethod = RM_IndexOfRefraction"));
|
||||
}
|
||||
}
|
||||
else if (Field == TEXT("SurfaceThickness"))
|
||||
{
|
||||
if (!RawSettingIsTrue(M, TEXT("bIsThinSurface")))
|
||||
{
|
||||
return Fail(TEXT("O.SurfaceThickness requires SL_SETTINGS(bIsThinSurface = true)"));
|
||||
}
|
||||
}
|
||||
// PixelDepthOffset / AmbientOcclusion: active broadly (Lit / depth) — accepted as-is.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire material-level outputs (already emitted as SLO_<Field> additional outputs of `Custom`) into the graph:
|
||||
* each non-Opacity field -> its main-node pin; Opacity -> either MP_Opacity (AlphaComposite) or a SubstrateWeight
|
||||
* wrapping the FrontMaterial root (translucent/Decal coverage). Validates each field against the blend/domain.
|
||||
* `RootBsdf` is updated in place when wrapped. Returns false on a contract violation.
|
||||
*/
|
||||
static bool WireMaterialOutputs(
|
||||
UMaterial& Material, UMaterialEditorOnlyData& EditorOnly, const FShaderLabModel& Model,
|
||||
UMaterialExpressionCustom* Custom, const TArray<TPair<FString, int32>>& WrittenOutputs, int32 BodyLine,
|
||||
UMaterialExpression*& RootBsdf, int32& IoY, TArray<FString>& OutErrors)
|
||||
{
|
||||
for (const TPair<FString, int32>& Out : WrittenOutputs)
|
||||
{
|
||||
if (!ValidateMaterialOutputActive(Model, Out.Key, BodyLine, OutErrors)) { return false; }
|
||||
|
||||
if (Out.Key == TEXT("Opacity"))
|
||||
{
|
||||
if (Model.Settings.BlendMode == EShaderLabBlendMode::AlphaComposite)
|
||||
{
|
||||
EditorOnly.Opacity.Connect(Out.Value, Custom); // engine uses MP_Opacity as the alpha-composite alpha override
|
||||
}
|
||||
else
|
||||
{
|
||||
// Coverage via a Substrate Weight over the whole material (MP_Opacity is inactive here under Substrate).
|
||||
UMaterialExpressionSubstrateWeight* W = NewExpr<UMaterialExpressionSubstrateWeight>(Material, IoY, -150);
|
||||
W->A.Connect(0, RootBsdf);
|
||||
W->Weight.Connect(Out.Value, Custom);
|
||||
RootBsdf = W;
|
||||
}
|
||||
}
|
||||
else if (FExpressionInput* Pin = GetMaterialOutputPin(EditorOnly, Out.Key))
|
||||
{
|
||||
Pin->Connect(Out.Value, Custom);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the Custom node for a pixel-stage body that writes the given BSDF's output-struct fields and wire
|
||||
* it into a fresh Substrate BSDF node (chosen by Desc). Returns the BSDF node (nullptr only on error).
|
||||
* When bAllowMaterialOutputs, S.Opacity / S.OpacityMask are wired to the material-level pins (single-entry
|
||||
* sugar path only). Generic across all Substrate BSDFs via the FBsdfDesc field->pin table.
|
||||
* it into a fresh Substrate BSDF node (chosen by Desc). Returns the FrontMaterial root (nullptr only on error).
|
||||
* When bAllowMaterialOutputs (single-entry SL_SURFACE sugar), the FShaderLabSurface material-level fields are
|
||||
* also emitted and wired (StructName/DefaultFn then name the full-surface struct). Generic across all Substrate
|
||||
* BSDFs via the FBsdfDesc field->pin table.
|
||||
*/
|
||||
static UMaterialExpression* BuildBsdf(
|
||||
const FBsdfDesc& Desc, const FShaderLabBsdfModifiers& Modifiers,
|
||||
@@ -1841,8 +1965,12 @@ namespace ShaderLabGraph
|
||||
const TMap<FName, FParamNode>& PropertyNodes,
|
||||
const TMap<FName, UMaterialExpression*>& InterpByName, TSet<FName>& UsedInterps,
|
||||
const FSampleNodes& Samples,
|
||||
bool bAllowMaterialOutputs, int32& IoY, TArray<FString>& OutErrors)
|
||||
bool bAllowMaterialOutputs, const TCHAR* StructNameOverride, const TCHAR* DefaultFnOverride,
|
||||
int32& IoY, TArray<FString>& OutErrors)
|
||||
{
|
||||
const TCHAR* StructName = StructNameOverride ? StructNameOverride : Desc.StructName;
|
||||
const TCHAR* DefaultFn = DefaultFnOverride ? DefaultFnOverride : Desc.DefaultFn;
|
||||
|
||||
UMaterialExpression* Bsdf = Desc.MakeNode(Material, IoY);
|
||||
if (!ApplyBsdfModifiers(Bsdf, Modifiers, OutErrors))
|
||||
{
|
||||
@@ -1867,12 +1995,20 @@ namespace ShaderLabGraph
|
||||
UsedSlab.Add(&F);
|
||||
}
|
||||
}
|
||||
const bool bUsesOpacity = bAllowMaterialOutputs && ReferencesToken(InBody, OutParamName + TEXT(".Opacity"));
|
||||
const bool bUsesOpacityMask = bAllowMaterialOutputs && ReferencesToken(InBody, OutParamName + TEXT(".OpacityMask"));
|
||||
const bool bUsesRefraction = bAllowMaterialOutputs && ReferencesToken(InBody, OutParamName + TEXT(".Refraction"));
|
||||
const bool bUsesPDO = bAllowMaterialOutputs && ReferencesToken(InBody, OutParamName + TEXT(".PixelDepthOffset"));
|
||||
// Single-entry SL_SURFACE also carries the material-level (whole-material) fields inline.
|
||||
TArray<const FMatOutFieldDef*> UsedMatOut;
|
||||
if (bAllowMaterialOutputs)
|
||||
{
|
||||
for (const FMatOutFieldDef& F : GMatOutFields)
|
||||
{
|
||||
if (ReferencesToken(InBody, OutParamName + TEXT(".") + F.Field))
|
||||
{
|
||||
UsedMatOut.Add(&F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedSlab.Num() == 0 && !bUsesOpacity && !bUsesOpacityMask && !bUsesRefraction && !bUsesPDO)
|
||||
if (UsedSlab.Num() == 0 && UsedMatOut.Num() == 0)
|
||||
{
|
||||
return Bsdf; // Empty body: a default Substrate BSDF.
|
||||
}
|
||||
@@ -1900,7 +2036,7 @@ namespace ShaderLabGraph
|
||||
|
||||
const FString SrcPath = MakeLineDirectivePath(Model.SourceFilePath);
|
||||
FString Code = FString::Printf(TEXT("%s %s = %s();\n{\n%s}\n"),
|
||||
Desc.StructName, *OutParamName, Desc.DefaultFn, *WrapBodyWithLineMapping(Body, BodyLine, SrcPath));
|
||||
StructName, *OutParamName, DefaultFn, *WrapBodyWithLineMapping(Body, BodyLine, SrcPath));
|
||||
|
||||
int32 OutputIndex = 1; // index 0 is the (unused) main return
|
||||
TArray<TPair<const FSlabFieldDef*, int32>> SlabOutputs;
|
||||
@@ -1914,37 +2050,16 @@ namespace ShaderLabGraph
|
||||
SlabOutputs.Add(TPair<const FSlabFieldDef*, int32>(F, OutputIndex));
|
||||
++OutputIndex;
|
||||
}
|
||||
int32 OpacityOutIdx = INDEX_NONE;
|
||||
int32 OpacityMaskOutIdx = INDEX_NONE;
|
||||
if (bUsesOpacity)
|
||||
TArray<TPair<FString, int32>> MatOutputs;
|
||||
for (const FMatOutFieldDef* F : UsedMatOut)
|
||||
{
|
||||
FCustomOutput Out; Out.OutputName = TEXT("SLO_Opacity"); Out.OutputType = CMOT_Float1;
|
||||
FCustomOutput Out;
|
||||
Out.OutputName = FName(*(FString(TEXT("SLO_")) + F->Field));
|
||||
Out.OutputType = F->OutType;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_Opacity = %s.Opacity;\n"), *OutParamName);
|
||||
OpacityOutIdx = OutputIndex++;
|
||||
}
|
||||
if (bUsesOpacityMask)
|
||||
{
|
||||
FCustomOutput Out; Out.OutputName = TEXT("SLO_OpacityMask"); Out.OutputType = CMOT_Float1;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_OpacityMask = %s.OpacityMask;\n"), *OutParamName);
|
||||
OpacityMaskOutIdx = OutputIndex++;
|
||||
}
|
||||
int32 RefractionOutIdx = INDEX_NONE;
|
||||
int32 PDOOutIdx = INDEX_NONE;
|
||||
if (bUsesRefraction)
|
||||
{
|
||||
FCustomOutput Out; Out.OutputName = TEXT("SLO_Refraction"); Out.OutputType = CMOT_Float1;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_Refraction = %s.Refraction;\n"), *OutParamName);
|
||||
RefractionOutIdx = OutputIndex++;
|
||||
}
|
||||
if (bUsesPDO)
|
||||
{
|
||||
FCustomOutput Out; Out.OutputName = TEXT("SLO_PixelDepthOffset"); Out.OutputType = CMOT_Float1;
|
||||
Custom->AdditionalOutputs.Add(Out);
|
||||
Code += FString::Printf(TEXT("SLO_PixelDepthOffset = %s.PixelDepthOffset;\n"), *OutParamName);
|
||||
PDOOutIdx = OutputIndex++;
|
||||
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;
|
||||
@@ -1957,10 +2072,12 @@ namespace ShaderLabGraph
|
||||
Pin->Connect(Pair.Value, Custom);
|
||||
}
|
||||
}
|
||||
if (OpacityOutIdx != INDEX_NONE) { EditorOnly.Opacity.Connect(OpacityOutIdx, Custom); }
|
||||
if (OpacityMaskOutIdx != INDEX_NONE) { EditorOnly.OpacityMask.Connect(OpacityMaskOutIdx, Custom); }
|
||||
if (RefractionOutIdx != INDEX_NONE) { EditorOnly.Refraction.Connect(RefractionOutIdx, Custom); }
|
||||
if (PDOOutIdx != INDEX_NONE) { EditorOnly.PixelDepthOffset.Connect(PDOOutIdx, Custom); }
|
||||
// Material-level outputs (single-entry sugar): wire to main-node pins / coverage-Weight. Root may be wrapped.
|
||||
UMaterialExpression* Root = Bsdf;
|
||||
if (!WireMaterialOutputs(Material, EditorOnly, Model, Custom, MatOutputs, BodyLine, Root, IoY, OutErrors))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Wire the water volume fields into the companion output node (by matching Custom output field name).
|
||||
if (WaterOut)
|
||||
@@ -1977,7 +2094,7 @@ namespace ShaderLabGraph
|
||||
ConnectWaterOut(WaterOut->PhaseG, TEXT("WaterPhaseG"));
|
||||
ConnectWaterOut(WaterOut->ColorScaleBehindWater, TEXT("ColorScaleBehindWater"));
|
||||
}
|
||||
return Bsdf;
|
||||
return Root;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2128,6 +2245,79 @@ namespace ShaderLabGraph
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the multi-slab material-level output block (SL_MATERIAL). Mirrors BuildBsdf's material-output half:
|
||||
* one Custom node runs the body filling an FShaderLabMaterialOutput; the written fields become AdditionalOutputs
|
||||
* wired to main-node pins (or, for Opacity, a SubstrateWeight over the FrontMaterial root). `RootBsdf` is
|
||||
* updated in place when the coverage-Weight wraps it. Empty body = nothing to wire.
|
||||
*/
|
||||
static bool BuildMaterialOutputBlock(
|
||||
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, UMaterialExpression*& RootBsdf, int32& IoY, TArray<FString>& OutErrors)
|
||||
{
|
||||
const FString& OutParamName = Model.MaterialOutputParamName;
|
||||
const FString& InBody = Model.MaterialOutputBody;
|
||||
|
||||
TArray<const FMatOutFieldDef*> Used;
|
||||
for (const FMatOutFieldDef& F : GMatOutFields)
|
||||
{
|
||||
if (ReferencesToken(InBody, OutParamName + TEXT(".") + F.Field))
|
||||
{
|
||||
Used.Add(&F);
|
||||
}
|
||||
}
|
||||
if (Used.Num() == 0)
|
||||
{
|
||||
return true; // Empty SL_MATERIAL body: leave all main-node pins at their defaults.
|
||||
}
|
||||
|
||||
UMaterialExpressionCustom* Custom = NewExpr<UMaterialExpressionCustom>(Material, IoY, -300);
|
||||
Custom->Description = TEXT("ShaderLab Material Output");
|
||||
Custom->OutputType = CMOT_Float1;
|
||||
AddIncludes(*Custom, Model);
|
||||
|
||||
FString Body = InBody;
|
||||
TArray<FIntrinsicWire> Wires;
|
||||
TSet<FName> ReqProps;
|
||||
if (!PrepareBody(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, Model.MaterialOutputBodyLine, 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("FShaderLabMaterialOutput %s = ShaderLabDefaultMaterialOutput();\n{\n%s}\n"),
|
||||
*OutParamName, *WrapBodyWithLineMapping(Body, Model.MaterialOutputBodyLine, SrcPath));
|
||||
|
||||
int32 OutputIndex = 1;
|
||||
TArray<TPair<FString, int32>> MatOutputs;
|
||||
for (const FMatOutFieldDef* F : Used)
|
||||
{
|
||||
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();
|
||||
|
||||
return WireMaterialOutputs(Material, EditorOnly, Model, Custom, MatOutputs, Model.MaterialOutputBodyLine, RootBsdf, IoY, OutErrors);
|
||||
}
|
||||
|
||||
/** Build a Custom node whose return value is the scalar Value-block body. Output 0 is the scalar. */
|
||||
static UMaterialExpressionCustom* BuildValueNode(
|
||||
UMaterial& Material, const FShaderLabValue& Value, const FShaderLabModel& Model,
|
||||
@@ -2431,6 +2621,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabValue& Value : Model.Values) { Collect(Value.Body); }
|
||||
for (const FShaderLabInterpolator& Interp : Model.Interpolators) { Collect(Interp.Body); }
|
||||
if (Model.bHasVertex) { Collect(Model.VertexBody); }
|
||||
if (Model.bHasMaterialOutput) { Collect(Model.MaterialOutputBody); }
|
||||
if (Model.bHasRVTOutput) { Collect(Model.RVTOutputBody); }
|
||||
for (const FName& Fn : DirectlyCalled)
|
||||
{
|
||||
if (!ComputeFunctionCtx(Fn, Emit, OutErrors))
|
||||
@@ -2477,6 +2669,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabValue& Value : Model.Values) { if (ReferencesToken(Value.Body, NameStr)) { return true; } }
|
||||
for (const FShaderLabInterpolator& Interp : Model.Interpolators) { if (ReferencesToken(Interp.Body, NameStr)) { return true; } }
|
||||
if (Model.bHasVertex && ReferencesToken(Model.VertexBody, NameStr)) { return true; }
|
||||
if (Model.bHasMaterialOutput && ReferencesToken(Model.MaterialOutputBody, NameStr)) { return true; }
|
||||
if (Model.bHasRVTOutput && ReferencesToken(Model.RVTOutputBody, NameStr)) { return true; }
|
||||
for (const FShaderLabTopoNode& Node : Model.Topology)
|
||||
{
|
||||
if (Node.bHasFactor && Node.Factor.Kind == FShaderLabFactor::EKind::Named && Node.Factor.Name == PropName)
|
||||
@@ -2732,6 +2926,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabValue& Value : Model.Values) { bRef |= ReferencesToken(Value.Body, NameStr); }
|
||||
for (const FShaderLabInterpolator& Interp : Model.Interpolators) { bRef |= ReferencesToken(Interp.Body, NameStr); }
|
||||
if (Model.bHasVertex) { bRef |= ReferencesToken(Model.VertexBody, NameStr); }
|
||||
if (Model.bHasMaterialOutput) { bRef |= ReferencesToken(Model.MaterialOutputBody, NameStr); }
|
||||
if (Model.bHasRVTOutput) { bRef |= ReferencesToken(Model.RVTOutputBody, NameStr); }
|
||||
if (!bRef)
|
||||
{
|
||||
continue; // Declared but unreferenced: skip (keeps the graph minimal/deterministic).
|
||||
@@ -2785,6 +2981,7 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabSlab& Slab : Model.Slabs) { if (ReferencesToken(Slab.Body, NameStr)) { return true; } }
|
||||
for (const FShaderLabValue& Value : Model.Values) { if (ReferencesToken(Value.Body, NameStr)) { return true; } }
|
||||
if (Model.bHasRVTOutput && ReferencesToken(Model.RVTOutputBody, NameStr)) { return true; }
|
||||
if (Model.bHasMaterialOutput && ReferencesToken(Model.MaterialOutputBody, NameStr)) { return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -2904,12 +3101,14 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
}
|
||||
else if (Model.bHasSurface)
|
||||
{
|
||||
// Single-Surface sugar: one slab straight to FrontMaterial, with S.Opacity/S.OpacityMask
|
||||
// allowed as material-level outputs.
|
||||
// Single-Surface sugar: one slab straight to FrontMaterial, with the FShaderLabSurface material-level
|
||||
// fields (S.Opacity/OpacityMask/Refraction/PixelDepthOffset/AmbientOcclusion/SurfaceThickness) allowed
|
||||
// inline. The full-surface struct name overrides the Slab desc's (which names the bare FShaderLabSlab).
|
||||
UMaterialExpression* Slab = BuildBsdf(
|
||||
*FindBsdfDesc(EShaderLabBsdfType::Slab), Model.SurfaceModifiers,
|
||||
Material, *EditorOnly, SurfaceOutParam->Name, Model.SurfaceBody, Model.SurfaceBodyLine,
|
||||
Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples, /*bAllowMaterialOutputs*/ true, ParamY, OutErrors);
|
||||
Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples,
|
||||
/*bAllowMaterialOutputs*/ true, TEXT("FShaderLabSurface"), TEXT("ShaderLabDefaultSurface"), ParamY, OutErrors);
|
||||
if (!Slab)
|
||||
{
|
||||
return false;
|
||||
@@ -2938,7 +3137,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
UMaterialExpression* Slab = BuildBsdf(
|
||||
*Desc, SlabDecl.Modifiers,
|
||||
Material, *EditorOnly, SlabDecl.OutParamName, SlabDecl.Body, SlabDecl.BodyLine,
|
||||
Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples, /*bAllowMaterialOutputs*/ false, ParamY, OutErrors);
|
||||
Model, Program, Emit, PropertyNodes, InterpByName, UsedInterps, Samples,
|
||||
/*bAllowMaterialOutputs*/ false, /*StructNameOverride*/ nullptr, /*DefaultFnOverride*/ nullptr, ParamY, OutErrors);
|
||||
if (!Slab)
|
||||
{
|
||||
return false;
|
||||
@@ -2991,25 +3191,19 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
{
|
||||
return false;
|
||||
}
|
||||
EditorOnly->FrontMaterial.Connect(0, WrapBsdfForDecal(Material, Model, Root, ParamY));
|
||||
|
||||
// Material-level Opacity / OpacityMask from named Value blocks.
|
||||
auto ConnectMaterialOutput = [&](FExpressionInput& Pin, FName ValueName, const TCHAR* What) -> bool
|
||||
// Material-level outputs (whole-material) come from an optional SL_MATERIAL block: one Custom node feeds
|
||||
// the main-node pins (Opacity routed as a coverage-Weight over the root). Wire before FrontMaterial so a
|
||||
// coverage-Weight can wrap the root.
|
||||
if (Model.bHasMaterialOutput)
|
||||
{
|
||||
if (ValueName.IsNone()) { return true; }
|
||||
UMaterialExpressionCustom* const* ValueNode = ValueByName.Find(ValueName);
|
||||
if (!ValueNode)
|
||||
if (!BuildMaterialOutputBlock(Material, *EditorOnly, Model, Program, Emit, PropertyNodes,
|
||||
InterpByName, UsedInterps, Samples, Root, ParamY, OutErrors))
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("%s references unknown Value '%s'"), What, *ValueName.ToString()));
|
||||
return false;
|
||||
}
|
||||
Pin.Connect(0, *ValueNode);
|
||||
return true;
|
||||
};
|
||||
if (!ConnectMaterialOutput(EditorOnly->Opacity, Model.OpacityValueName, TEXT("Opacity"))) { return false; }
|
||||
if (!ConnectMaterialOutput(EditorOnly->OpacityMask, Model.OpacityMaskValueName, TEXT("OpacityMask"))) { return false; }
|
||||
if (!ConnectMaterialOutput(EditorOnly->Refraction, Model.RefractionValueName, TEXT("Refraction"))) { return false; }
|
||||
if (!ConnectMaterialOutput(EditorOnly->PixelDepthOffset, Model.PixelDepthOffsetValueName, TEXT("PixelDepthOffset"))) { return false; }
|
||||
}
|
||||
EditorOnly->FrontMaterial.Connect(0, WrapBsdfForDecal(Material, Model, Root, ParamY));
|
||||
}
|
||||
|
||||
// 3) Optional Vertex stage. Per-pixel/vertex context is read via UE_* intrinsics, so the entry
|
||||
@@ -3027,6 +3221,31 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
}
|
||||
}
|
||||
|
||||
// Contract: a written V.CustomizedUV<i> requires SL_SETTINGS(NumCustomizedUVs = N) with N > i — the
|
||||
// engine only allocates/compiles the first NumCustomizedUVs slots (UMaterial::NumCustomizedUVs, default 0)
|
||||
// and silently passes the raw vertex texcoord through for the rest. Fail loudly instead of that silent no-op.
|
||||
{
|
||||
int32 NumCustomizedUVs = 0;
|
||||
for (const TPair<FString, FString>& P : Model.RawSettings)
|
||||
{
|
||||
if (P.Key == TEXT("NumCustomizedUVs")) { NumCustomizedUVs = FCString::Atoi(*P.Value.TrimStartAndEnd()); }
|
||||
}
|
||||
for (const FVertexFieldDef* F : UsedVtx)
|
||||
{
|
||||
const FString Field(F->Field);
|
||||
if (Field.StartsWith(TEXT("CustomizedUV")))
|
||||
{
|
||||
const int32 UvIndex = FCString::Atoi(*Field.Mid(12));
|
||||
if (UvIndex >= NumCustomizedUVs)
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("%s(%d): V.CustomizedUV%d requires SL_SETTINGS(NumCustomizedUVs = %d) or higher (currently %d)"),
|
||||
*Model.SourceFilePath, Model.VertexBodyLine, UvIndex, UvIndex + 1, NumCustomizedUVs));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedVtx.Num() > 0)
|
||||
{
|
||||
UMaterialExpressionCustom* VCustom = NewExpr<UMaterialExpressionCustom>(Material, ParamY, -300);
|
||||
@@ -3136,6 +3355,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabValue& Value : Model.Values) { if (ReferencesToken(Value.Body, Token)) { return true; } }
|
||||
for (const FShaderLabInterpolator& Interp : Model.Interpolators) { if (ReferencesToken(Interp.Body, Token)) { return true; } }
|
||||
if (Model.bHasVertex && ReferencesToken(Model.VertexBody, Token)) { return true; }
|
||||
if (Model.bHasMaterialOutput && ReferencesToken(Model.MaterialOutputBody, Token)) { return true; }
|
||||
if (Model.bHasRVTOutput && ReferencesToken(Model.RVTOutputBody, Token)) { return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -3186,6 +3407,8 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
for (const FShaderLabValue& Value : Model.Values) { Bodies.Add({ &Value.Body, Value.BodyLine }); }
|
||||
for (const FShaderLabInterpolator& Interp : Model.Interpolators) { Bodies.Add({ &Interp.Body, Interp.BodyLine }); }
|
||||
if (Model.bHasVertex) { Bodies.Add({ &Model.VertexBody, Model.VertexBodyLine }); }
|
||||
if (Model.bHasMaterialOutput) { Bodies.Add({ &Model.MaterialOutputBody, Model.MaterialOutputBodyLine }); }
|
||||
if (Model.bHasRVTOutput) { Bodies.Add({ &Model.RVTOutputBody, Model.RVTOutputBodyLine }); }
|
||||
if (Emit.HasLibraries())
|
||||
{
|
||||
for (const TPair<FName, FFunctionCtx>& Pair : Emit.Ctx)
|
||||
|
||||
Reference in New Issue
Block a user