mirror of
https://github.com/DeMuenu/MoonlightVRC.git
synced 2025-12-13 19:33:56 +00:00
Refactor shadowcaster to use explicit plane basis
Replaces the use of world-to-local matrices for shadowcaster planes with explicit plane origin, basis, and normal vectors in both C# and shader code. Updates property setting in ShadowcasterUpdater and its editor preview, and rewrites the shadow sampling function in the shader include to use the new basis. Adjusts all affected shaders to use the new properties and sampling function for improved clarity and flexibility.
This commit is contained in:
@@ -1,141 +1,151 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class ShadowcasterUpdaterPreview
|
||||
public static class ShadowcasterUpdaterEditorLoop
|
||||
{
|
||||
const double kTickInterval = 0.1; // seconds
|
||||
static double _nextTick;
|
||||
private const double Interval = 0.1;
|
||||
private const float Eps = 1e-6f;
|
||||
|
||||
// One shared MPB to avoid allocations each frame
|
||||
static readonly MaterialPropertyBlock sMPB = new MaterialPropertyBlock();
|
||||
private static double _lastUpdateTime;
|
||||
private static ShadowcasterUpdater[] _cached;
|
||||
private static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock();
|
||||
|
||||
// Cache: component -> property ID, and renderer -> last applied matrix
|
||||
static readonly Dictionary<ShadowcasterUpdater, int> _propId = new Dictionary<ShadowcasterUpdater, int>();
|
||||
static readonly Dictionary<Renderer, Matrix4x4> _lastW2L = new Dictionary<Renderer, Matrix4x4>();
|
||||
static readonly List<Renderer> _toRemove = new List<Renderer>(32);
|
||||
|
||||
static ShadowcasterUpdaterPreview()
|
||||
static ShadowcasterUpdaterEditorLoop()
|
||||
{
|
||||
_lastUpdateTime = EditorApplication.timeSinceStartup;
|
||||
|
||||
EditorApplication.update += Update;
|
||||
EditorApplication.hierarchyChanged += ForceTick;
|
||||
Undo.undoRedoPerformed += ForceTick;
|
||||
Selection.selectionChanged += ForceTick;
|
||||
EditorApplication.playModeStateChanged += _ => ForceTick();
|
||||
EditorApplication.hierarchyChanged += RefreshCache;
|
||||
AssemblyReloadEvents.afterAssemblyReload += RefreshCache;
|
||||
|
||||
RefreshCache();
|
||||
}
|
||||
|
||||
public static void ForceTick() => _nextTick = 0;
|
||||
|
||||
static void Update()
|
||||
private static void RefreshCache()
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
_cached = FindSceneUpdaters();
|
||||
}
|
||||
|
||||
private static ShadowcasterUpdater[] FindSceneUpdaters()
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
return Object.FindObjectsOfType<ShadowcasterUpdater>(true);
|
||||
#else
|
||||
if (EditorApplication.isPlaying) return;
|
||||
#endif
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
if (now < _nextTick) return;
|
||||
_nextTick = now + kTickInterval;
|
||||
|
||||
CleanupNullRenderers();
|
||||
|
||||
var behaviours = FindAllInScene();
|
||||
foreach (var b in behaviours)
|
||||
// Unity 2019-compatible path: include inactive, filter out assets/prefabs not in a scene
|
||||
var all = Resources.FindObjectsOfTypeAll(typeof(ShadowcasterUpdater));
|
||||
var list = new List<ShadowcasterUpdater>(all.Length);
|
||||
foreach (var o in all)
|
||||
{
|
||||
if (b == null || !b.isActiveAndEnabled) continue;
|
||||
if (EditorUtility.IsPersistent(b)) continue; // skip assets/prefabs
|
||||
ApplyToBehaviour(b);
|
||||
var c = o as ShadowcasterUpdater;
|
||||
if (c == null) continue;
|
||||
if (EditorUtility.IsPersistent(c)) continue; // skip assets
|
||||
if (!c.gameObject.scene.IsValid()) continue;
|
||||
list.Add(c);
|
||||
}
|
||||
return list.ToArray();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
if (EditorApplication.isCompiling) return;
|
||||
|
||||
var now = EditorApplication.timeSinceStartup;
|
||||
if (now - _lastUpdateTime < Interval) return;
|
||||
_lastUpdateTime = now;
|
||||
|
||||
var arr = _cached;
|
||||
if (arr == null || arr.Length == 0) return;
|
||||
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
{
|
||||
var u = arr[i];
|
||||
if (u == null) continue;
|
||||
TryUpdate(u);
|
||||
}
|
||||
|
||||
// Make changes visible in Scene/Game view without wiggling the mouse
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
static ShadowcasterUpdater[] FindAllInScene()
|
||||
private static void TryUpdate(ShadowcasterUpdater u)
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
return Object.FindObjectsByType<ShadowcasterUpdater>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
#elif UNITY_2020_1_OR_NEWER
|
||||
return Object.FindObjectsOfType<ShadowcasterUpdater>(true);
|
||||
#else
|
||||
return Resources.FindObjectsOfTypeAll<ShadowcasterUpdater>();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void CleanupNullRenderers()
|
||||
{
|
||||
_toRemove.Clear();
|
||||
foreach (var kv in _lastW2L)
|
||||
if (kv.Key == null) _toRemove.Add(kv.Key);
|
||||
for (int i = 0; i < _toRemove.Count; i++)
|
||||
_lastW2L.Remove(_toRemove[i]);
|
||||
}
|
||||
|
||||
static int GetPropertyId(ShadowcasterUpdater b)
|
||||
{
|
||||
string name = string.IsNullOrEmpty(b.propertyName) ? "_Udon_WorldToLocal" : b.propertyName;
|
||||
|
||||
if (!_propId.TryGetValue(b, out int id))
|
||||
// If inspector values changed, make sure textures/colors/min brightness are pushed
|
||||
try
|
||||
{
|
||||
id = Shader.PropertyToID(name);
|
||||
_propId[b] = id;
|
||||
return id;
|
||||
u.ApplyTextureData();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// UdonSharp can be touchy during certain editor states. Ignore and continue.
|
||||
}
|
||||
|
||||
// If user changed the property name in inspector, refresh the ID
|
||||
int newId = Shader.PropertyToID(name);
|
||||
if (newId != id)
|
||||
var targets = u.rendererTargets;
|
||||
if (targets == null || targets.Length == 0) return;
|
||||
|
||||
// Match the runtime script's plane definition (0.5 half-size before scaling)
|
||||
const float quadHalfWidth = 0.5f;
|
||||
const float quadHalfHeight = 0.5f;
|
||||
|
||||
Transform t = u.transform;
|
||||
|
||||
// World-space basis from transform
|
||||
Vector3 Udir = t.rotation * Vector3.right; // local +X
|
||||
Vector3 Vdir = t.rotation * Vector3.up; // local +Y
|
||||
|
||||
// Half extents after non-uniform scaling
|
||||
float halfW = Mathf.Max(quadHalfWidth * t.lossyScale.x, Eps);
|
||||
float halfH = Mathf.Max(quadHalfHeight * t.lossyScale.y, Eps);
|
||||
|
||||
// Reciprocal axes so dot(r, Uinv/Vinv) -> [-0.5, 0.5]
|
||||
Vector3 Uinv = Udir / (2.0f * halfW);
|
||||
Vector3 Vinv = Vdir / (2.0f * halfH);
|
||||
|
||||
// Unit normal
|
||||
Vector3 N = Vector3.Normalize(Vector3.Cross(Udir, Vdir));
|
||||
|
||||
int idx = Mathf.Max(0, u.shadowcasterIndex);
|
||||
string suf = "_" + idx.ToString();
|
||||
|
||||
int idShadowTex = Shader.PropertyToID("_Udon_shadowCasterTex" + suf);
|
||||
int idShadowColor = Shader.PropertyToID("_Udon_shadowCasterColor" + suf);
|
||||
int idOutsideColor = Shader.PropertyToID("_Udon_OutSideColor" + suf);
|
||||
int idMinBrightness = Shader.PropertyToID("_Udon_MinBrightnessShadow" + suf);
|
||||
|
||||
int idPlaneOrigin = Shader.PropertyToID("_Udon_Plane_Origin_" + idx.ToString());
|
||||
int idPlaneUinv = Shader.PropertyToID("_Udon_Plane_Uinv_" + idx.ToString());
|
||||
int idPlaneVinv = Shader.PropertyToID("_Udon_Plane_Vinv_" + idx.ToString());
|
||||
int idPlaneNormal = Shader.PropertyToID("_Udon_Plane_Normal_" + idx.ToString());
|
||||
|
||||
Vector4 origin = new Vector4(t.position.x, t.position.y, t.position.z, 0);
|
||||
Vector4 uinv4 = new Vector4(Uinv.x, Uinv.y, Uinv.z, 0);
|
||||
Vector4 vinv4 = new Vector4(Vinv.x, Vinv.y, Vinv.z, 0);
|
||||
Vector4 n4 = new Vector4(N.x, N.y, N.z, 0);
|
||||
|
||||
for (int r = 0; r < targets.Length; r++)
|
||||
{
|
||||
_propId[b] = newId;
|
||||
id = newId;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
var ren = targets[r];
|
||||
if (ren == null) continue;
|
||||
|
||||
static void ApplyToBehaviour(ShadowcasterUpdater b)
|
||||
{
|
||||
var renderers = b.rendererTargets;
|
||||
if (renderers == null || renderers.Length == 0) return;
|
||||
ren.GetPropertyBlock(_mpb);
|
||||
|
||||
int id = GetPropertyId(b);
|
||||
Matrix4x4 w2l = b.transform.worldToLocalMatrix;
|
||||
// Also mirror texture/color in case ApplyTextureData couldn't run
|
||||
if (u.ShadowcasterTexture != null) _mpb.SetTexture(idShadowTex, u.ShadowcasterTexture);
|
||||
_mpb.SetColor(idShadowColor, u.TextureColor);
|
||||
_mpb.SetColor(idOutsideColor, u.OutsideColor);
|
||||
_mpb.SetFloat(idMinBrightness, u.MinBrightness);
|
||||
|
||||
for (int i = 0; i < renderers.Length; i++)
|
||||
{
|
||||
Renderer r = renderers[i];
|
||||
if (r == null) continue;
|
||||
// Plane data
|
||||
_mpb.SetVector(idPlaneOrigin, origin);
|
||||
_mpb.SetVector(idPlaneUinv, uinv4);
|
||||
_mpb.SetVector(idPlaneVinv, vinv4);
|
||||
_mpb.SetVector(idPlaneNormal, n4);
|
||||
|
||||
if (_lastW2L.TryGetValue(r, out var last) && last == w2l)
|
||||
continue; // nothing changed for this renderer
|
||||
|
||||
r.GetPropertyBlock(sMPB);
|
||||
sMPB.SetMatrix(id, w2l);
|
||||
r.SetPropertyBlock(sMPB);
|
||||
|
||||
_lastW2L[r] = w2l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(ShadowcasterUpdater))]
|
||||
public class ShadowcasterUpdaterInspector : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
GUILayout.Space(6);
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.LabelField("Edit-Mode Preview", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("Keeps the matrix property updated in the Scene View.");
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Refresh Now"))
|
||||
{
|
||||
ShadowcasterUpdaterPreview.ForceTick();
|
||||
EditorApplication.QueuePlayerLoopUpdate();
|
||||
SceneView.RepaintAll();
|
||||
ren.SetPropertyBlock(_mpb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user