From 868e713336e6d448d91e0ad2dcff4d3864dda09d Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:14:38 +0200 Subject: [PATCH 1/7] Add shadowcaster support to lighting and shaders Introduces shadowcaster support by adding shadow map index fields to light data, updating PlayerPositionsToShader and LightdataStorage to handle shadow map indices, and extending the shader and its includes to sample shadowcaster planes. Adds ShadowcasterUpdater script and editor preview for updating world-to-local matrices, and updates relevant arrays and property handling throughout the codebase. Also adds a sample plane mesh for shadowcasting. --- .../Editor/PlayerPositionsToShaderPreview.cs | 53 ++++--- .../Editor/ShadowcasterUpdaterPreview.cs | 142 ++++++++++++++++++ .../Editor/ShadowcasterUpdaterPreview.cs.meta | 11 ++ .../PlayerPositionsToShader.Editor.cs | 60 ++++---- Mesh.meta | 8 + Mesh/Plane.fbx | Bin 0 -> 11580 bytes Mesh/Plane.fbx.meta | 109 ++++++++++++++ Scripts/LightdataStorage.cs | 4 + Scripts/PlayerPositionsToShader.cs | 62 ++++++-- Scripts/ShadowcasterUpdater.cs | 35 +++++ Scripts/ShadowcasterUpdater.cs.meta | 11 ++ Shader/BlendinShader.shader | 23 ++- Shader/Includes/Shadowcaster.cginc | 56 +++++++ Shader/Includes/Shadowcaster.cginc.meta | 7 + Shader/Includes/Variables.hlsl | 1 + 15 files changed, 521 insertions(+), 61 deletions(-) create mode 100644 EditorPreview/Editor/ShadowcasterUpdaterPreview.cs create mode 100644 EditorPreview/Editor/ShadowcasterUpdaterPreview.cs.meta create mode 100644 Mesh.meta create mode 100644 Mesh/Plane.fbx create mode 100644 Mesh/Plane.fbx.meta create mode 100644 Scripts/ShadowcasterUpdater.cs create mode 100644 Scripts/ShadowcasterUpdater.cs.meta create mode 100644 Shader/Includes/Shadowcaster.cginc create mode 100644 Shader/Includes/Shadowcaster.cginc.meta 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 0000000000000000000000000000000000000000..4371e312c3cee84f87299a7d10b27467a7187d73 GIT binary patch literal 11580 zcmc&)eQX@X6<-HCc5F9*TPV=9Tp+;-1m^;z;iHmszKfG1=MTKLn?!D5eK)o@+1ows z?wNBz6bk&;imFuNL$v?30#!&Ulp;|iD5^?CTcx5!6)i#$Z9}3!2`yC-A8LPZW_EAR zzTG?9e{|&a?#;~m&Ag9!@6GJ)Hx>$pYa|8-4kQLl+i({W3GEW?)CNuabGsIn9<=Xc zMuE9|Dn7G4)3K+}*n+DK*Lh*{By*`Td*&QXt0+A>(IaRh@Q0PQ^?U5RyHN5$H@;9} zSu|<)5VS1kr(I_7R4>H5eo(316D79@vj_uE7*P-fs zKm;h~WQ>LE7g5=(RJQJO%5Js?wf#!%+9A^}`z*T)^bC$@OLF>Nufq3p=k#MO!G`gZnRlgN$hc;4B! zB;Ts!4NwfM4u2xcj zuD0GNTS?8Ks0;Yf32_IdV~F_%Va-ML{-)B7>pA4H{fa+)w^)ZIQcT6Ot_X#WczjBHSJlQL_&4AX}sh183*Eb`(~yqKa((D zK^tJ!Qx`YC5~PzrY6bZwfkfIGam`t*UW=;^ES8+IT@ceitF{~6iG>_4jC~1i0mYrE zQpqy&e0@#A;vi?0BlOe+4lRSgCI<+GbpkDKU^UJYP5FSs_Bfm#wYg zizF42tWv{wOXyKCn~S5MWB7|v@gF4m9)XU?5H)cgSDMlBcGwvts0|Cq3u_<)ovu*H zPfP51f&C@iLiJR_|~O!#^brf-}g7wkYrC=?W>7a|`B7RWBBJF8OnG}>1c&n5R$9;F{xM>GrFn(N&p*@WE6uM!KWFZ+gW0WmFjY#Sm5pLyk=!ev$P|hQ9 z9EEQwA)kULx){@CFw*(qlK`SIYs8X z3mf51>SkWFfTXRV$)F$(33KX)9ErIFFYkU91Vw8hi#FgHmHc zCnamrmr_c1OBwd(+H}@}oW?CV+#XxevdEbeU_UfKhO!?Q^g)Yg%<&CcnIkhZ9`jeK z=!Ycm%>tNo6#$Q#^UjKK-&=}HQ68z`>gGELe?o%Diusa8B<`xF`z^qd()$D~d4h4* zus!GmA{MT?p4pb3#AXrZaVrhOoa*T)-C9*m-V zs}bd)D9R03Eitn-Dv`6DY(yD3+wV4_jGXN;NvSx$U_p(zI1c1o)>+}**ya+KU&e(M zPP_Dg4k=rn4LWYlESmm^ZPj(W=&wykjGF`omS7dbH)m#S=6MGiCtaHM!zO?ZHUso_ z6F`TW0osf#pTv_CtkJTd?NW}}jj1>7z5~=mB6EC7k4Ie=%9pZ;6@=YaC92|!Mjg*z zEo>Lg;E2_$a4qun@s7v<;UX@BxvatqJsX3%au@Uu$>dhSB>8b=u8}KxGu8#kraoEZ za-NDMi(JbO#*oFhomXPXA}85`Q{5=uj7sDrcS*7>LXu?LS$mGTrqA>3h=jtCREnFD zaWvk>eg!(RvAtq6%A_RSDopHR9Ct>5HfLdH`!kpbit;c5P!gvim+DL-4eb6nsz}xS zB91CjbMHq}#aLG_&iNwvGAfZX{8}7UW`8OEQ%Yt1pko@Gr% z*pK5enhLJ{IK7UUM^kb2-Z~gYWqG{39FNfyFI|@|SmkGW>PEMHy*5{nt>usM}&^9Z(UoW~N=4iug7W>+Hh684NYT)Bo2Hy;nOC<-V z!UR|6Oy|j36l`*W^X!z5SLf68Jq5mYs6@YUp;riRN|ejRTeUgcPSQ$AFJJGx?UxdJANoP?l(Q0c z7^#+2phIKS3pR-v4+>Pu-v~8`QHy^s!K@ddCV)v|yGCGAmZlurE&5pp@o`DNN6=#v zr_d*4dTHS7SdVGqxb%-73ev-J%UiSslBHdoA$vVoF6W@`3S1q*(QodJ%*#8eBY&mD zU$ts2K_fR6I^ex!Gj$QA=5#KoR;myj$#!BA((09%%aq>NCD}5i_nai7EPBnvWT8Xyx?A3t-p+L5~1)>*A_`zWREqa4SpbyZ5x*@7wHz93)x_kPW=9E!Tk`N4ye5h4 za$NXW60bFKA8&Yv`zIc=qMoR5oaj3Q$RSN_~06+c^nk&=&%pha--p^ZN0tP z-IFh7K+8+`Z~=xRTbJ3&3i1rX+b>aIVKlcc+!~yTkj>N-9}s~dbq?XZy;(J~2b8vT zylvb#B0hWlk!&J+ZKwBLaBk_AW3raqT6?;TuSxN(AuVqCjCUe6gV+3+*jD}7m3C~K zJ_*1Z*^Q_tJ>#@VYf0-g;>bv->RdpyuWq*>V21sJ?)?58Wym(-&AS^lfjGc{i~Urc zPcUmhK6?Hq>wbUik261=|M=bw|GDBW(MaD8|G&zIhrWII&40gs*E^@X|5|=vYuEn( D{BuXL literal 0 HcmV?d00001 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 From 7a3065211e23d8e9dac02b1a1b4166b522951eec Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:20:22 +0200 Subject: [PATCH 2/7] Add shadow caster support to LitParticles shader Introduced shadow caster texture, color, outside color, and minimum brightness parameters to the LitParticles shader. Moved related variable declarations to Variables.hlsl for reuse and removed redundant declarations from BlendinShader.shader. Updated LitParticles lighting calculation to apply shadow caster effects when appropriate. --- Shader/BlendinShader.shader | 9 ++------- Shader/Includes/Variables.hlsl | 8 ++++++++ Shader/LitParticles.shader | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 1de969c..ec85d5e 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -15,12 +15,13 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _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 + //Moonlight END @@ -91,12 +92,6 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" MoonlightGlobalVariables - float4x4 _Udon_WorldToLocal; - sampler2D _shadowCasterTex; - float4 _shadowCasterColor; - float4 _OutSideColor; - float _MinBrightnessShadow; - v2f vert (appdata v) { diff --git a/Shader/Includes/Variables.hlsl b/Shader/Includes/Variables.hlsl index 95e74a8..bb89d05 100644 --- a/Shader/Includes/Variables.hlsl +++ b/Shader/Includes/Variables.hlsl @@ -11,4 +11,12 @@ float _Udon_ShadowMapIndex[MAX_LIGHTS];\ float _Udon_PlayerCount; /* set via SetFloat */ \ + + float4x4 _Udon_WorldToLocal; \ + sampler2D _shadowCasterTex; \ + float4 _shadowCasterColor; \ + float4 _OutSideColor; \ + float _MinBrightnessShadow; \ + + #endif \ No newline at end of file diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index e4debd5..ba6d357 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -9,6 +9,12 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 + + + _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 //Moonlight END @@ -36,6 +42,7 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" #include "Includes/Lambert.hlsl" #include "Includes/DefaultSetup.hlsl" #include "Includes/Variables.hlsl" + #include "Includes/Shadowcaster.cginc" @@ -118,7 +125,14 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" 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) * 1 * max(ShadowCasterMult, _MinBrightnessShadow); } From fd5eb748e256bfb77d673b7985d26e0d0cbc0249 Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:59:56 +0200 Subject: [PATCH 3/7] Add support for multiple shadowcaster planes Extended ShadowcasterUpdater and shaders to handle multiple shadowcaster planes by introducing indexed properties and logic for two shadowcaster sets. Updated property names and shader logic to support per-index shadowcaster textures, colors, and brightness, enabling more flexible shadow casting in scenes. --- Scripts/ShadowcasterUpdater.cs | 25 +++++++++++++++++++++++-- Shader/BlendinShader.shader | 32 +++++++++++++++++++++++++------- Shader/Includes/Variables.hlsl | 8 -------- Shader/LitParticles.shader | 34 ++++++++++++++++++++++++++-------- 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/Scripts/ShadowcasterUpdater.cs b/Scripts/ShadowcasterUpdater.cs index a1fee8a..f688a92 100644 --- a/Scripts/ShadowcasterUpdater.cs +++ b/Scripts/ShadowcasterUpdater.cs @@ -9,15 +9,36 @@ public class ShadowcasterUpdater : UdonSharpBehaviour { public Renderer[] rendererTargets; + public Texture2D ShadowcasterTexture; + public Color OutsideColor = Color.white; + public Color TextureColor = Color.white; + public float MinBrightness = 0.0f; + + public int shadowcasterIndex = 1; + public string propertyName = "_Udon_WorldToLocal"; private MaterialPropertyBlock _mpb; + void Start() { _mpb = new MaterialPropertyBlock(); + ApplyTextureData(); } - + public void ApplyTextureData() + { + foreach (Renderer mat in rendererTargets) + { + if (mat == null) continue; + mat.GetPropertyBlock(_mpb); + _mpb.SetTexture("_Udon_ShadowcasterTex" + "_" + (string)shadowcasterIndex, ShadowcasterTexture); + _mpb.SetColor("_Udon_shadowCasterColor" + "_" + (string)shadowcasterIndex, TextureColor); + _mpb.SetColor("_Udon_OutSideColor" + "_" + (string)shadowcasterIndex, OutsideColor); + _mpb.SetFloat("_Udon_MinBrightnessShadow" + "_" + (string)shadowcasterIndex, MinBrightness); + mat.SetPropertyBlock(_mpb); + } + } void LateUpdate() { var w2l = transform.worldToLocalMatrix; @@ -27,7 +48,7 @@ public class ShadowcasterUpdater : UdonSharpBehaviour { if (mat == null) continue; mat.GetPropertyBlock(_mpb); - _mpb.SetMatrix(propertyName, w2l); + _mpb.SetMatrix(propertyName + "_" + (string)shadowcasterIndex, w2l); mat.SetPropertyBlock(_mpb); } diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index ec85d5e..89d642e 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -92,6 +92,17 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" MoonlightGlobalVariables + float4x4 _Udon_WorldToLocal_1; + sampler2D _Udon_shadowCasterTex_1; + float4 _Udon_shadowCasterColor_1; + float4 _Udon_OutSideColor_1; + float _Udon_MinBrightnessShadow_1; + + float4x4 _Udon_WorldToLocal_2; + sampler2D _Udon_shadowCasterTex_2; + float4 _Udon_shadowCasterColor_2; + float4 _Udon_OutSideColor_2; + float _Udon_MinBrightnessShadow_2; v2f vert (appdata v) { @@ -148,15 +159,22 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" LightTypeCalculations(_Udon_LightColors, LightCounter, i, NdotL, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); - 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); + float4 ShadowCasterMult_1 = float4(1,1,1,1); + float4 ShadowCasterMult_2 = float4(1,1,1,1); + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) { + ShadowCasterMult_1 = SampleShadowcasterPlane(_Udon_WorldToLocal_1, _Udon_shadowCasterTex_1, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_1); + ShadowCasterMult_1 *= _Udon_shadowCasterColor_1; + ShadowCasterMult_1 = float4(ShadowCasterMult_1.rgb * (1-ShadowCasterMult_1.a), 1); + ShadowCasterMult_1 = max(ShadowCasterMult_1, _Udon_MinBrightnessShadow_1) + } + if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { + ShadowCasterMult_2 = SampleShadowcasterPlane(_Udon_WorldToLocal_2, _Udon_shadowCasterTex_2, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_2); + ShadowCasterMult_2 *= _Udon_shadowCasterColor_2; + ShadowCasterMult_2 = float4(ShadowCasterMult_2.rgb * (1-ShadowCasterMult_2.a), 1); + ShadowCasterMult_2 = max(ShadowCasterMult_2, _Udon_MinBrightnessShadow_2) } - - dmax = dmax + contrib * float4(LightColor, 1) * NdotL * max(ShadowCasterMult, _MinBrightnessShadow); + dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2; } diff --git a/Shader/Includes/Variables.hlsl b/Shader/Includes/Variables.hlsl index bb89d05..95e74a8 100644 --- a/Shader/Includes/Variables.hlsl +++ b/Shader/Includes/Variables.hlsl @@ -11,12 +11,4 @@ float _Udon_ShadowMapIndex[MAX_LIGHTS];\ float _Udon_PlayerCount; /* set via SetFloat */ \ - - float4x4 _Udon_WorldToLocal; \ - sampler2D _shadowCasterTex; \ - float4 _shadowCasterColor; \ - float4 _OutSideColor; \ - float _MinBrightnessShadow; \ - - #endif \ No newline at end of file diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index ba6d357..f4ea031 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -85,6 +85,17 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" MoonlightGlobalVariables + float4x4 _Udon_WorldToLocal_1; + sampler2D _Udon_shadowCasterTex_1; + float4 _Udon_shadowCasterColor_1; + float4 _Udon_OutSideColor_1; + float _Udon_MinBrightnessShadow_1; + + float4x4 _Udon_WorldToLocal_2; + sampler2D _Udon_shadowCasterTex_2; + float4 _Udon_shadowCasterColor_2; + float4 _Udon_OutSideColor_2; + float _Udon_MinBrightnessShadow_2; v2f vert (appdata v) { @@ -121,18 +132,25 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" InLoopSetup(_Udon_LightPositions, LightCounter, count, i); //defines distanceFromLight, contrib - Lambert(_Udon_LightPositions[LightCounter].xyz ,i, N); //defines NdotL - LightTypeCalculations(_Udon_LightColors, LightCounter, i, NdotL, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); + LightTypeCalculations(_Udon_LightColors, LightCounter, i, 1, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); - 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); + float4 ShadowCasterMult_1 = float4(1,1,1,1); + float4 ShadowCasterMult_2 = float4(1,1,1,1); + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) { + ShadowCasterMult_1 = SampleShadowcasterPlane(_Udon_WorldToLocal_1, _Udon_shadowCasterTex_1, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_1); + ShadowCasterMult_1 *= _Udon_shadowCasterColor_1; + ShadowCasterMult_1 = float4(ShadowCasterMult_1.rgb * (1-ShadowCasterMult_1.a), 1); + ShadowCasterMult_1 = max(ShadowCasterMult_1, _Udon_MinBrightnessShadow_1) + } + if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { + ShadowCasterMult_2 = SampleShadowcasterPlane(_Udon_WorldToLocal_2, _Udon_shadowCasterTex_2, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_2); + ShadowCasterMult_2 *= _Udon_shadowCasterColor_2; + ShadowCasterMult_2 = float4(ShadowCasterMult_2.rgb * (1-ShadowCasterMult_2.a), 1); + ShadowCasterMult_2 = max(ShadowCasterMult_2, _Udon_MinBrightnessShadow_2) } - dmax = dmax + contrib * float4(LightColor, 1) * 1 * max(ShadowCasterMult, _MinBrightnessShadow); + dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult1 * ShadowCasterMult_2; } From 061ac7fdc6273589dc1c5e14fffa9a0f47e81293 Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:02:05 +0200 Subject: [PATCH 4/7] Remove unused shadow properties from shaders Deleted shadow-related properties from BlendinShader.shader and LitParticles.shader to clean up unused parameters and simplify shader property blocks. --- Shader/BlendinShader.shader | 6 ------ Shader/LitParticles.shader | 6 ------ 2 files changed, 12 deletions(-) diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 89d642e..706ee4c 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -15,12 +15,6 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 - - - _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 //Moonlight END diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index f4ea031..c8e7a02 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -9,12 +9,6 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 - - - _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 //Moonlight END From 0e633983cf7e8b64f41a26c0b3827409e8c469f7 Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:26:33 +0200 Subject: [PATCH 5/7] 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. --- .../Editor/ShadowcasterUpdaterPreview.cs | 228 +++++++++--------- Scripts/ShadowcasterUpdater.cs | 36 ++- Shader/BlendinShader.shader | 43 ++-- Shader/Includes/Shadowcaster.cginc | 62 ++--- Shader/LitParticles.shader | 47 ++-- 5 files changed, 225 insertions(+), 191 deletions(-) diff --git a/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs index 6c604c5..4403b87 100644 --- a/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs +++ b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs @@ -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 _propId = new Dictionary(); - static readonly Dictionary _lastW2L = new Dictionary(); - static readonly List _toRemove = new List(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(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(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(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)) + // 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); } } } diff --git a/Scripts/ShadowcasterUpdater.cs b/Scripts/ShadowcasterUpdater.cs index f688a92..14c11cb 100644 --- a/Scripts/ShadowcasterUpdater.cs +++ b/Scripts/ShadowcasterUpdater.cs @@ -16,7 +16,6 @@ public class ShadowcasterUpdater : UdonSharpBehaviour public int shadowcasterIndex = 1; - public string propertyName = "_Udon_WorldToLocal"; private MaterialPropertyBlock _mpb; @@ -32,23 +31,44 @@ public class ShadowcasterUpdater : UdonSharpBehaviour { if (mat == null) continue; mat.GetPropertyBlock(_mpb); - _mpb.SetTexture("_Udon_ShadowcasterTex" + "_" + (string)shadowcasterIndex, ShadowcasterTexture); - _mpb.SetColor("_Udon_shadowCasterColor" + "_" + (string)shadowcasterIndex, TextureColor); - _mpb.SetColor("_Udon_OutSideColor" + "_" + (string)shadowcasterIndex, OutsideColor); - _mpb.SetFloat("_Udon_MinBrightnessShadow" + "_" + (string)shadowcasterIndex, MinBrightness); + _mpb.SetTexture("_Udon_shadowCasterTex" + "_" + shadowcasterIndex.ToString(), ShadowcasterTexture); + _mpb.SetColor("_Udon_shadowCasterColor" + "_" + shadowcasterIndex.ToString(), TextureColor); + _mpb.SetColor("_Udon_OutSideColor" + "_" + shadowcasterIndex.ToString(), OutsideColor); + _mpb.SetFloat("_Udon_MinBrightnessShadow" + "_" + shadowcasterIndex.ToString(), MinBrightness); mat.SetPropertyBlock(_mpb); } } void LateUpdate() { - var w2l = transform.worldToLocalMatrix; - foreach (Renderer mat in rendererTargets) { if (mat == null) continue; mat.GetPropertyBlock(_mpb); - _mpb.SetMatrix(propertyName + "_" + (string)shadowcasterIndex, w2l); + + float quadHalfWidth = 0.5f; + float quadHalfHeight = 0.5f; + + // World-space basis directions from transform + Vector3 Udir = transform.rotation * Vector3.right; // plane local +X + Vector3 Vdir = transform.rotation * Vector3.up; // plane local +Y + + // World-space half extents after non-uniform scaling + float halfW = quadHalfWidth * transform.lossyScale.x; + float halfH = quadHalfHeight * transform.lossyScale.y; + + // Reciprocal axes so dot(r, Uinv/Vinv) -> [-0.5, 0.5] + Vector3 Uinv = Udir / (2.0f * Mathf.Max(halfW, 1e-6f)); + Vector3 Vinv = Vdir / (2.0f * Mathf.Max(halfH, 1e-6f)); + + // Unit normal + Vector3 N = Vector3.Normalize(Vector3.Cross(Udir, Vdir)); + + _mpb.SetVector("_Udon_Plane_Origin_" + shadowcasterIndex.ToString(), new Vector4(transform.position.x, transform.position.y, transform.position.z, 0)); + _mpb.SetVector("_Udon_Plane_Uinv_" + shadowcasterIndex.ToString(), new Vector4(Uinv.x, Uinv.y, Uinv.z, 0)); + _mpb.SetVector("_Udon_Plane_Vinv_" + shadowcasterIndex.ToString(), new Vector4(Vinv.x, Vinv.y, Vinv.z, 0)); + _mpb.SetVector("_Udon_Plane_Normal_" + shadowcasterIndex.ToString(), new Vector4(N.x, N.y, N.z, 0)); + mat.SetPropertyBlock(_mpb); } diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 706ee4c..435939f 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -86,13 +86,22 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" MoonlightGlobalVariables - float4x4 _Udon_WorldToLocal_1; + + float4 _Udon_Plane_Origin_1; // xyz = origin (world), w unused + float4 _Udon_Plane_Uinv_1; // xyz = Udir / (2*halfWidth) + float4 _Udon_Plane_Vinv_1; // xyz = Vdir / (2*halfHeight) + float4 _Udon_Plane_Normal_1; // xyz = unit normal + sampler2D _Udon_shadowCasterTex_1; float4 _Udon_shadowCasterColor_1; float4 _Udon_OutSideColor_1; float _Udon_MinBrightnessShadow_1; - float4x4 _Udon_WorldToLocal_2; + float4 _Udon_Plane_Origin_2; + float4 _Udon_Plane_Uinv_2; + float4 _Udon_Plane_Vinv_2; + float4 _Udon_Plane_Normal_2; + sampler2D _Udon_shadowCasterTex_2; float4 _Udon_shadowCasterColor_2; float4 _Udon_OutSideColor_2; @@ -152,20 +161,26 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" Lambert(_Udon_LightPositions[LightCounter].xyz ,i, N); //defines NdotL LightTypeCalculations(_Udon_LightColors, LightCounter, i, NdotL, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); + + float4 ShadowCasterMult_1 = 1; + float4 ShadowCasterMult_2 = 1; - float4 ShadowCasterMult_1 = float4(1,1,1,1); - float4 ShadowCasterMult_2 = float4(1,1,1,1); - if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) { - ShadowCasterMult_1 = SampleShadowcasterPlane(_Udon_WorldToLocal_1, _Udon_shadowCasterTex_1, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_1); - ShadowCasterMult_1 *= _Udon_shadowCasterColor_1; - ShadowCasterMult_1 = float4(ShadowCasterMult_1.rgb * (1-ShadowCasterMult_1.a), 1); - ShadowCasterMult_1 = max(ShadowCasterMult_1, _Udon_MinBrightnessShadow_1) + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) + { + float4 sc1 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_1.xyz, _Udon_Plane_Uinv_1.xyz, _Udon_Plane_Vinv_1.xyz, _Udon_Plane_Normal_1.xyz, + _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1); + ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { - ShadowCasterMult_2 = SampleShadowcasterPlane(_Udon_WorldToLocal_2, _Udon_shadowCasterTex_2, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_2); - ShadowCasterMult_2 *= _Udon_shadowCasterColor_2; - ShadowCasterMult_2 = float4(ShadowCasterMult_2.rgb * (1-ShadowCasterMult_2.a), 1); - ShadowCasterMult_2 = max(ShadowCasterMult_2, _Udon_MinBrightnessShadow_2) + + if (_Udon_ShadowMapIndex[LightCounter] > 1.5) + { + float4 sc2 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, + _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2); + ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2); } dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2; diff --git a/Shader/Includes/Shadowcaster.cginc b/Shader/Includes/Shadowcaster.cginc index 0b062da..f5b2c7d 100644 --- a/Shader/Includes/Shadowcaster.cginc +++ b/Shader/Includes/Shadowcaster.cginc @@ -3,54 +3,30 @@ #include "UnityCG.cginc" // for tex2Dlod, etc. -static const float EPS = 1e-5; +static const float WS_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) +inline float4 SampleShadowcasterPlaneWS_Basis( + float3 A, float3 B, + float3 P0, float3 Uinv, float3 Vinv, float3 N, + sampler2D tex, float4 OutsideColor, float4 ShadowColor) { - float3 aP = mul(worldToLocal, float4(rayOrigin, 1)).xyz; - float3 bP = mul(worldToLocal, float4(rayEnd, 1)).xyz; + float3 d = B - A; + float dn = dot(N, d); + if (abs(dn) < WS_EPS) return OutsideColor; - float3 d = bP - aP; - float dz = d.z; + float t = dot(N, P0 - A) / dn; + if (t < 0.0 || t > 1.0) return OutsideColor; + float3 hit = A + d * t; + float3 r = hit - P0; - // Parallel-ish to plane? - if (abs(dz) < EPS) return false; + // u,v in [-0.5, 0.5] if inside quad + float u = dot(r, Uinv); + float v = dot(r, Vinv); + if (abs(u) > 0.5 || abs(v) > 0.5) return OutsideColor; - // 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; + float4 returnColor = tex2D(tex, float2(u + 0.5, v + 0.5)) * ShadowColor; + returnColor = float4(returnColor.rgb * (1 - returnColor.a), 1); + return returnColor; } #endif diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index c8e7a02..ec82bce 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -79,18 +79,25 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" MoonlightGlobalVariables - float4x4 _Udon_WorldToLocal_1; + float4 _Udon_Plane_Origin_1; // xyz = origin (world), w unused + float4 _Udon_Plane_Uinv_1; // xyz = Udir / (2*halfWidth) + float4 _Udon_Plane_Vinv_1; // xyz = Vdir / (2*halfHeight) + float4 _Udon_Plane_Normal_1; // xyz = unit normal + sampler2D _Udon_shadowCasterTex_1; float4 _Udon_shadowCasterColor_1; float4 _Udon_OutSideColor_1; float _Udon_MinBrightnessShadow_1; - float4x4 _Udon_WorldToLocal_2; + float4 _Udon_Plane_Origin_2; + float4 _Udon_Plane_Uinv_2; + float4 _Udon_Plane_Vinv_2; + float4 _Udon_Plane_Normal_2; + sampler2D _Udon_shadowCasterTex_2; float4 _Udon_shadowCasterColor_2; float4 _Udon_OutSideColor_2; float _Udon_MinBrightnessShadow_2; - v2f vert (appdata v) { v2f o; @@ -129,22 +136,28 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" LightTypeCalculations(_Udon_LightColors, LightCounter, i, 1, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); - float4 ShadowCasterMult_1 = float4(1,1,1,1); - float4 ShadowCasterMult_2 = float4(1,1,1,1); - if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) { - ShadowCasterMult_1 = SampleShadowcasterPlane(_Udon_WorldToLocal_1, _Udon_shadowCasterTex_1, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_1); - ShadowCasterMult_1 *= _Udon_shadowCasterColor_1; - ShadowCasterMult_1 = float4(ShadowCasterMult_1.rgb * (1-ShadowCasterMult_1.a), 1); - ShadowCasterMult_1 = max(ShadowCasterMult_1, _Udon_MinBrightnessShadow_1) - } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { - ShadowCasterMult_2 = SampleShadowcasterPlane(_Udon_WorldToLocal_2, _Udon_shadowCasterTex_2, _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_OutSideColor_2); - ShadowCasterMult_2 *= _Udon_shadowCasterColor_2; - ShadowCasterMult_2 = float4(ShadowCasterMult_2.rgb * (1-ShadowCasterMult_2.a), 1); - ShadowCasterMult_2 = max(ShadowCasterMult_2, _Udon_MinBrightnessShadow_2) + float4 ShadowCasterMult_1 = 1; + float4 ShadowCasterMult_2 = 1; + + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) + { + float4 sc1 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_1.xyz, _Udon_Plane_Uinv_1.xyz, _Udon_Plane_Vinv_1.xyz, _Udon_Plane_Normal_1.xyz, + _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1); + ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult1 * ShadowCasterMult_2; + if (_Udon_ShadowMapIndex[LightCounter] > 1.5) + { + float4 sc2 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, + _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2); + ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2); + } + + dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2; } From 8e740bd636b3264e6a127a0574143f0f8a935f8f Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:48:44 +0200 Subject: [PATCH 6/7] Add shadowcaster blur support to shaders Introduces a _BlurPixels property and related parameters to BlendinShader and LitParticles shaders, enabling blurred shadowcaster sampling. Updates Shadowcaster.cginc to support blurred texture sampling using tex2Dgrad when blur is enabled. Also adjusts light intensity calculation in LightStrength.hlsl to remove NdotL multiplication for consistency with new shadow handling. --- Shader/BlendinShader.shader | 13 +++++++++---- Shader/Includes/LightStrength.hlsl | 4 ++-- Shader/Includes/Shadowcaster.cginc | 24 +++++++++++++++++++----- Shader/LitParticles.shader | 13 ++++++++++--- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 435939f..8233348 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -15,6 +15,8 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 + + _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 //Moonlight END @@ -107,6 +109,10 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" float4 _Udon_OutSideColor_2; float _Udon_MinBrightnessShadow_2; + float _BlurPixels; + float4 _Udon_shadowCasterTex_1_TexelSize; // xy = 1/width, 1/height + float4 _Udon_shadowCasterTex_2_TexelSize; + v2f vert (appdata v) { v2f o; @@ -170,20 +176,19 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" float4 sc1 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_Plane_Origin_1.xyz, _Udon_Plane_Uinv_1.xyz, _Udon_Plane_Vinv_1.xyz, _Udon_Plane_Normal_1.xyz, - _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1); + _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1, _BlurPixels, _Udon_shadowCasterTex_1_TexelSize.xy); ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { float4 sc2 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, - _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2); + _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2, _BlurPixels, _Udon_shadowCasterTex_2_TexelSize.xy); ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2); } - dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2; + dmax = dmax + contrib * float4(LightColor, 1) * NdotL * ShadowCasterMult_1 * ShadowCasterMult_2; } diff --git a/Shader/Includes/LightStrength.hlsl b/Shader/Includes/LightStrength.hlsl index 61b520e..d9119d0 100644 --- a/Shader/Includes/LightStrength.hlsl +++ b/Shader/Includes/LightStrength.hlsl @@ -6,7 +6,7 @@ { \ contrib = _Udon_LightColors[LightCounter].a / max(1e-4, max(0, max(1, distanceFromLight - radius) * invSqMul) * max(0, max(1, distanceFromLight - radius) * invSqMul)); \ \ - dIntensity += contrib * NdotL; \ + dIntensity += contrib; \ } \ else if (_Udon_LightType[LightCounter] == 1) \ { \ @@ -17,7 +17,7 @@ contrib= 1 - step(threshold, contrib); \ \ contrib = contrib * invSq; \ - dIntensity += contrib * NdotL; \ + dIntensity += contrib; \ } \ float3 LightColor = _Udon_LightColors[LightCounter].xyz; \ diff --git a/Shader/Includes/Shadowcaster.cginc b/Shader/Includes/Shadowcaster.cginc index f5b2c7d..f986308 100644 --- a/Shader/Includes/Shadowcaster.cginc +++ b/Shader/Includes/Shadowcaster.cginc @@ -1,14 +1,15 @@ #ifndef SHADOWCASTER_PLANE #define SHADOWCASTER_PLANE -#include "UnityCG.cginc" // for tex2Dlod, etc. +#include "UnityCG.cginc" // tex2D, tex2Dgrad static const float WS_EPS = 1e-5; inline float4 SampleShadowcasterPlaneWS_Basis( float3 A, float3 B, float3 P0, float3 Uinv, float3 Vinv, float3 N, - sampler2D tex, float4 OutsideColor, float4 ShadowColor) + sampler2D tex, float4 OutsideColor, float4 ShadowColor, + float blurPixels, float2 texelSize) { float3 d = B - A; float dn = dot(N, d); @@ -16,6 +17,7 @@ inline float4 SampleShadowcasterPlaneWS_Basis( float t = dot(N, P0 - A) / dn; if (t < 0.0 || t > 1.0) return OutsideColor; + float3 hit = A + d * t; float3 r = hit - P0; @@ -24,9 +26,21 @@ inline float4 SampleShadowcasterPlaneWS_Basis( float v = dot(r, Vinv); if (abs(u) > 0.5 || abs(v) > 0.5) return OutsideColor; - float4 returnColor = tex2D(tex, float2(u + 0.5, v + 0.5)) * ShadowColor; - returnColor = float4(returnColor.rgb * (1 - returnColor.a), 1); - return returnColor; + float2 uv = float2(u + 0.5, v + 0.5); + + // If blur is tiny, do the normal one-tap + if (blurPixels <= 0.001) + { + float4 col = tex2D(tex, uv) * ShadowColor; + return float4(col.rgb * (1 - col.a), 1); + } + + // Inflate gradients so the sampler picks a higher mip (cheap blur). + float2 g = texelSize * blurPixels; + float4 blurred = tex2Dgrad(tex, uv, float2(g.x, 0), float2(0, g.y)); + + float4 outCol = blurred * ShadowColor; + return float4(outCol.rgb * (1 - outCol.a), 1); } #endif diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index ec82bce..abd5eae 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -9,6 +9,9 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" //Moonlight _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 + + + _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 //Moonlight END @@ -98,6 +101,11 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" float4 _Udon_shadowCasterColor_2; float4 _Udon_OutSideColor_2; float _Udon_MinBrightnessShadow_2; + + float _BlurPixels; + float4 _Udon_shadowCasterTex_1_TexelSize; // xy = 1/width, 1/height + float4 _Udon_shadowCasterTex_2_TexelSize; + v2f vert (appdata v) { v2f o; @@ -144,16 +152,15 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" float4 sc1 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_Plane_Origin_1.xyz, _Udon_Plane_Uinv_1.xyz, _Udon_Plane_Vinv_1.xyz, _Udon_Plane_Normal_1.xyz, - _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1); + _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1, _BlurPixels, _Udon_shadowCasterTex_1_TexelSize.xy); ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) { float4 sc2 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, - _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2); + _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2, _BlurPixels, _Udon_shadowCasterTex_2_TexelSize.xy); ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2); } From 6981c3985999a12aa4320b3e4b716792198218f1 Mon Sep 17 00:00:00 2001 From: DeMuenu <96650288+DeMuenu@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:03:25 +0200 Subject: [PATCH 7/7] Add shadow casting toggle to shaders Introduced an _EnableShadowCasting property to BlendinShader, LitParticles, and Water shaders, allowing shadow casting to be enabled or disabled via a material parameter. Updated shadow sampling logic to respect this toggle, and added related parameters and includes where necessary. Also added a Cull Mode property to BlendinShader for improved rendering control. --- Shader/BlendinShader.shader | 9 +++++-- Shader/LitParticles.shader | 9 ++++--- Shader/Water.shader | 52 +++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/Shader/BlendinShader.shader b/Shader/BlendinShader.shader index 8233348..83c70a6 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -16,9 +16,11 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 + _EnableShadowCasting ("Enable Shadowcasting", Float) = 0 _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 //Moonlight END + [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2 @@ -27,6 +29,7 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" { Tags { "RenderType"="Opaque" } LOD 100 + Cull[_Cull] Pass { @@ -113,6 +116,8 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" float4 _Udon_shadowCasterTex_1_TexelSize; // xy = 1/width, 1/height float4 _Udon_shadowCasterTex_2_TexelSize; + bool _EnableShadowCasting; + v2f vert (appdata v) { v2f o; @@ -171,7 +176,7 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" float4 ShadowCasterMult_1 = 1; float4 ShadowCasterMult_2 = 1; - if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5) && (_EnableShadowCasting > 0.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5 && _EnableShadowCasting)) { float4 sc1 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, @@ -179,7 +184,7 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1, _BlurPixels, _Udon_shadowCasterTex_1_TexelSize.xy); ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) + if (_Udon_ShadowMapIndex[LightCounter] > 1.5 && (_EnableShadowCasting > 0.5)) { float4 sc2 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index abd5eae..893a091 100644 --- a/Shader/LitParticles.shader +++ b/Shader/LitParticles.shader @@ -10,7 +10,7 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 - + _EnableShadowCasting ("Enable Shadowcasting", Float) = 0 _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 //Moonlight END @@ -106,6 +106,8 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" float4 _Udon_shadowCasterTex_1_TexelSize; // xy = 1/width, 1/height float4 _Udon_shadowCasterTex_2_TexelSize; + bool _EnableShadowCasting; + v2f vert (appdata v) { v2f o; @@ -147,7 +149,7 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" float4 ShadowCasterMult_1 = 1; float4 ShadowCasterMult_2 = 1; - if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5)) + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5) && (_EnableShadowCasting > 0.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5 && _EnableShadowCasting)) { float4 sc1 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, @@ -155,8 +157,7 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1, _BlurPixels, _Udon_shadowCasterTex_1_TexelSize.xy); ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); } - if (_Udon_ShadowMapIndex[LightCounter] > 1.5) - { + if (_Udon_ShadowMapIndex[LightCounter] > 1.5 && (_EnableShadowCasting > 0.5)) { float4 sc2 = SampleShadowcasterPlaneWS_Basis( _Udon_LightPositions[LightCounter].xyz, i.worldPos, _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, diff --git a/Shader/Water.shader b/Shader/Water.shader index ee4baeb..6157359 100644 --- a/Shader/Water.shader +++ b/Shader/Water.shader @@ -17,6 +17,10 @@ Shader "DeMuenu/World/Hoppou/Water" _InverseSqareMultiplier ("Inverse Square Multiplier", Float) = 1 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100 + _EnableShadowCasting ("Enable Shadowcasting", Float) = 0 + _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 + //Moonlight END + _SpecPower ("Spec Power", Range(4,256)) = 64 _SpecIntensity ("Spec Intensity", Range(0,10)) = 1 _AmbientFloor ("Ambient Floor", Range(0,1)) = 0.08 @@ -24,7 +28,6 @@ Shader "DeMuenu/World/Hoppou/Water" _F0 ("F0", Range(0,1)) = 0.02 _FresnelPower ("Fresnel Power", Range(1,8)) = 5 _ReflectionStrength ("Reflection Strength", Range(0,1)) = 0.7 - //Moonlight END _WaveInput ("Wave Input", 2D) = "black" {} _CameraScale ("Camera Scale", Float) = 15 @@ -51,6 +54,7 @@ Shader "DeMuenu/World/Hoppou/Water" #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 @@ -94,6 +98,33 @@ Shader "DeMuenu/World/Hoppou/Water" MoonlightGlobalVariables + float4 _Udon_Plane_Origin_1; // xyz = origin (world), w unused + float4 _Udon_Plane_Uinv_1; // xyz = Udir / (2*halfWidth) + float4 _Udon_Plane_Vinv_1; // xyz = Vdir / (2*halfHeight) + float4 _Udon_Plane_Normal_1; // xyz = unit normal + + sampler2D _Udon_shadowCasterTex_1; + float4 _Udon_shadowCasterColor_1; + float4 _Udon_OutSideColor_1; + float _Udon_MinBrightnessShadow_1; + + float4 _Udon_Plane_Origin_2; + float4 _Udon_Plane_Uinv_2; + float4 _Udon_Plane_Vinv_2; + float4 _Udon_Plane_Normal_2; + + sampler2D _Udon_shadowCasterTex_2; + float4 _Udon_shadowCasterColor_2; + float4 _Udon_OutSideColor_2; + float _Udon_MinBrightnessShadow_2; + + float _BlurPixels; + float4 _Udon_shadowCasterTex_1_TexelSize; // xy = 1/width, 1/height + float4 _Udon_shadowCasterTex_2_TexelSize; + + bool _EnableShadowCasting; + + //Watershader specific float _SpecPower, _SpecIntensity; float3 _AmbientFloor; @@ -185,13 +216,30 @@ Shader "DeMuenu/World/Hoppou/Water" LightTypeCalculations(_Udon_LightColors, LightCounter, i, 1, dIntensity, _Udon_LightPositions[LightCounter].a, _Udon_LightPositions[LightCounter].xyz); + float4 ShadowCasterMult_1 = 1; + float4 ShadowCasterMult_2 = 1; + if (((_Udon_ShadowMapIndex[LightCounter] > 0.5) && (_Udon_ShadowMapIndex[LightCounter] < 1.5) && (_EnableShadowCasting > 0.5)) || (_Udon_ShadowMapIndex[LightCounter] > 2.5 && _EnableShadowCasting)) + { + float4 sc1 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_1.xyz, _Udon_Plane_Uinv_1.xyz, _Udon_Plane_Vinv_1.xyz, _Udon_Plane_Normal_1.xyz, + _Udon_shadowCasterTex_1, _Udon_OutSideColor_1, _Udon_shadowCasterColor_1, _BlurPixels, _Udon_shadowCasterTex_1_TexelSize.xy); + ShadowCasterMult_1 = max(sc1, _Udon_MinBrightnessShadow_1); + } + if (_Udon_ShadowMapIndex[LightCounter] > 1.5 && (_EnableShadowCasting > 0.5)) { + float4 sc2 = SampleShadowcasterPlaneWS_Basis( + _Udon_LightPositions[LightCounter].xyz, i.worldPos, + _Udon_Plane_Origin_2.xyz, _Udon_Plane_Uinv_2.xyz, _Udon_Plane_Vinv_2.xyz, _Udon_Plane_Normal_2.xyz, + _Udon_shadowCasterTex_2, _Udon_OutSideColor_2, _Udon_shadowCasterColor_2, _BlurPixels, _Udon_shadowCasterTex_2_TexelSize.xy); + ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2); + } //Watershader specific //float fres = Schlick(saturate(dot(N, V)), _F0, _FresnelPower); float spec = pow(saturate(dot(R, L)), _SpecPower); //return float4(spec, spec, spec,1); - dmax.rgb += _Udon_LightColors[LightCounter].rgb * contrib + _Udon_LightColors[LightCounter].rgb * _SpecIntensity * spec * contrib; + dmax.rgb += _Udon_LightColors[LightCounter].rgb * contrib * ShadowCasterMult_1 * ShadowCasterMult_2 + _Udon_LightColors[LightCounter].rgb * _SpecIntensity * spec * contrib * ShadowCasterMult_1 * ShadowCasterMult_2; dmax.a -= _SpecIntensity * spec; //dmax = dmax + contrib * float4(LightColor, 1); // accumulate light contributions