Files
Fishing2/Packages/com.waveharmonic.crest/Editor/Scripts/Utility/Shared/ShaderGraphLegacySubTarget.cs
2026-01-08 22:30:55 +08:00

740 lines
30 KiB
C#

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Rendering.BuiltIn;
using UnityEditor.Rendering.BuiltIn.ShaderGraph;
using UnityEditor.ShaderGraph;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_2022_3_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.UIElements;
using UnityEditor.UIElements;
#endif
using UnityBuiltInLitSubTarget = UnityEditor.Rendering.BuiltIn.ShaderGraph.BuiltInLitSubTarget;
namespace WaveHarmonic.Crest.Editor.ShaderGraph
{
sealed class MaterialModificationProcessor : AssetModificationProcessor
{
static void OnWillCreateAsset(string asset)
{
if (!asset.ToLowerInvariant().EndsWith(".mat"))
{
return;
}
MaterialPostProcessor.s_CreatedAssets.Add(asset);
}
}
sealed class MaterialPostProcessor : AssetPostprocessor
{
public override int GetPostprocessOrder()
{
return 1;
}
internal static readonly List<string> s_CreatedAssets = new();
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (var asset in importedAssets)
{
// We only care about materials
if (!asset.EndsWith(".mat", System.StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
// Load the material and look for it's BuiltIn ShaderID.
// We only care about versioning materials using a known BuiltIn ShaderID.
// This skips any materials that only target other render pipelines, are user shaders,
// or are shaders we don't care to version
var material = (Material)AssetDatabase.LoadAssetAtPath(asset, typeof(Material));
var shaderID = ShaderUtils.GetShaderID(material.shader);
if (shaderID == ShaderUtils.ShaderID.Unknown)
{
continue;
}
if (material.shader == null || material.shader.name != "Crest/Water")
{
continue;
}
// Look for the BuiltIn AssetVersion
AssetVersion assetVersion = null;
var allAssets = AssetDatabase.LoadAllAssetsAtPath(asset);
foreach (var subAsset in allAssets)
{
if (subAsset is AssetVersion sub)
{
assetVersion = sub;
}
}
if (!assetVersion)
{
if (s_CreatedAssets.Contains(asset))
{
s_CreatedAssets.Remove(asset);
CustomBuiltInLitGUI.UpdateMaterial(material);
}
}
}
}
}
class CustomBuiltInLitGUI : BuiltInLitGUI
{
MaterialEditor _MaterialEditor;
MaterialProperty[] _Properties;
static readonly GUIContent s_WorkflowModeText = EditorGUIUtility.TrTextContent
(
"Workflow Mode",
"Select a workflow that fits your textures. Choose between Metallic or Specular."
);
static readonly GUIContent s_TransparentReceiveShadowsText = EditorGUIUtility.TrTextContent
(
"Receives Shadows",
"When enabled, other GameObjects can cast shadows onto this GameObject."
);
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
_MaterialEditor = materialEditor;
_Properties = properties;
base.OnGUI(materialEditor, properties);
}
public override void ValidateMaterial(Material material)
{
base.ValidateMaterial(material);
UpdateMaterial(material);
}
public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
{
base.AssignNewShaderToMaterial(material, oldShader, newShader);
UpdateMaterial(material);
}
protected override void DrawSurfaceOptions(Material material)
{
var materialEditor = _MaterialEditor;
var properties = _Properties;
var workflowProperty = FindProperty(Property.SpecularWorkflowMode(), properties, false);
if (workflowProperty != null)
{
DoPopup(s_WorkflowModeText, materialEditor, workflowProperty, System.Enum.GetNames(typeof(WorkflowMode)));
}
base.DrawSurfaceOptions(material);
var surfaceTypeProp = FindProperty(Property.Surface(), properties, false);
if (surfaceTypeProp != null && (SurfaceType)surfaceTypeProp.floatValue == SurfaceType.Transparent)
{
var trsProperty = FindProperty(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty, properties, false);
DrawFloatToggleProperty(s_TransparentReceiveShadowsText, trsProperty);
}
}
// Should be called by ShaderGraphMaterialsUpdater, but we will never upgrade.
public static new void UpdateMaterial(Material material)
{
if (material.HasProperty(Property.SpecularWorkflowMode()))
{
var workflow = (WorkflowMode)material.GetFloat(Property.SpecularWorkflowMode());
CoreUtils.SetKeyword(material, BuiltInLitSubTarget.LitDefines.s_SpecularSetup.referenceName, workflow == WorkflowMode.Specular);
}
if (material.HasProperty(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty))
{
var receive = material.GetFloat(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty) == 1f;
CoreUtils.SetKeyword(material, BuiltInLitSubTarget.LitDefines.s_TransparentReceivesShadows.referenceName, receive);
}
}
}
sealed class BuiltInLitSubTarget : BuiltInSubTarget
{
const string k_ShaderPath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Legacy";
const string k_TemplatePath = "Packages/com.waveharmonic.crest/Editor/Shaders/Templates";
readonly UnityBuiltInLitSubTarget _BuiltInLitSubTarget;
#pragma warning disable IDE0032, IDE1006
[SerializeField]
WorkflowMode m_WorkflowMode = WorkflowMode.Metallic;
[SerializeField]
NormalDropOffSpace m_NormalDropOffSpace = NormalDropOffSpace.Tangent;
[SerializeField]
bool m_TransparentReceiveShadows = true;
#pragma warning restore IDE0032, IDE1006
public static readonly string s_TransparentReceiveShadowsProperty = "_BUILTIN_TransparentReceiveShadows";
public BuiltInLitSubTarget()
{
_BuiltInLitSubTarget = new();
displayName = _BuiltInLitSubTarget.displayName;
}
protected override ShaderUtils.ShaderID shaderID => ShaderUtils.ShaderID.SG_Lit;
public override bool IsActive() => true;
WorkflowMode WorkflowMode
{
get => m_WorkflowMode;
set => m_WorkflowMode = value;
}
NormalDropOffSpace NormalDropOffSpace
{
get => m_NormalDropOffSpace;
set => m_NormalDropOffSpace = value;
}
bool TransparentReceiveShadows
{
get => m_TransparentReceiveShadows;
set => m_TransparentReceiveShadows = value;
}
#if UNITY_2022_3_OR_NEWER
static FieldInfo s_CustomEditorForRenderPipelines;
static FieldInfo CustomEditorForRenderPipelines => s_CustomEditorForRenderPipelines ??= typeof(TargetSetupContext).GetField("customEditorForRenderPipelines", BindingFlags.NonPublic | BindingFlags.Instance);
#endif
public override void Setup(ref TargetSetupContext context)
{
_BuiltInLitSubTarget.target = target;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
_BuiltInLitSubTarget.Setup(ref context);
// Caused a crash: !context.HasCustomEditorForRenderPipeline(null)
if (string.IsNullOrEmpty(target.customEditorGUI))
{
#if UNITY_2022_3_OR_NEWER
var editors = (List<ShaderCustomEditor>)CustomEditorForRenderPipelines.GetValue(context);
if (editors.Count > 0)
{
editors.RemoveAt(editors.Count - 1);
}
context.AddCustomEditorForRenderPipeline(typeof(CustomBuiltInLitGUI).FullName, "");
#else
if (context.customEditorForRenderPipelines.Count > 0)
{
context.customEditorForRenderPipelines.RemoveAt(context.customEditorForRenderPipelines.Count - 1);
}
context.customEditorForRenderPipelines.Add((typeof(CustomBuiltInLitGUI).FullName, ""));
#endif
}
context.subShaders.RemoveAt(0);
context.AddSubShader(SubShaders.Lit(this));
}
public override void ProcessPreviewMaterial(Material material)
{
_BuiltInLitSubTarget.target = target;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
_BuiltInLitSubTarget.ProcessPreviewMaterial(material);
CustomBuiltInLitGUI.UpdateMaterial(material);
}
public override void GetFields(ref TargetFieldContext context)
{
_BuiltInLitSubTarget.target = target;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
_BuiltInLitSubTarget.GetFields(ref context);
// Do not use this, as we handle this properly.
context.AddField(BuiltInFields.SpecularSetup, false);
}
public override void GetActiveBlocks(ref TargetActiveBlockContext context)
{
_BuiltInLitSubTarget.target = target;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
_BuiltInLitSubTarget.GetActiveBlocks(ref context);
context.activeBlocks.Remove(BlockFields.SurfaceDescription.Metallic);
var insertion = context.activeBlocks.FindIndex(x => x == BlockFields.SurfaceDescription.Occlusion) + 1;
if ((WorkflowMode == WorkflowMode.Specular) || target.allowMaterialOverride)
{
context.activeBlocks.Insert(insertion, BlockFields.SurfaceDescription.Specular);
}
if ((WorkflowMode == WorkflowMode.Metallic) || target.allowMaterialOverride)
{
context.activeBlocks.Insert(insertion, BlockFields.SurfaceDescription.Metallic);
}
}
public override void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode)
{
if (target.allowMaterialOverride)
{
collector.AddFloatProperty(Property.SpecularWorkflowMode(), (float)WorkflowMode);
}
_BuiltInLitSubTarget.target = target;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
_BuiltInLitSubTarget.CollectShaderProperties(collector, generationMode);
if (target.allowMaterialOverride)
{
collector.AddFloatProperty(s_TransparentReceiveShadowsProperty, TransparentReceiveShadows ? 1f : 0f);
}
// LEqual
collector.AddFloatProperty(SubShaders.k_ShadowCasterZTest, 4, UnityEditor.ShaderGraph.Internal.HLSLDeclaration.UnityPerMaterial);
}
public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, System.Action onChange, System.Action<string> registerUndo)
{
target.AddDefaultMaterialOverrideGUI(ref context, onChange, registerUndo);
context.AddProperty("Workflow", new EnumField(WorkflowMode.Metallic) { value = WorkflowMode }, (evt) =>
{
if (Equals(WorkflowMode, evt.newValue))
return;
registerUndo("Change Workflow");
WorkflowMode = (WorkflowMode)evt.newValue;
onChange();
});
target.GetDefaultSurfacePropertiesGUI(ref context, onChange, registerUndo);
context.AddProperty("Transparent Receives Shadows", new Toggle() { value = TransparentReceiveShadows }, (evt) =>
{
if (Equals(TransparentReceiveShadows, evt.newValue))
return;
registerUndo("Change Transparent Receives Shadows");
TransparentReceiveShadows = evt.newValue;
onChange();
});
context.AddProperty("Fragment Normal Space", new EnumField(NormalDropOffSpace.Tangent) { value = NormalDropOffSpace }, (evt) =>
{
if (Equals(NormalDropOffSpace, evt.newValue))
return;
registerUndo("Change Fragment Normal Space");
NormalDropOffSpace = (NormalDropOffSpace)evt.newValue;
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
onChange();
});
}
static class SubShaders
{
static readonly string s_ShaderPathDefines = $"{k_ShaderPath}/Defines.hlsl";
static readonly string s_ShaderPathBuilding = $"{k_ShaderPath}/LegacyBuilding.hlsl";
// SetShaderPassEnabled on ShadowCaster pass does not work for BIRP. We set ZTest
// to Never which is the best we can do. We are still incurring the draw call cost.
// This is an issue because of the way we trigger motion vectors, but is a bug with
// Unity and should be reported.
internal const string k_ShadowCasterZTest = "_Crest_BUILTIN_ShadowCasterZTest";
internal static System.Type s_SubShadersType;
internal static System.Type SubShadersType => s_SubShadersType ??= typeof(UnityBuiltInLitSubTarget).GetNestedType("SubShaders", BindingFlags.Static | BindingFlags.NonPublic);
internal static MethodInfo s_LitMethod;
internal static MethodInfo LitMethod => s_LitMethod ??= SubShadersType.GetMethod("Lit", BindingFlags.Static | BindingFlags.Public);
static void PatchIncludes(ref PassDescriptor result)
{
var includes = new IncludeCollection();
includes.Add(s_ShaderPathDefines, IncludeLocation.Pregraph);
includes.Add("Packages/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/Editor/ShaderGraph/Includes/ShaderPass.hlsl", IncludeLocation.Pregraph);
foreach (var include in result.includes)
{
includes.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
}
result.includes = includes;
}
static void PatchSpecularIncludes(ref PassDescriptor result, string file)
{
var ic = new IncludeCollection();
foreach (var include in result.includes)
{
if (include.path.EndsWith(file))
{
ic.Add(s_ShaderPathBuilding, include.location);
ic.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
}
else
{
ic.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
}
}
result.includes = ic;
}
static readonly Dictionary<string, string> s_Mappings = new()
{
{ "SHADERPASS_FORWARD", "PBRForwardPass.hlsl" },
{ "SHADERPASS_FORWARD_ADD", "PBRForwardAddPass.hlsl" },
{ "SHADERPASS_DEFERRED", "PBRDeferredPass.hlsl" },
};
static readonly string[] s_SkipVariants = new string[]
{
"LIGHTMAP_ON",
"LIGHTMAP_SHADOW_MIXING",
"DIRLIGHTMAP_COMBINED",
"DYNAMICLIGHTMAP_ON",
"SHADOWS_SHADOWMASK",
};
public static SubShaderDescriptor Lit(BuiltInLitSubTarget subtarget)
{
var target = subtarget.target;
var ssd = (SubShaderDescriptor)LitMethod.Invoke(null, new object[] { target, target.renderType, target.renderQueue });
PassCollection passes = new();
foreach (var item in ssd.passes)
{
// Many artifacts in U6 if our Write Depth enabled.
// Caused by _SURFACE_TYPE_TRANSPARENT in m_ValidKeywords.
if (item.descriptor.referenceName == "SceneSelectionPass")
{
continue;
}
var result = item.descriptor;
var keywords = new KeywordCollection();
foreach (var keyword in result.keywords)
{
// All others are either duplicate or unused.
if (!keyword.descriptor.referenceName.StartsWith("_BUILTIN_"))
{
continue;
}
keywords.Add(keyword.descriptor, keyword.fieldConditions);
}
result.keywords = keywords;
switch (item.descriptor.referenceName)
{
case "SHADERPASS_FORWARD":
case "SHADERPASS_FORWARD_ADD":
case "SHADERPASS_DEFERRED":
AddWorkflowModeControlToPass(ref result, target, subtarget.WorkflowMode);
PatchSpecularIncludes(ref result, s_Mappings[item.descriptor.referenceName]);
var pragmas = new PragmaCollection();
foreach (var pragma in result.pragmas)
{
// For UAVs (RWStructuredBuffer).
if (pragma.descriptor.value.StartsWithNoAlloc("target"))
{
pragmas.Add(Pragma.Target(ShaderModel.Target45));
continue;
}
if (pragma.descriptor.value.StartsWithNoAlloc("vertex"))
{
pragmas.Add(Pragma.SkipVariants(s_SkipVariants));
}
pragmas.Add(pragma.descriptor, pragma.fieldConditions);
}
result.pragmas = pragmas;
goto default;
default:
PatchIncludes(ref result);
break;
}
switch (item.descriptor.referenceName)
{
case "SHADERPASS_FORWARD":
case "SHADERPASS_FORWARD_ADD":
AddReceivesShadowsControlToPass(ref result, target, subtarget.TransparentReceiveShadows);
break;
case "SHADERPASS_SHADOWCASTER":
var states = new RenderStateCollection();
foreach (var state in result.renderStates)
{
if (state.descriptor.type == RenderStateType.ZTest)
{
states.Add(RenderState.ZTest($"[{k_ShadowCasterZTest}]"));
continue;
}
states.Add(state.descriptor, state.fieldConditions);
}
result.renderStates = states;
break;
}
// Add missing cull render state.
if (item.descriptor.referenceName == "SHADERPASS_FORWARD_ADD")
{
CoreRenderStates.AddUberSwitchedCull(target, result.renderStates);
}
// Inject MV before DO pass.
if (item.descriptor.referenceName == "SHADERPASS_DEPTHONLY")
{
var mv = LitPasses.MotionVectors(target);
PatchIncludes(ref mv);
passes.Add(mv);
}
// Fix XR SPI.
if (result.requiredFields != null)
{
var found = false;
foreach (var collection in result.requiredFields)
{
if (collection.field == StructFields.Attributes.instanceID)
{
found = true;
break;
}
}
if (!found)
{
result.requiredFields.Add(StructFields.Attributes.instanceID);
}
}
passes.Add(result);
}
ssd.passes = passes;
return ssd;
}
static void AddWorkflowModeControlToPass(ref PassDescriptor pass, BuiltInTarget target, WorkflowMode workflowMode)
{
if (target.allowMaterialOverride)
{
pass.keywords.Add(LitDefines.s_SpecularSetup);
}
else if (workflowMode == WorkflowMode.Specular)
{
pass.defines.Add(LitDefines.s_SpecularSetup, 1);
}
}
static void AddReceivesShadowsControlToPass(ref PassDescriptor pass, BuiltInTarget target, bool receives)
{
if (target.allowMaterialOverride)
{
pass.keywords.Add(LitDefines.s_TransparentReceivesShadows);
pass.keywords.Add(LitDefines.s_ShadowsSingleCascade);
pass.keywords.Add(LitDefines.s_ShadowsSplitSpheres);
pass.keywords.Add(LitDefines.s_ShadowsSoft);
}
else if (receives)
{
pass.defines.Add(LitDefines.s_TransparentReceivesShadows, 1);
pass.keywords.Add(LitDefines.s_ShadowsSingleCascade);
pass.keywords.Add(LitDefines.s_ShadowsSplitSpheres);
pass.keywords.Add(LitDefines.s_ShadowsSoft);
}
}
}
static class LitPasses
{
static readonly string s_ShaderPathMotionVectorCommon = $"{k_ShaderPath}/MotionVectorCommon.hlsl";
static readonly string s_ShaderPathMotionVectorPass = $"{k_ShaderPath}/MotionVectorPass.hlsl";
public static RenderStateDescriptor UberSwitchedCullRenderState(BuiltInTarget target)
{
if (target.allowMaterialOverride)
{
return RenderState.Cull(CoreRenderStates.Uniforms.cullMode);
}
else
{
return RenderState.Cull(CoreRenderStates.RenderFaceToCull(target.renderFace));
}
}
public static PassDescriptor MotionVectors(BuiltInTarget target)
{
var result = new PassDescriptor()
{
// Definition
displayName = "BuiltIn MotionVectors",
referenceName = "SHADERPASS_MOTION_VECTORS",
lightMode = "MotionVectors",
useInPreview = false,
// Template
passTemplatePath = BuiltInTarget.kTemplatePath,
sharedTemplateDirectories = BuiltInTarget.kSharedTemplateDirectories.Union
(
new string[]
{
k_TemplatePath,
"Packages/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/Editor/ShaderGraph"
}
).ToArray(),
// Port Mask
validVertexBlocks = new BlockFieldDescriptor[]
{
BlockFields.VertexDescription.Position,
},
validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,
// Fields
structs = CoreStructCollections.Default,
requiredFields = new()
{
// Needed for XR, but not sure if correct.
StructFields.Attributes.instanceID,
},
fieldDependencies = CoreFieldDependencies.Default,
// Conditional State
renderStates = new()
{
{ RenderState.ZTest(ZTest.LEqual) },
{ RenderState.ZWrite(ZWrite.On) },
{ UberSwitchedCullRenderState(target) },
// MVs write to the depth buffer causing z-fighting. Luckily, the depth texture has
// already been updated, and will not be updated before water renders.
{ RenderState.ColorMask("ColorMask RG\nOffset 1, 1") },
},
pragmas = new()
{
{ Pragma.Target(ShaderModel.Target35) }, // NOTE: SM 2.0 only GL
{ Pragma.MultiCompileInstancing },
{ Pragma.Vertex("vert") },
{ Pragma.Fragment("frag") },
},
defines = new() { CoreDefines.BuiltInTargetAPI },
keywords = new(),
includes = new()
{
// Pre-graph
{ CoreIncludes.CorePregraph },
{ CoreIncludes.ShaderGraphPregraph },
// Post-graph
{ s_ShaderPathMotionVectorCommon, IncludeLocation.Postgraph },
{ CoreIncludes.CorePostgraph },
{ s_ShaderPathMotionVectorPass, IncludeLocation.Postgraph },
},
// Custom Interpolator Support
customInterpolators = CoreCustomInterpDescriptors.Common,
};
// Only support time for now.
result.defines.Add(LitDefines.s_AutomaticTimeBasedMotionVectors, 1);
CorePasses.AddAlphaClipControlToPass(ref result, target);
return result;
}
}
internal static class LitDefines
{
public static readonly KeywordDescriptor s_AutomaticTimeBasedMotionVectors = new()
{
displayName = "Automatic Time-Based Motion Vectors",
referenceName = "AUTOMATIC_TIME_BASED_MOTION_VECTORS",
type = KeywordType.Boolean,
definition = KeywordDefinition.Predefined,
scope = KeywordScope.Local,
stages = KeywordShaderStage.Vertex,
};
public static readonly KeywordDescriptor s_SpecularSetup = new()
{
displayName = "Specular Setup",
referenceName = "_BUILTIN_SPECULAR_SETUP",
type = KeywordType.Boolean,
definition = KeywordDefinition.ShaderFeature,
scope = KeywordScope.Local,
stages = KeywordShaderStage.Fragment
};
public static readonly KeywordDescriptor s_TransparentReceivesShadows = new()
{
displayName = "Transparent Receives Shadows",
referenceName = "_BUILTIN_TRANSPARENT_RECEIVES_SHADOWS",
type = KeywordType.Boolean,
definition = KeywordDefinition.ShaderFeature,
scope = KeywordScope.Local,
stages = KeywordShaderStage.Fragment
};
public static readonly KeywordDescriptor s_ShadowsSingleCascade = new()
{
displayName = "Single Cascade Shadows",
referenceName = "SHADOWS_SINGLE_CASCADE",
type = KeywordType.Boolean,
definition = KeywordDefinition.MultiCompile,
scope = KeywordScope.Global,
stages = KeywordShaderStage.All,
};
public static readonly KeywordDescriptor s_ShadowsSoft = new()
{
displayName = "Soft Shadows",
referenceName = "SHADOWS_SOFT",
type = KeywordType.Boolean,
definition = KeywordDefinition.MultiCompile,
scope = KeywordScope.Global,
stages = KeywordShaderStage.All,
};
public static readonly KeywordDescriptor s_ShadowsSplitSpheres = new()
{
displayName = "Stable Fit Shadows",
referenceName = "SHADOWS_SPLIT_SPHERES",
type = KeywordType.Boolean,
definition = KeywordDefinition.MultiCompile,
scope = KeywordScope.Global,
stages = KeywordShaderStage.All,
};
}
}
}