ui基本完毕,修了一大把的bug
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
// Bakes a Unity Gradient into a 1D ramp texture (width x 1) that can be assigned
|
||||
// to the Dissolve URP material's "Gradient Ramp" (_GradientTex) slot. The dissolve
|
||||
// edge value samples this ramp along its U axis, so the left of the gradient maps to
|
||||
// the inner edge and the right maps to the outer edge of the dissolve.
|
||||
public class DissolveGradientRampBaker : EditorWindow
|
||||
{
|
||||
[SerializeField] private Gradient gradient = CreateDefaultGradient();
|
||||
[SerializeField] private int width = 256;
|
||||
[SerializeField] private bool hdr = true;
|
||||
[SerializeField] private string outputFolder = "Assets/Rainbow-Cats-Unity-Dissolve-HDR-Shaders-main/Materials";
|
||||
[SerializeField] private string fileName = "DissolveGradientRamp";
|
||||
|
||||
private SerializedObject serialized;
|
||||
|
||||
[MenuItem("Bansonic/Rendering/Dissolve Gradient Ramp Baker")]
|
||||
public static void Open()
|
||||
{
|
||||
GetWindow<DissolveGradientRampBaker>("Gradient Ramp Baker").minSize = new Vector2(360f, 260f);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
serialized = new SerializedObject(this);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
serialized.Update();
|
||||
|
||||
EditorGUILayout.LabelField("Bake a Gradient into a ramp texture", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"Assign the baked texture to the material's 'Gradient Ramp' slot. " +
|
||||
"The dissolve edge samples it left-to-right.",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.PropertyField(serialized.FindProperty(nameof(gradient)));
|
||||
EditorGUILayout.PropertyField(serialized.FindProperty(nameof(width)));
|
||||
EditorGUILayout.PropertyField(serialized.FindProperty(nameof(hdr)),
|
||||
new GUIContent("HDR (float texture)"));
|
||||
EditorGUILayout.PropertyField(serialized.FindProperty(nameof(outputFolder)));
|
||||
EditorGUILayout.PropertyField(serialized.FindProperty(nameof(fileName)));
|
||||
|
||||
serialized.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Bake Ramp Texture", GUILayout.Height(32f)))
|
||||
Bake();
|
||||
}
|
||||
|
||||
private void Bake()
|
||||
{
|
||||
int w = Mathf.Clamp(width, 2, 4096);
|
||||
var format = hdr ? TextureFormat.RGBAFloat : TextureFormat.RGBA32;
|
||||
var tex = new Texture2D(w, 1, format, false, true)
|
||||
{
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
filterMode = FilterMode.Bilinear
|
||||
};
|
||||
|
||||
var pixels = new Color[w];
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
float t = w == 1 ? 0f : (float)x / (w - 1);
|
||||
pixels[x] = gradient.Evaluate(t);
|
||||
}
|
||||
tex.SetPixels(pixels);
|
||||
tex.Apply(false, false);
|
||||
|
||||
if (!AssetDatabase.IsValidFolder(outputFolder))
|
||||
{
|
||||
Debug.LogError($"[DissolveGradientRampBaker] Output folder does not exist: {outputFolder}");
|
||||
Object.DestroyImmediate(tex);
|
||||
return;
|
||||
}
|
||||
|
||||
string extension = hdr ? "exr" : "png";
|
||||
byte[] bytes = hdr
|
||||
? tex.EncodeToEXR(Texture2D.EXRFlags.OutputAsFloat)
|
||||
: tex.EncodeToPNG();
|
||||
string assetPath = $"{outputFolder}/{fileName}.{extension}";
|
||||
assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
|
||||
File.WriteAllBytes(assetPath, bytes);
|
||||
Object.DestroyImmediate(tex);
|
||||
|
||||
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
|
||||
ConfigureImporter(assetPath);
|
||||
|
||||
var imported = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
EditorGUIUtility.PingObject(imported);
|
||||
Selection.activeObject = imported;
|
||||
Debug.Log($"[DissolveGradientRampBaker] Baked ramp to {assetPath}");
|
||||
}
|
||||
|
||||
private void ConfigureImporter(string assetPath)
|
||||
{
|
||||
if (AssetImporter.GetAtPath(assetPath) is not TextureImporter importer)
|
||||
return;
|
||||
|
||||
importer.textureType = TextureImporterType.Default;
|
||||
importer.wrapMode = TextureWrapMode.Clamp;
|
||||
importer.filterMode = FilterMode.Bilinear;
|
||||
importer.mipmapEnabled = false;
|
||||
importer.sRGBTexture = !hdr;
|
||||
importer.alphaSource = TextureImporterAlphaSource.FromInput;
|
||||
importer.alphaIsTransparency = false;
|
||||
importer.SaveAndReimport();
|
||||
}
|
||||
|
||||
private static Gradient CreateDefaultGradient()
|
||||
{
|
||||
var g = new Gradient();
|
||||
g.SetKeys(
|
||||
new[]
|
||||
{
|
||||
new GradientColorKey(new Color(1f, 0.35f, 0.1f), 0f),
|
||||
new GradientColorKey(new Color(1f, 0.9f, 0.3f), 0.5f),
|
||||
new GradientColorKey(new Color(0.3f, 0.7f, 1f), 1f)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GradientAlphaKey(1f, 0f),
|
||||
new GradientAlphaKey(1f, 1f)
|
||||
});
|
||||
return g;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9821637dad915c344b817ce763d891ae
|
||||
@@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
// Compiles the FX / sprite / particle shaders used by gameplay during the preload
|
||||
// screen so the first note (or first VFX / UI blur) does not pay the variant compile
|
||||
// cost mid-gameplay.
|
||||
//
|
||||
// Progress: WarmupRoutine drives compilation across frames and reports a localized
|
||||
// progress string ("正在编译着色器 xx%") through the onProgress callback so the caller
|
||||
// (preload) can surface it on the loading overlay. When nothing needs compiling
|
||||
// (cache valid) it reports the idle default ("加载中...").
|
||||
//
|
||||
// Persistence: after compiling, a validation manifest is written to a project-local
|
||||
// ShaderCache folder (never Application.persistentDataPath / the C: AppData path). On
|
||||
// the next load the manifest is compared against the current shader sources + GPU +
|
||||
// build. If everything matches, compilation is skipped; if any shader source changed
|
||||
// (or the GPU / build changed, invalidating previously compiled variants) the shaders
|
||||
// are recompiled and the manifest refreshed.
|
||||
public static class GlobalFxShaderWarmer
|
||||
{
|
||||
public const string IdleLoadingText = "加载中...";
|
||||
private const string CompilingTextFormat = "正在编译着色器 {0}%";
|
||||
|
||||
[Serializable]
|
||||
private class ShaderEntry
|
||||
{
|
||||
public string name;
|
||||
public string sourceHash;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class WarmManifest
|
||||
{
|
||||
public string unityVersion;
|
||||
public string appVersion;
|
||||
public string graphicsDevice;
|
||||
public string graphicsDeviceType;
|
||||
public ShaderEntry[] shaders;
|
||||
}
|
||||
|
||||
// A shader to warm plus its source path relative to Application.dataPath (the
|
||||
// Assets folder). sourcePath may be null for engine/built-in shaders that have no
|
||||
// file under Assets; those are still gated by the Unity/GPU/build fingerprint.
|
||||
private readonly struct WarmTarget
|
||||
{
|
||||
public readonly string ShaderName;
|
||||
public readonly string SourcePath;
|
||||
|
||||
public WarmTarget(string shaderName, string sourcePath)
|
||||
{
|
||||
ShaderName = shaderName;
|
||||
SourcePath = sourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Order is stable and used directly for manifest comparison.
|
||||
private static readonly WarmTarget[] Targets =
|
||||
{
|
||||
// Global full-screen FX blit shaders (noteFunction effects).
|
||||
new WarmTarget("Hidden/Bansonic/GlobalGlitch", "Shaders/GlobalGlitch.shader"),
|
||||
new WarmTarget("Hidden/Bansonic/GlobalMonochrome", "Shaders/GlobalMonochrome.shader"),
|
||||
new WarmTarget("Hidden/Bansonic/GlobalDistortion", "Shaders/GlobalDistortion.shader"),
|
||||
new WarmTarget("Hidden/Bansonic/GlobalAfterimage", "Shaders/GlobalAfterimage.shader"),
|
||||
|
||||
// Explicitly requested particle shaders.
|
||||
new WarmTarget("Cartoon FX/Remaster/Particle Ubershader",
|
||||
"JMO Assets/Cartoon FX Remaster/CFXR Assets/Shaders/CFXR Particle Ubershader.cfxrshader"),
|
||||
new WarmTarget("Mobile/Particles/Additive", null),
|
||||
|
||||
// Other CFXR particle shaders used across gameplay VFX.
|
||||
new WarmTarget("Cartoon FX/Remaster/Particle Procedural Glow",
|
||||
"JMO Assets/Cartoon FX Remaster/CFXR Assets/Shaders/CFXR Particle Glow.cfxrshader"),
|
||||
new WarmTarget("Cartoon FX/Remaster/Particle Screen Distortion",
|
||||
"JMO Assets/Cartoon FX Remaster/CFXR Assets/Shaders/CFXR Particle Distortion.cfxrshader"),
|
||||
new WarmTarget("Cartoon FX/Remaster/Particle Procedural Ring",
|
||||
"JMO Assets/Cartoon FX Remaster/CFXR Assets/Shaders/CFXR Particle Procedural Ring.cfxrshader"),
|
||||
|
||||
// Legacy Cartoon FX particle shaders still referenced by prefabs.
|
||||
new WarmTarget("Cartoon FX/Legacy/Particles Additive Alpha8",
|
||||
"JMO Assets/Cartoon FX (legacy)/Shaders/CFXM_MobileParticleAdd_Alpha8.shader"),
|
||||
new WarmTarget("Cartoon FX/Legacy/Particle Multiply Colored",
|
||||
"JMO Assets/Cartoon FX (legacy)/Shaders/CFX3 Multiply Color.shader"),
|
||||
|
||||
// Custom sprite/UI shaders used by gameplay materials.
|
||||
new WarmTarget("Bansonic/Sprite Glow", "Shaders/BansonicSpriteGlow.shader"),
|
||||
new WarmTarget("Bansonic/Sprite Light Curtain", "Shaders/BansonicSpriteLightCurtain.shader"),
|
||||
new WarmTarget("Bansonic/Sprite Vertical Glow Only", "Shaders/BansonicSpriteVerticalGlowOnly.shader"),
|
||||
new WarmTarget("Sprites/Outline", "SpriteGlow/Resources/SpriteGlow/Shaders/SpriteOutline.shader"),
|
||||
new WarmTarget("Custom/SphereGradientShader", "sphereShaders/SphereGradient.shader"),
|
||||
new WarmTarget("UI/Soft Shadow", "Shaders/UI/UIShadow.shader"),
|
||||
new WarmTarget("UI/UIGaussianBlur", "Shaders/UI/UIGaussianBlur.shader"),
|
||||
new WarmTarget("UI/Bansonic/Blur Behind", "Shaders/UI/BansonicUIBlurBehind.shader")
|
||||
};
|
||||
|
||||
private const string ManifestFolder = "ShaderCache";
|
||||
private const string ManifestFileName = "fx_shader_warm_manifest.json";
|
||||
|
||||
private static bool s_warmed;
|
||||
|
||||
// Coroutine entry point: warms shaders across frames and reports progress. Safe to
|
||||
// call once per process; subsequent calls are no-ops (guarded by s_warmed).
|
||||
public static IEnumerator WarmupRoutine(Action<string> onProgress)
|
||||
{
|
||||
if (s_warmed)
|
||||
{
|
||||
onProgress?.Invoke(IdleLoadingText);
|
||||
yield break;
|
||||
}
|
||||
s_warmed = true;
|
||||
|
||||
WarmManifest current = BuildCurrentManifest();
|
||||
WarmManifest cached = TryLoadManifest();
|
||||
|
||||
if (cached != null && ManifestsMatch(cached, current))
|
||||
{
|
||||
// Shaders (and GPU/build) unchanged: the driver's persistent pipeline cache
|
||||
// still holds valid binaries, so skip the redundant compile this process.
|
||||
Debug.Log("[GlobalFxShaderWarmer] Cached warm manifest valid; skipping shader recompile.");
|
||||
onProgress?.Invoke(IdleLoadingText);
|
||||
yield break;
|
||||
}
|
||||
|
||||
RenderTexture temp = null;
|
||||
RenderTexture previousActive = RenderTexture.active;
|
||||
int count = Targets.Length;
|
||||
try
|
||||
{
|
||||
temp = RenderTexture.GetTemporary(4, 4, 0);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int percent = Mathf.Clamp(Mathf.RoundToInt((float)i / count * 100f), 0, 100);
|
||||
onProgress?.Invoke(string.Format(CompilingTextFormat, percent));
|
||||
CompileOne(Targets[i].ShaderName, temp);
|
||||
|
||||
// Yield so the compile spikes are spread over frames and the progress
|
||||
// text can refresh instead of blocking on one long frame.
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (temp != null)
|
||||
RenderTexture.ReleaseTemporary(temp);
|
||||
RenderTexture.active = previousActive;
|
||||
}
|
||||
|
||||
SaveManifest(current);
|
||||
onProgress?.Invoke(IdleLoadingText);
|
||||
Debug.Log(cached == null
|
||||
? "[GlobalFxShaderWarmer] No cache found; compiled shaders and wrote manifest."
|
||||
: "[GlobalFxShaderWarmer] Shader source or device changed; recompiled shaders and refreshed manifest.");
|
||||
}
|
||||
|
||||
private static void CompileOne(string shaderName, RenderTexture temp)
|
||||
{
|
||||
Shader shader = Shader.Find(shaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogWarning("[GlobalFxShaderWarmer] Shader not found: " + shaderName);
|
||||
return;
|
||||
}
|
||||
|
||||
Material material = CoreUtils.CreateEngineMaterial(shader);
|
||||
if (material == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Blit pass 0 to force the shader's variant to compile. Even for sprite /
|
||||
// particle shaders this triggers program compilation; the throwaway 4x4
|
||||
// render is discarded. Use a real source texture so URP's Blit does not
|
||||
// warn about a null source.
|
||||
Graphics.Blit(Texture2D.blackTexture, temp, material, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[GlobalFxShaderWarmer] Warmup blit failed for " + shaderName + ": " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CoreUtils.Destroy(material);
|
||||
}
|
||||
}
|
||||
|
||||
private static WarmManifest BuildCurrentManifest()
|
||||
{
|
||||
var entries = new ShaderEntry[Targets.Length];
|
||||
for (int i = 0; i < Targets.Length; i++)
|
||||
{
|
||||
entries[i] = new ShaderEntry
|
||||
{
|
||||
name = Targets[i].ShaderName,
|
||||
sourceHash = ComputeSourceHash(Targets[i].SourcePath)
|
||||
};
|
||||
}
|
||||
|
||||
return new WarmManifest
|
||||
{
|
||||
unityVersion = Application.unityVersion,
|
||||
appVersion = Application.version,
|
||||
graphicsDevice = SystemInfo.graphicsDeviceName,
|
||||
graphicsDeviceType = SystemInfo.graphicsDeviceType.ToString(),
|
||||
shaders = entries
|
||||
};
|
||||
}
|
||||
|
||||
// Hashes the .shader source so an edit invalidates the cache. Engine/built-in
|
||||
// shaders (sourcePath == null) or a stripped player where the file is absent hash
|
||||
// to empty; validity there is gated by the build version + GPU fingerprint.
|
||||
private static string ComputeSourceHash(string relativePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
string full = Path.Combine(Application.dataPath, relativePath);
|
||||
if (!File.Exists(full))
|
||||
return string.Empty;
|
||||
|
||||
byte[] bytes = File.ReadAllBytes(full);
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(bytes);
|
||||
var sb = new StringBuilder(hash.Length * 2);
|
||||
foreach (byte b in hash)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[GlobalFxShaderWarmer] Failed to hash shader source '" + relativePath + "': " + ex.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ManifestsMatch(WarmManifest a, WarmManifest b)
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
|
||||
if (a.unityVersion != b.unityVersion
|
||||
|| a.appVersion != b.appVersion
|
||||
|| a.graphicsDevice != b.graphicsDevice
|
||||
|| a.graphicsDeviceType != b.graphicsDeviceType)
|
||||
return false;
|
||||
|
||||
if (a.shaders == null || b.shaders == null || a.shaders.Length != b.shaders.Length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < a.shaders.Length; i++)
|
||||
{
|
||||
ShaderEntry ea = a.shaders[i];
|
||||
ShaderEntry eb = b.shaders[i];
|
||||
if (ea == null || eb == null)
|
||||
return false;
|
||||
if (ea.name != eb.name || ea.sourceHash != eb.sourceHash)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetManifestPath()
|
||||
{
|
||||
// Project-local folder only. Deliberately not Application.persistentDataPath
|
||||
// (that lives under C:/Users/.../AppData) per the requirement to stay off C:.
|
||||
string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
string dir = Path.Combine(projectRoot, ManifestFolder);
|
||||
Directory.CreateDirectory(dir);
|
||||
return Path.Combine(dir, ManifestFileName);
|
||||
}
|
||||
|
||||
private static WarmManifest TryLoadManifest()
|
||||
{
|
||||
try
|
||||
{
|
||||
string path = GetManifestPath();
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
string json = File.ReadAllText(path);
|
||||
if (string.IsNullOrEmpty(json))
|
||||
return null;
|
||||
|
||||
return JsonUtility.FromJson<WarmManifest>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[GlobalFxShaderWarmer] Failed to read warm manifest: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveManifest(WarmManifest manifest)
|
||||
{
|
||||
try
|
||||
{
|
||||
string path = GetManifestPath();
|
||||
string json = JsonUtility.ToJson(manifest, true);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[GlobalFxShaderWarmer] Failed to write warm manifest: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6ba0a08c1b219f4e9eb2af1707a5510
|
||||
Reference in New Issue
Block a user