Files
Fishing2/Assets/KriptoFX/WaterSystem2/WaterResources/Scripts/Editor/KWS_ShaderStripping.cs
2025-11-16 22:37:55 +08:00

322 lines
13 KiB
C#

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace KWS.Build
{
internal sealed class KWS_PreProcessBuild : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public static readonly HashSet<string> _waterVariants = new(StringComparer.Ordinal);
public static void Clear() => _waterVariants.Clear();
public static void AddFromKeys(IEnumerable<string> keys)
{
var list = new List<string>(keys);
if (list.Count == 0) return;
list.Sort(StringComparer.Ordinal);
_waterVariants.Add(string.Join("|", list));
}
public void OnPreprocessBuild(BuildReport report)
{
KWS_VariantStubBuilder.RemoveAllMaterials();
CollectAllVariantsFromAllScenes();
var pathToResourcesFolder = KW_Extensions.GetFullPathToResourcesFolder();
if (string.IsNullOrEmpty(pathToResourcesFolder)) return;
var relativePath = KW_Extensions.GetRelativePathToBuildShaderFeatureMaterials();
KW_Extensions.WaterLog("Path: " + relativePath);
KWS_ShaderFeatureCatalog.BuildFromFolder(KW_Extensions.GetPathToWaterShadersFolder().GetRelativeToAssetsPath());
KWS_VariantStubBuilder.GenerateShaderFeatureMaterials(_waterVariants, outputFolder: relativePath, shaderFilter: s => s != null && s.name.StartsWith("Hidden/KriptoFX/KWS/"));
KW_Extensions.WaterLog($"[KWS] Generated 'ShaderFeature' Materials: {_waterVariants.Count} combinations.");
}
void CollectAllVariantsFromAllScenes()
{
var opened = new List<Scene>();
var active = SceneManager.GetActiveScene().path;
try
{
_waterVariants.Clear();
var paths = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path);
foreach (var p in paths)
{
var sc = EditorSceneManager.OpenScene(p, OpenSceneMode.Additive);
opened.Add(sc);
CollectFromScene(sc);
}
}
finally
{
foreach (var sc in opened) EditorSceneManager.CloseScene(sc, true);
if (!string.IsNullOrEmpty(active)) EditorSceneManager.OpenScene(active, OpenSceneMode.Single);
}
}
void CollectFromScene(Scene sc)
{
var managers = sc.GetRootGameObjects()
.SelectMany(go => go.GetComponentsInChildren<WaterSystem>(true))
.ToList();
if (managers.Count == 0) return;
var qualityLevels = KWS_WaterSettingsRuntimeLoader._waterSystemQualitySettings?.qualityLevelSettings;
if (qualityLevels == null || qualityLevels.Count == 0) return;
foreach (var waterManager in managers)
{
foreach (var qualityLevel in qualityLevels)
{
var variant = GetCurrentQualityKeywords(waterManager, qualityLevel);
AddFromKeys(BuildFeatureKeys(variant));
}
}
}
struct ActiveVariant
{
public bool useReflectSun;
public bool useVolumetric;
public bool useCaustic;
public bool useCausticFiltering;
public bool useCausticDispersion;
public bool useSSR;
public bool usePlanar;
public bool useRefrIOR;
public bool useRefrDisp;
public bool useUnderwater;
public bool useHalfLineTension;
}
static ActiveVariant GetCurrentQualityKeywords(WaterSystem waterSettings, WaterQualityLevelSettings qualitySettings)
{
ActiveVariant variant = default;
variant.usePlanar = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.PlanarReflection, qualitySettings.UsePlanarReflection);
variant.useSSR = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.ScreenSpaceReflection, qualitySettings.UseScreenSpaceReflection);
variant.useRefrDisp = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.RefractionDispersion, qualitySettings.UseRefractionDispersion);
variant.useVolumetric = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.VolumetricLighting, qualitySettings.UseVolumetricLight);
variant.useCaustic = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.CausticEffect, qualitySettings.UseCausticEffect);
variant.useUnderwater = WaterQualityLevelSettings.ResolveQualityOverride(waterSettings.UnderwaterEffect, qualitySettings.UseUnderwaterEffect);
variant.useHalfLineTension = waterSettings.UseUnderwaterHalfLineTensionEffect;
variant.useReflectSun = waterSettings.ReflectSun;
variant.useRefrIOR = waterSettings.RefractionMode == WaterQualityLevelSettings.RefractionModeEnum.PhysicalAproximationIOR;
variant.useCausticFiltering = qualitySettings.UseOceanCausticHighQualityFiltering;
variant.useCausticDispersion = qualitySettings.UseOceanCausticDispersion;
return variant;
}
static IEnumerable<string> BuildFeatureKeys(ActiveVariant variant)
{
if (variant.useReflectSun) yield return "KWS_REFLECT_SUN";
if (variant.useVolumetric) yield return "KWS_USE_VOLUMETRIC_LIGHT";
if (variant.useCaustic) yield return "KWS_USE_CAUSTIC";
if (variant.useCausticFiltering) yield return "KWS_USE_CAUSTIC_FILTERING";
if (variant.useCausticDispersion) yield return "KWS_USE_CAUSTIC_DISPERSION";
if (variant.useSSR) yield return "KWS_SSR_REFLECTION";
if (variant.usePlanar) yield return "KWS_USE_PLANAR_REFLECTION";
if (variant.useRefrIOR) yield return "KWS_USE_REFRACTION_IOR";
if (variant.useRefrDisp) yield return "KWS_USE_REFRACTION_DISPERSION";
if (variant.useHalfLineTension) yield return "KWS_USE_HALF_LINE_TENSION";
}
#if KWS_DEBUG
[MenuItem("KWS/Generate Water Variant Stubs (Resources)", priority = 2000)]
static void MenuGenerateGenerate()
{
var pre = new KWS_PreProcessBuild();
pre.OnPreprocessBuild(null);
KW_Extensions.WaterLog("[KWS] Variant stubs regenerated (manual).");
}
[MenuItem("KWS/Clear Water Variant Stubs (Resources)", priority = 2001)]
static void MenuGenerateClear()
{
var pre = new KWS_PostProcessBuild();
pre.OnPostprocessBuild(null);
KW_Extensions.WaterLog("[KWS] Variant stubs removed (manual).");
}
#endif
}
internal sealed class KWS_PostProcessBuild : IPostprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPostprocessBuild(BuildReport report)
{
KWS_VariantStubBuilder.RemoveAllMaterials();
}
}
internal static class KWS_VariantStubBuilder
{
public static void RemoveAllMaterials()
{
var relativePath = KW_Extensions.GetRelativePathToBuildShaderFeatureMaterials();
var guids = AssetDatabase.FindAssets("t:Material", new[] { relativePath });
foreach (var guid in guids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
AssetDatabase.DeleteAsset(assetPath);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
KW_Extensions.WaterLog("[KWS] Variant stubs removed (manual).");
}
public static void GenerateShaderFeatureMaterials(
IReadOnlyCollection<string> signatures,
string outputFolder,
Func<Shader, bool> shaderFilter)
{
EnsureFolder(outputFolder);
var oldGuids = AssetDatabase.FindAssets("t:Material", new[] { outputFolder });
foreach (var g in oldGuids)
{
var p = AssetDatabase.GUIDToAssetPath(g);
AssetDatabase.DeleteAsset(p);
}
var shaders = new List<Shader>();
foreach (var guid in AssetDatabase.FindAssets("t:Shader"))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var sh = AssetDatabase.LoadAssetAtPath<Shader>(path);
if (shaderFilter(sh)) shaders.Add(sh);
}
if (shaders.Count == 0)
{
KW_Extensions.WaterLog("[KWS] Variant materials: water shaders not found.");
return;
}
foreach (var shader in shaders)
{
if (!KWS_ShaderFeatureCatalog.FeatureByShader.TryGetValue(shader.name, out var shaderFeatures) ||
shaderFeatures.Count == 0)
continue;
foreach (var sig in signatures)
{
var allKeys = string.IsNullOrEmpty(sig)
? Array.Empty<string>()
: sig.Split('|');
var filtered = allKeys.Where(k => shaderFeatures.Contains(k))
.Distinct(StringComparer.Ordinal)
.ToArray();
if (filtered.Length == 0) continue;
var mat = new Material(shader) { name = BuildMatName(shader.name, filtered) };
foreach (var k in filtered) mat.EnableKeyword(k);
var assetPath = $"{outputFolder}/{Sanitize(mat.name)}.mat";
AssetDatabase.CreateAsset(mat, assetPath);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
static string BuildMatName(string shaderName, string[] keys)
{
var shortShader = shaderName.Replace("Hidden/", "").Replace("KriptoFX/", "").Replace("KWS/", "");
var keyPart = keys.Length == 0 ? "BASE" : string.Join("__", keys);
if (keyPart.Length > 80) keyPart = keyPart.Substring(0, 80);
return $"KWS_Stub_{shortShader}__{keyPart}";
}
static string Sanitize(string s)
{
foreach (var c in Path.GetInvalidFileNameChars()) s = s.Replace(c, '_');
return s.Replace(' ', '_');
}
static void EnsureFolder(string path)
{
if (AssetDatabase.IsValidFolder(path)) return;
var parts = path.Replace('\\', '/').Split('/');
var acc = parts[0];
for (int i = 1; i < parts.Length; i++)
{
var next = acc + "/" + parts[i];
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(acc, parts[i]);
acc = next;
}
}
}
static class KWS_ShaderFeatureCatalog
{
public static readonly Dictionary<string, HashSet<string>> FeatureByShader = new(StringComparer.Ordinal);
static readonly System.Text.RegularExpressions.Regex kFeatureLine =
new(@"^\s*#\s*pragma\s+shader_feature(?:_local)?(?:_vertex|_fragment)?\s+(.+)$",
System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.Compiled);
static readonly System.Text.RegularExpressions.Regex kToken =
new(@"[A-Za-z0-9_]+", System.Text.RegularExpressions.RegexOptions.Compiled);
public static void BuildFromFolder(string absFolderPath)
{
FeatureByShader.Clear();
foreach (var guid in AssetDatabase.FindAssets("t:Shader", new[] { absFolderPath }))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
if (!shader) continue;
var set = FeatureByShader.TryGetValue(shader.name, out var exist)
? exist
: (FeatureByShader[shader.name] = new HashSet<string>(StringComparer.Ordinal));
var text = File.ReadAllText(path);
foreach (System.Text.RegularExpressions.Match m in kFeatureLine.Matches(text))
foreach (System.Text.RegularExpressions.Match t in kToken.Matches(m.Groups[1].Value))
if (t.Value != "_")
set.Add(t.Value);
}
}
}
}
#endif