diff --git a/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs b/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs index 321c96e..716a6f1 100644 --- a/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs +++ b/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs @@ -17,7 +17,8 @@ public static class PlayerPositionsToShaderPreview public Vector4[] colors; public Vector4[] directions; public float[] types; - public int size; + public float[] shadowMapIndices; + public int size; } static PlayerPositionsToShaderPreview() @@ -66,16 +67,17 @@ public static class PlayerPositionsToShaderPreview static void EnsureArrays(PlayerPositionsToShader src, int required) { if (!_cache.TryGetValue(src, out var c) || - c.positions == null || c.colors == null || c.directions == null || c.types == null || + c.positions == null || c.colors == null || c.directions == null || c.types == null || c.shadowMapIndices == null || c.size != required) { c = new Cache { - positions = new Vector4[required], - colors = new Vector4[required], - directions = new Vector4[required], - types = new float[required], - size = required + positions = new Vector4[required], + colors = new Vector4[required], + directions = new Vector4[required], + types = new float[required], + shadowMapIndices = new float[required], + size = required }; _cache[src] = c; } @@ -87,25 +89,27 @@ public static class PlayerPositionsToShaderPreview EnsureArrays(src, max); var c = _cache[src]; - var positions = c.positions; - var colors = c.colors; - var directions = c.directions; - var types = c.types; + var positions = c.positions; + var colors = c.colors; + var directions = c.directions; + var types = c.types; + var shadowMapIndices = c.shadowMapIndices; // Clear arrays to safe defaults for (int i = 0; i < max; i++) { - positions[i] = Vector4.zero; - colors[i] = Vector4.zero; - directions[i] = Vector4.zero; - types[i] = 0f; + positions[i] = Vector4.zero; + colors[i] = Vector4.zero; + directions[i] = Vector4.zero; + types[i] = 0f; + shadowMapIndices[i] = 0f; } // Use the Editor-side function defined on the partial class int count = 0; try { - src.Editor_BuildPreview(out positions, out colors, out directions, out types, out count); + src.Editor_BuildPreview(out positions, out colors, out directions, out types, out shadowMapIndices, out count); // replace cache arrays if sizes changed if (positions.Length != c.size) @@ -113,11 +117,12 @@ public static class PlayerPositionsToShaderPreview _cache[src] = new Cache { - positions = positions, - colors = colors, - directions = directions, - types = types, - size = positions.Length + positions = positions, + colors = colors, + directions = directions, + types = types, + shadowMapIndices = shadowMapIndices, + size = positions.Length }; } catch @@ -152,6 +157,12 @@ public static class PlayerPositionsToShaderPreview Shader.SetGlobalFloatArray(id, types); } + if (!string.IsNullOrEmpty(src.shadowMapIndexProperty)) + { + int id = Shader.PropertyToID(src.shadowMapIndexProperty); + Shader.SetGlobalFloatArray(id, shadowMapIndices); + } + if (!string.IsNullOrEmpty(src.countProperty)) { int id = Shader.PropertyToID(src.countProperty); diff --git a/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs new file mode 100644 index 0000000..6c604c5 --- /dev/null +++ b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs @@ -0,0 +1,142 @@ +#if UNITY_EDITOR +using UnityEditor; +using UnityEngine; +using System.Collections.Generic; + +[InitializeOnLoad] +public static class ShadowcasterUpdaterPreview +{ + const double kTickInterval = 0.1; // seconds + static double _nextTick; + + // One shared MPB to avoid allocations each frame + static readonly MaterialPropertyBlock sMPB = new MaterialPropertyBlock(); + + // Cache: component -> property ID, and renderer -> last applied matrix + static readonly Dictionary _propId = new Dictionary(); + static readonly Dictionary _lastW2L = new Dictionary(); + static readonly List _toRemove = new List(32); + + static ShadowcasterUpdaterPreview() + { + EditorApplication.update += Update; + EditorApplication.hierarchyChanged += ForceTick; + Undo.undoRedoPerformed += ForceTick; + Selection.selectionChanged += ForceTick; + EditorApplication.playModeStateChanged += _ => ForceTick(); + } + + public static void ForceTick() => _nextTick = 0; + + static void Update() + { +#if UNITY_2019_1_OR_NEWER + if (EditorApplication.isPlayingOrWillChangePlaymode) return; +#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) + { + if (b == null || !b.isActiveAndEnabled) continue; + if (EditorUtility.IsPersistent(b)) continue; // skip assets/prefabs + ApplyToBehaviour(b); + } + + SceneView.RepaintAll(); + } + + static ShadowcasterUpdater[] FindAllInScene() + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); +#elif UNITY_2020_1_OR_NEWER + return Object.FindObjectsOfType(true); +#else + return Resources.FindObjectsOfTypeAll(); +#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)) + { + id = Shader.PropertyToID(name); + _propId[b] = id; + return id; + } + + // If user changed the property name in inspector, refresh the ID + int newId = Shader.PropertyToID(name); + if (newId != id) + { + _propId[b] = newId; + id = newId; + } + return id; + } + + static void ApplyToBehaviour(ShadowcasterUpdater b) + { + var renderers = b.rendererTargets; + if (renderers == null || renderers.Length == 0) return; + + int id = GetPropertyId(b); + Matrix4x4 w2l = b.transform.worldToLocalMatrix; + + for (int i = 0; i < renderers.Length; i++) + { + Renderer r = renderers[i]; + if (r == null) continue; + + 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(); + } + } +} +#endif diff --git a/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs.meta b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs.meta new file mode 100644 index 0000000..db780ad --- /dev/null +++ b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0491ab6ce27c5ba449e98288f0e3d8ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/EditorPreview/PlayerPositionsToShader.Editor.cs b/EditorPreview/PlayerPositionsToShader.Editor.cs index 9d5e592..bb57b34 100644 --- a/EditorPreview/PlayerPositionsToShader.Editor.cs +++ b/EditorPreview/PlayerPositionsToShader.Editor.cs @@ -9,44 +9,52 @@ public partial class PlayerPositionsToShader out Vector4[] colors, out Vector4[] directions, out float[] types, + out float[] shadowMapIndices, out int count) { int max = Mathf.Max(1, maxLights); - positions = new Vector4[max]; - colors = new Vector4[max]; - directions = new Vector4[max]; - types = new float[max]; - count = 0; + positions = new Vector4[max]; + colors = new Vector4[max]; + directions = new Vector4[max]; + types = new float[max]; + shadowMapIndices = new float[max]; + count = 0; - // ✅ Avoid Array.Empty(); just guard the loop - if (otherLightSources != null) + if (otherLightSources == null) return; + + for (int i = 0; i < otherLightSources.Length && count < max; i++) { - for (int i = 0; i < otherLightSources.Length && count < max; i++) - { - Transform t = otherLightSources[i]; - if (t == null || !t.gameObject.activeInHierarchy) continue; + Transform t = otherLightSources[i]; + if (t == null || !t.gameObject.activeInHierarchy) continue; - LightdataStorage data = t.GetComponent(); + LightdataStorage data = t.GetComponent(); - Vector3 pos = t.position; - float range = (data != null) ? data.range * t.localScale.x : t.localScale.x; - Vector4 col = (data != null) ? data.GetFinalColor() : new Vector4(1f, 1f, 1f, 1f); - float intens = (data != null) ? data.intensity * t.localScale.x : 1f; - float cosHalf = (data != null) ? data.GetCosHalfAngle() : 1f; - int typeId = (data != null) ? data.GetTypeId() : 0; + Vector3 pos = t.position; + float range = (data != null) ? data.range * t.localScale.x : t.localScale.x; + // rgb = color, a = intensity (packed to match runtime/shader) + Vector4 col = (data != null) ? data.GetFinalColor() : new Vector4(1f, 1f, 1f, 1f); + float intensity = (data != null) ? data.intensity * t.localScale.x : 1f; - Quaternion rot = t.rotation; - Vector3 fwd = rot * Vector3.down; + // w = cosHalfAngle (0 for omni) + float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f; - positions[count] = new Vector4(pos.x, pos.y, pos.z, range); - colors[count] = new Vector4(col.x, col.y, col.z, intens); - directions[count] = new Vector4(fwd.x, fwd.y, fwd.z, data.spotAngleDeg); - types[count] = (float)typeId; + // 0=Omni, 1=Spot, 2=Directional (your custom enum) + int typeId = (data != null) ? data.GetTypeId() : 0; - count++; - } + float shIndex = (data != null) ? data.shadowMapIndex : 0f; + + Quaternion rot = t.rotation; + Vector3 fwd = rot * Vector3.down; + + positions[count] = new Vector4(pos.x, pos.y, pos.z, range); + colors[count] = new Vector4(col.x, col.y, col.z, intensity); + directions[count] = new Vector4(fwd.x, fwd.y, fwd.z, cosHalf); + types[count] = (float)typeId; + shadowMapIndices[count] = shIndex; + + count++; } } } diff --git a/Mesh.meta b/Mesh.meta new file mode 100644 index 0000000..96703d0 --- /dev/null +++ b/Mesh.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d573c2258a40a04f9932e8910763931 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mesh/Plane.fbx b/Mesh/Plane.fbx new file mode 100644 index 0000000..4371e31 Binary files /dev/null and b/Mesh/Plane.fbx differ diff --git a/Mesh/Plane.fbx.meta b/Mesh/Plane.fbx.meta new file mode 100644 index 0000000..52d3b6f --- /dev/null +++ b/Mesh/Plane.fbx.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: 26a14a3ac3d4388458abdbca51f4efc8 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/LightdataStorage.cs b/Scripts/LightdataStorage.cs index 503f501..3c547d5 100644 --- a/Scripts/LightdataStorage.cs +++ b/Scripts/LightdataStorage.cs @@ -32,6 +32,10 @@ public class LightdataStorage : UdonSharpBehaviour [Tooltip("0 = omni (no cone)")] public float spotAngleDeg = 0f; + [Header("Shadow Settings")] + [Tooltip("0 = no shadows, 1-4 = shadow map index")] + public float shadowMapIndex = 0f; // 0 = no shadows, 1-4 = shadow map index + // Convert to a Vector4 for your shader upload public Vector4 GetFinalColor() { diff --git a/Scripts/PlayerPositionsToShader.cs b/Scripts/PlayerPositionsToShader.cs index 8e398c0..5b8b2e5 100644 --- a/Scripts/PlayerPositionsToShader.cs +++ b/Scripts/PlayerPositionsToShader.cs @@ -24,6 +24,9 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour public float playerLightIntensity = 5f; public float remoteLightIntensity = 2f; + [Tooltip("0 = no shadows, 1-4 = shadow map index")] + public float PlayerShadowMapIndex = 0f; // 0 = no shadows, 1-4 = shadow map index + [Header("Shader property names (advanced users)")] [Tooltip("Vector4 array: xyz = position, w = range")] @@ -41,6 +44,9 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour [Tooltip("float array: light type (1=area, 2=cone, etc)")] public string typeProperty = "_Udon_LightType"; + [Tooltip("float array: shadow map index (0=none, 1-4=shadow map index)")] + public string shadowMapIndexProperty = "_Udon_ShadowMapIndex"; + [Header("Max Lights (advanced users)")] [Tooltip("Hard cap / array size. 80 = default cap")] public int maxLights = 80; @@ -57,9 +63,11 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour private float[] _TypeArray; private bool _TypeArray_isDirty = false; + private float[] _ShadowMapArray; + private bool _ShadowMap_isDirty = false; private VRCPlayerApi[] _players; - private MaterialPropertyBlock _mpb; + public int currentCount { get; private set; } @@ -70,6 +78,7 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour private int UdonID_LightColors; private int UdonID_LightDirections; private int UdonID_LightType; + private int UdonID_ShadowMapIndex; void Start() { @@ -79,15 +88,16 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour _lightColors = new Vector4[maxLights]; _directions = new Vector4[maxLights]; _TypeArray = new float[maxLights]; + _ShadowMapArray = new float[maxLights]; _players = new VRCPlayerApi[maxLights]; - _mpb = new MaterialPropertyBlock(); UdonID_PlayerPositions = VRCShader.PropertyToID(positionsProperty); UdonID_LightCount = VRCShader.PropertyToID(countProperty); UdonID_LightColors = VRCShader.PropertyToID(colorProperty); UdonID_LightDirections = VRCShader.PropertyToID(directionsProperty); UdonID_LightType = VRCShader.PropertyToID(typeProperty); + UdonID_ShadowMapIndex = VRCShader.PropertyToID(shadowMapIndexProperty); UpdateData(); @@ -150,6 +160,11 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour _TypeArray[i] = 0f; _TypeArray_isDirty = true; } + if (_ShadowMapArray[i] != PlayerShadowMapIndex) + { + _ShadowMapArray[i] = PlayerShadowMapIndex; + _ShadowMap_isDirty = true; + } } @@ -175,6 +190,11 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour _TypeArray[i] = 0f; _TypeArray_isDirty = true; } + if (_ShadowMapArray[i] != 0f) + { + _ShadowMapArray[i] = 0f; + _ShadowMap_isDirty = true; + } } } @@ -230,6 +250,13 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour _TypeArray_isDirty = true; } + float shadowMapIndex = (data != null) ? data.shadowMapIndex : 0f; + if (_ShadowMapArray[currentCount] != shadowMapIndex) + { + _ShadowMapArray[currentCount] = shadowMapIndex; + _ShadowMap_isDirty = true; + } + currentCount++; } } @@ -259,6 +286,12 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour _TypeArray[i] = 0f; _TypeArray_isDirty = true; } + + if (_ShadowMapArray[i] != 0f) + { + _ShadowMapArray[i] = 0f; + _ShadowMap_isDirty = true; + } } } @@ -267,23 +300,26 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour // Snapshot which things are dirty this frame bool pushPositions = _positons_isDirty; - bool pushColors = _lightColors_isDirty; - bool pushDirs = _directions_isDirty; - bool pushTypes = _TypeArray_isDirty && !string.IsNullOrEmpty(typeProperty); + bool pushColors = _lightColors_isDirty; + bool pushDirs = _directions_isDirty; + bool pushTypes = _TypeArray_isDirty && !string.IsNullOrEmpty(typeProperty); + bool pushShadowMap = _ShadowMap_isDirty; if (pushPositions) VRCShader.SetGlobalVectorArray(UdonID_PlayerPositions, _positions); - if (pushColors) VRCShader.SetGlobalVectorArray(UdonID_LightColors, _lightColors); - if (pushDirs) VRCShader.SetGlobalVectorArray(UdonID_LightDirections, _directions); - if (pushTypes) _mpb.SetFloatArray(UdonID_LightType, _TypeArray); + if (pushColors) VRCShader.SetGlobalVectorArray(UdonID_LightColors, _lightColors); + if (pushDirs) VRCShader.SetGlobalVectorArray(UdonID_LightDirections, _directions); + if (pushTypes) VRCShader.SetGlobalFloatArray(UdonID_LightType, _TypeArray); + if (pushShadowMap) VRCShader.SetGlobalFloatArray(UdonID_ShadowMapIndex, _ShadowMapArray); VRCShader.SetGlobalFloat(UdonID_LightCount, currentCount); - + Debug.Log($"[MoonlightVRC] Pushed {currentCount} lights to shader."); // Only now mark them clean - if (pushPositions) { _positons_isDirty = false;} - if (pushColors) { _lightColors_isDirty = false;} - if (pushDirs) { _directions_isDirty = false;} - if (pushTypes) { _TypeArray_isDirty = false;} + if (pushPositions) { _positons_isDirty = false; } + if (pushColors) { _lightColors_isDirty = false; } + if (pushDirs) { _directions_isDirty = false; } + if (pushTypes) { _TypeArray_isDirty = false; } + if (pushShadowMap) { _ShadowMap_isDirty = false; } } } diff --git a/Scripts/ShadowcasterUpdater.cs b/Scripts/ShadowcasterUpdater.cs new file mode 100644 index 0000000..a1fee8a --- /dev/null +++ b/Scripts/ShadowcasterUpdater.cs @@ -0,0 +1,35 @@ + +using System.Security.Permissions; +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using VRC.Udon; + +public class ShadowcasterUpdater : UdonSharpBehaviour +{ + + public Renderer[] rendererTargets; + public string propertyName = "_Udon_WorldToLocal"; + private MaterialPropertyBlock _mpb; + + void Start() + { + _mpb = new MaterialPropertyBlock(); + } + + + void LateUpdate() + { + var w2l = transform.worldToLocalMatrix; + + + foreach (Renderer mat in rendererTargets) + { + if (mat == null) continue; + mat.GetPropertyBlock(_mpb); + _mpb.SetMatrix(propertyName, w2l); + mat.SetPropertyBlock(_mpb); + } + + } +} diff --git a/Scripts/ShadowcasterUpdater.cs.meta b/Scripts/ShadowcasterUpdater.cs.meta new file mode 100644 index 0000000..794e5aa --- /dev/null +++ b/Scripts/ShadowcasterUpdater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36c63f8382c2aad48b73fe4628db0f38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 68200cc..1de969c 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -17,6 +17,12 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 //Moonlight END + _shadowCasterTex ("Shadow Caster Texture", 2D) = "white" {} + _shadowCasterColor ("Shadow Caster Color", Color) = (1,1,1,1) + _OutSideColor ("Outside Color", Color) = (1,1,1,1) + _MinBrightnessShadow ("Min Brightness for Shadows", Range(0,1)) = 0 + + } @@ -36,6 +42,7 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" #include "Includes/Lambert.hlsl" #include "Includes/DefaultSetup.hlsl" #include "Includes/Variables.hlsl" + #include "Includes/Shadowcaster.cginc" //Moonlight Defines #define MAX_LIGHTS 80 // >= maxPlayers in script @@ -84,6 +91,12 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" MoonlightGlobalVariables + float4x4 _Udon_WorldToLocal; + sampler2D _shadowCasterTex; + float4 _shadowCasterColor; + float4 _OutSideColor; + float _MinBrightnessShadow; + v2f vert (appdata v) { @@ -140,7 +153,15 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" LightTypeCalculations(_Udon_LightColors, LightCounter, i, NdotL, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); - dmax = dmax + contrib * float4(LightColor, 1) * NdotL; // accumulate light contributions + float4 ShadowCasterMult = float4(1,1,1,1); + if (_Udon_ShadowMapIndex[LightCounter] > 0.5) { + ShadowCasterMult = SampleShadowcasterPlane(_Udon_WorldToLocal, _shadowCasterTex, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _OutSideColor); + ShadowCasterMult *= _shadowCasterColor; + ShadowCasterMult = float4(ShadowCasterMult.rgb * (1-ShadowCasterMult.a), 1); + } + + + dmax = dmax + contrib * float4(LightColor, 1) * NdotL * max(ShadowCasterMult, _MinBrightnessShadow); } diff --git a/Shader/Includes/Shadowcaster.cginc b/Shader/Includes/Shadowcaster.cginc new file mode 100644 index 0000000..0b062da --- /dev/null +++ b/Shader/Includes/Shadowcaster.cginc @@ -0,0 +1,56 @@ +#ifndef SHADOWCASTER_PLANE +#define SHADOWCASTER_PLANE + +#include "UnityCG.cginc" // for tex2Dlod, etc. + +static const float EPS = 1e-5; + +// Returns whether segment hits the unit plane quad in plane-local z=0. +// Outputs uv in [0,1] and t in [0,1] along A->B. +inline bool RaySegmentHitsPlaneQuad(float4x4 worldToLocal, float3 rayOrigin, float3 rayEnd, out float2 uv, out float t) +{ + float3 aP = mul(worldToLocal, float4(rayOrigin, 1)).xyz; + float3 bP = mul(worldToLocal, float4(rayEnd, 1)).xyz; + + float3 d = bP - aP; + float dz = d.z; + + // Parallel-ish to plane? + if (abs(dz) < EPS) return false; + + // Intersect z=0 + t = -aP.z / dz; + + // Segment only + if (t < 0.0 || t > 1.0) return false; + + float3 hit = aP + d * t; + + // Inside 1x1 centered quad? + if (abs(hit.x) > 0.5 || abs(hit.y) > 0.5) return false; + + uv = hit.xy + 0.5; // [-0.5,0.5] -> [0,1] + return true; +} + +// Fragment-shader version: uses proper filtering/mips via tex2D +inline float4 SampleShadowcasterPlane(float4x4 worldToLocal, sampler2D tex, float3 rayOrigin, float3 rayEnd, float4 OutsideColor) +{ + float2 uv; float t; + if (RaySegmentHitsPlaneQuad(worldToLocal, rayOrigin, rayEnd, uv, t)) + return tex2D(tex, uv); // full color + + return OutsideColor; +} + +// Anywhere (vertex/geom/compute/custom code) version: forces LOD 0 +inline float4 SampleShadowcasterPlaneLOD0(float4x4 worldToLocal, sampler2D tex, float3 rayOrigin, float3 rayEnd, float4 OutsideColor) +{ + float2 uv; float t; + if (RaySegmentHitsPlaneQuad(worldToLocal, rayOrigin, rayEnd, uv, t)) + return tex2Dlod(tex, float4(uv, 0, 0)); // full color at mip 0 + + return OutsideColor; +} + +#endif diff --git a/Shader/Includes/Shadowcaster.cginc.meta b/Shader/Includes/Shadowcaster.cginc.meta new file mode 100644 index 0000000..cf0d55a --- /dev/null +++ b/Shader/Includes/Shadowcaster.cginc.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 407fd8c92bce1a84ab69e3abad2320b0 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Shader/Includes/Variables.hlsl b/Shader/Includes/Variables.hlsl index 665b19f..95e74a8 100644 --- a/Shader/Includes/Variables.hlsl +++ b/Shader/Includes/Variables.hlsl @@ -8,6 +8,7 @@ float4 _Udon_LightColors[MAX_LIGHTS]; /* xyz = position */ \ float4 _Udon_LightDirections[MAX_LIGHTS]; /* xyz = direction, w = cos(halfAngle) */ \ float _Udon_LightType[MAX_LIGHTS]; /* 0 = sphere, 1 = cone */ \ + float _Udon_ShadowMapIndex[MAX_LIGHTS];\ float _Udon_PlayerCount; /* set via SetFloat */ \ #endif \ No newline at end of file