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..4403b87 --- /dev/null +++ b/EditorPreview/Editor/ShadowcasterUpdaterPreview.cs @@ -0,0 +1,152 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +[InitializeOnLoad] +public static class ShadowcasterUpdaterEditorLoop +{ + private const double Interval = 0.1; + private const float Eps = 1e-6f; + + private static double _lastUpdateTime; + private static ShadowcasterUpdater[] _cached; + private static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock(); + + static ShadowcasterUpdaterEditorLoop() + { + _lastUpdateTime = EditorApplication.timeSinceStartup; + + EditorApplication.update += Update; + EditorApplication.hierarchyChanged += RefreshCache; + AssemblyReloadEvents.afterAssemblyReload += RefreshCache; + + RefreshCache(); + } + + private static void RefreshCache() + { + _cached = FindSceneUpdaters(); + } + + private static ShadowcasterUpdater[] FindSceneUpdaters() + { +#if UNITY_2020_1_OR_NEWER + return Object.FindObjectsOfType(true); +#else + // 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) + { + 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(); + } + + private static void TryUpdate(ShadowcasterUpdater u) + { + // If inspector values changed, make sure textures/colors/min brightness are pushed + try + { + u.ApplyTextureData(); + } + catch + { + // UdonSharp can be touchy during certain editor states. Ignore and continue. + } + + 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++) + { + var ren = targets[r]; + if (ren == null) continue; + + ren.GetPropertyBlock(_mpb); + + // 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); + + // Plane data + _mpb.SetVector(idPlaneOrigin, origin); + _mpb.SetVector(idPlaneUinv, uinv4); + _mpb.SetVector(idPlaneVinv, vinv4); + _mpb.SetVector(idPlaneNormal, n4); + + ren.SetPropertyBlock(_mpb); + } + } +} +#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..14c11cb --- /dev/null +++ b/Scripts/ShadowcasterUpdater.cs @@ -0,0 +1,76 @@ + +using System.Security.Permissions; +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using VRC.Udon; + +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; + + 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" + "_" + 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() + { + + foreach (Renderer mat in rendererTargets) + { + if (mat == null) continue; + mat.GetPropertyBlock(_mpb); + + 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/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..83c70a6 100644 --- a/Shader/BlendinShader.shader +++ b/Shader/BlendinShader.shader @@ -15,8 +15,13 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" //Moonlight _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 + } @@ -24,6 +29,7 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" { Tags { "RenderType"="Opaque" } LOD 100 + Cull[_Cull] Pass { @@ -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 @@ -85,6 +92,32 @@ Shader "DeMuenu/World/Hoppou/RevealStandart" 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; + v2f vert (appdata v) { v2f o; @@ -139,8 +172,28 @@ 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; - dmax = dmax + contrib * float4(LightColor, 1) * NdotL; // accumulate light contributions + 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); + } + + 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 new file mode 100644 index 0000000..f986308 --- /dev/null +++ b/Shader/Includes/Shadowcaster.cginc @@ -0,0 +1,46 @@ +#ifndef SHADOWCASTER_PLANE +#define SHADOWCASTER_PLANE + +#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, + float blurPixels, float2 texelSize) +{ + float3 d = B - A; + float dn = dot(N, d); + if (abs(dn) < WS_EPS) return OutsideColor; + + 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; + + // 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; + + 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/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 diff --git a/Shader/LitParticles.shader b/Shader/LitParticles.shader index e4debd5..893a091 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 + + _EnableShadowCasting ("Enable Shadowcasting", Float) = 0 + _BlurPixels ("Shadowcaster Blur Pixels", Float) = 0 //Moonlight END @@ -36,6 +39,7 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" #include "Includes/Lambert.hlsl" #include "Includes/DefaultSetup.hlsl" #include "Includes/Variables.hlsl" + #include "Includes/Shadowcaster.cginc" @@ -78,6 +82,31 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles" 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; v2f vert (appdata v) { @@ -114,11 +143,29 @@ 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); - dmax = dmax + contrib * float4(LightColor, 1) * NdotL; // accumulate light contributions + 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); + } + + dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2; } 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