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.
This commit is contained in:
DeMuenu
2025-09-29 23:14:38 +02:00
parent bce6fbdc3d
commit 868e713336
15 changed files with 521 additions and 61 deletions

View File

@@ -17,7 +17,8 @@ public static class PlayerPositionsToShaderPreview
public Vector4[] colors; public Vector4[] colors;
public Vector4[] directions; public Vector4[] directions;
public float[] types; public float[] types;
public int size; public float[] shadowMapIndices;
public int size;
} }
static PlayerPositionsToShaderPreview() static PlayerPositionsToShaderPreview()
@@ -66,16 +67,17 @@ public static class PlayerPositionsToShaderPreview
static void EnsureArrays(PlayerPositionsToShader src, int required) static void EnsureArrays(PlayerPositionsToShader src, int required)
{ {
if (!_cache.TryGetValue(src, out var c) || 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.size != required)
{ {
c = new Cache c = new Cache
{ {
positions = new Vector4[required], positions = new Vector4[required],
colors = new Vector4[required], colors = new Vector4[required],
directions = new Vector4[required], directions = new Vector4[required],
types = new float[required], types = new float[required],
size = required shadowMapIndices = new float[required],
size = required
}; };
_cache[src] = c; _cache[src] = c;
} }
@@ -87,25 +89,27 @@ public static class PlayerPositionsToShaderPreview
EnsureArrays(src, max); EnsureArrays(src, max);
var c = _cache[src]; var c = _cache[src];
var positions = c.positions; var positions = c.positions;
var colors = c.colors; var colors = c.colors;
var directions = c.directions; var directions = c.directions;
var types = c.types; var types = c.types;
var shadowMapIndices = c.shadowMapIndices;
// Clear arrays to safe defaults // Clear arrays to safe defaults
for (int i = 0; i < max; i++) for (int i = 0; i < max; i++)
{ {
positions[i] = Vector4.zero; positions[i] = Vector4.zero;
colors[i] = Vector4.zero; colors[i] = Vector4.zero;
directions[i] = Vector4.zero; directions[i] = Vector4.zero;
types[i] = 0f; types[i] = 0f;
shadowMapIndices[i] = 0f;
} }
// Use the Editor-side function defined on the partial class // Use the Editor-side function defined on the partial class
int count = 0; int count = 0;
try 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 // replace cache arrays if sizes changed
if (positions.Length != c.size) if (positions.Length != c.size)
@@ -113,11 +117,12 @@ public static class PlayerPositionsToShaderPreview
_cache[src] = new Cache _cache[src] = new Cache
{ {
positions = positions, positions = positions,
colors = colors, colors = colors,
directions = directions, directions = directions,
types = types, types = types,
size = positions.Length shadowMapIndices = shadowMapIndices,
size = positions.Length
}; };
} }
catch catch
@@ -152,6 +157,12 @@ public static class PlayerPositionsToShaderPreview
Shader.SetGlobalFloatArray(id, types); Shader.SetGlobalFloatArray(id, types);
} }
if (!string.IsNullOrEmpty(src.shadowMapIndexProperty))
{
int id = Shader.PropertyToID(src.shadowMapIndexProperty);
Shader.SetGlobalFloatArray(id, shadowMapIndices);
}
if (!string.IsNullOrEmpty(src.countProperty)) if (!string.IsNullOrEmpty(src.countProperty))
{ {
int id = Shader.PropertyToID(src.countProperty); int id = Shader.PropertyToID(src.countProperty);

View File

@@ -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<ShadowcasterUpdater, int> _propId = new Dictionary<ShadowcasterUpdater, int>();
static readonly Dictionary<Renderer, Matrix4x4> _lastW2L = new Dictionary<Renderer, Matrix4x4>();
static readonly List<Renderer> _toRemove = new List<Renderer>(32);
static ShadowcasterUpdaterPreview()
{
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<ShadowcasterUpdater>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
#elif UNITY_2020_1_OR_NEWER
return Object.FindObjectsOfType<ShadowcasterUpdater>(true);
#else
return Resources.FindObjectsOfTypeAll<ShadowcasterUpdater>();
#endif
}
static void CleanupNullRenderers()
{
_toRemove.Clear();
foreach (var kv in _lastW2L)
if (kv.Key == null) _toRemove.Add(kv.Key);
for (int i = 0; i < _toRemove.Count; i++)
_lastW2L.Remove(_toRemove[i]);
}
static int GetPropertyId(ShadowcasterUpdater b)
{
string name = string.IsNullOrEmpty(b.propertyName) ? "_Udon_WorldToLocal" : b.propertyName;
if (!_propId.TryGetValue(b, out int id))
{
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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0491ab6ce27c5ba449e98288f0e3d8ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,44 +9,52 @@ public partial class PlayerPositionsToShader
out Vector4[] colors, out Vector4[] colors,
out Vector4[] directions, out Vector4[] directions,
out float[] types, out float[] types,
out float[] shadowMapIndices,
out int count) out int count)
{ {
int max = Mathf.Max(1, maxLights); int max = Mathf.Max(1, maxLights);
positions = new Vector4[max]; positions = new Vector4[max];
colors = new Vector4[max]; colors = new Vector4[max];
directions = new Vector4[max]; directions = new Vector4[max];
types = new float[max]; types = new float[max];
count = 0; shadowMapIndices = new float[max];
count = 0;
// ✅ Avoid Array.Empty<T>(); just guard the loop if (otherLightSources == null) return;
if (otherLightSources != null)
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>(); LightdataStorage data = t.GetComponent<LightdataStorage>();
Vector3 pos = t.position; Vector3 pos = t.position;
float range = (data != null) ? data.range * t.localScale.x : t.localScale.x; 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;
// 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; // w = cosHalfAngle (0 for omni)
Vector3 fwd = rot * Vector3.down; float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f;
positions[count] = new Vector4(pos.x, pos.y, pos.z, range); // 0=Omni, 1=Spot, 2=Directional (your custom enum)
colors[count] = new Vector4(col.x, col.y, col.z, intens); int typeId = (data != null) ? data.GetTypeId() : 0;
directions[count] = new Vector4(fwd.x, fwd.y, fwd.z, data.spotAngleDeg);
types[count] = (float)typeId;
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++;
} }
} }
} }

8
Mesh.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2d573c2258a40a04f9932e8910763931
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Mesh/Plane.fbx Normal file

Binary file not shown.

109
Mesh/Plane.fbx.meta Normal file
View File

@@ -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:

View File

@@ -32,6 +32,10 @@ public class LightdataStorage : UdonSharpBehaviour
[Tooltip("0 = omni (no cone)")] [Tooltip("0 = omni (no cone)")]
public float spotAngleDeg = 0f; 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 // Convert to a Vector4 for your shader upload
public Vector4 GetFinalColor() public Vector4 GetFinalColor()
{ {

View File

@@ -24,6 +24,9 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
public float playerLightIntensity = 5f; public float playerLightIntensity = 5f;
public float remoteLightIntensity = 2f; 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)")] [Header("Shader property names (advanced users)")]
[Tooltip("Vector4 array: xyz = position, w = range")] [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)")] [Tooltip("float array: light type (1=area, 2=cone, etc)")]
public string typeProperty = "_Udon_LightType"; 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)")] [Header("Max Lights (advanced users)")]
[Tooltip("Hard cap / array size. 80 = default cap")] [Tooltip("Hard cap / array size. 80 = default cap")]
public int maxLights = 80; public int maxLights = 80;
@@ -57,9 +63,11 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
private float[] _TypeArray; private float[] _TypeArray;
private bool _TypeArray_isDirty = false; private bool _TypeArray_isDirty = false;
private float[] _ShadowMapArray;
private bool _ShadowMap_isDirty = false;
private VRCPlayerApi[] _players; private VRCPlayerApi[] _players;
private MaterialPropertyBlock _mpb;
public int currentCount { get; private set; } public int currentCount { get; private set; }
@@ -70,6 +78,7 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
private int UdonID_LightColors; private int UdonID_LightColors;
private int UdonID_LightDirections; private int UdonID_LightDirections;
private int UdonID_LightType; private int UdonID_LightType;
private int UdonID_ShadowMapIndex;
void Start() void Start()
{ {
@@ -79,15 +88,16 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
_lightColors = new Vector4[maxLights]; _lightColors = new Vector4[maxLights];
_directions = new Vector4[maxLights]; _directions = new Vector4[maxLights];
_TypeArray = new float[maxLights]; _TypeArray = new float[maxLights];
_ShadowMapArray = new float[maxLights];
_players = new VRCPlayerApi[maxLights]; _players = new VRCPlayerApi[maxLights];
_mpb = new MaterialPropertyBlock();
UdonID_PlayerPositions = VRCShader.PropertyToID(positionsProperty); UdonID_PlayerPositions = VRCShader.PropertyToID(positionsProperty);
UdonID_LightCount = VRCShader.PropertyToID(countProperty); UdonID_LightCount = VRCShader.PropertyToID(countProperty);
UdonID_LightColors = VRCShader.PropertyToID(colorProperty); UdonID_LightColors = VRCShader.PropertyToID(colorProperty);
UdonID_LightDirections = VRCShader.PropertyToID(directionsProperty); UdonID_LightDirections = VRCShader.PropertyToID(directionsProperty);
UdonID_LightType = VRCShader.PropertyToID(typeProperty); UdonID_LightType = VRCShader.PropertyToID(typeProperty);
UdonID_ShadowMapIndex = VRCShader.PropertyToID(shadowMapIndexProperty);
UpdateData(); UpdateData();
@@ -150,6 +160,11 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
_TypeArray[i] = 0f; _TypeArray[i] = 0f;
_TypeArray_isDirty = true; _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[i] = 0f;
_TypeArray_isDirty = true; _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; _TypeArray_isDirty = true;
} }
float shadowMapIndex = (data != null) ? data.shadowMapIndex : 0f;
if (_ShadowMapArray[currentCount] != shadowMapIndex)
{
_ShadowMapArray[currentCount] = shadowMapIndex;
_ShadowMap_isDirty = true;
}
currentCount++; currentCount++;
} }
} }
@@ -259,6 +286,12 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour
_TypeArray[i] = 0f; _TypeArray[i] = 0f;
_TypeArray_isDirty = true; _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 // Snapshot which things are dirty this frame
bool pushPositions = _positons_isDirty; bool pushPositions = _positons_isDirty;
bool pushColors = _lightColors_isDirty; bool pushColors = _lightColors_isDirty;
bool pushDirs = _directions_isDirty; bool pushDirs = _directions_isDirty;
bool pushTypes = _TypeArray_isDirty && !string.IsNullOrEmpty(typeProperty); bool pushTypes = _TypeArray_isDirty && !string.IsNullOrEmpty(typeProperty);
bool pushShadowMap = _ShadowMap_isDirty;
if (pushPositions) VRCShader.SetGlobalVectorArray(UdonID_PlayerPositions, _positions); if (pushPositions) VRCShader.SetGlobalVectorArray(UdonID_PlayerPositions, _positions);
if (pushColors) VRCShader.SetGlobalVectorArray(UdonID_LightColors, _lightColors); if (pushColors) VRCShader.SetGlobalVectorArray(UdonID_LightColors, _lightColors);
if (pushDirs) VRCShader.SetGlobalVectorArray(UdonID_LightDirections, _directions); if (pushDirs) VRCShader.SetGlobalVectorArray(UdonID_LightDirections, _directions);
if (pushTypes) _mpb.SetFloatArray(UdonID_LightType, _TypeArray); if (pushTypes) VRCShader.SetGlobalFloatArray(UdonID_LightType, _TypeArray);
if (pushShadowMap) VRCShader.SetGlobalFloatArray(UdonID_ShadowMapIndex, _ShadowMapArray);
VRCShader.SetGlobalFloat(UdonID_LightCount, currentCount); VRCShader.SetGlobalFloat(UdonID_LightCount, currentCount);
Debug.Log($"[MoonlightVRC] Pushed {currentCount} lights to shader.");
// Only now mark them clean // Only now mark them clean
if (pushPositions) { _positons_isDirty = false;} if (pushPositions) { _positons_isDirty = false; }
if (pushColors) { _lightColors_isDirty = false;} if (pushColors) { _lightColors_isDirty = false; }
if (pushDirs) { _directions_isDirty = false;} if (pushDirs) { _directions_isDirty = false; }
if (pushTypes) { _TypeArray_isDirty = false;} if (pushTypes) { _TypeArray_isDirty = false; }
if (pushShadowMap) { _ShadowMap_isDirty = false; }
} }
} }

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36c63f8382c2aad48b73fe4628db0f38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -17,6 +17,12 @@ Shader "DeMuenu/World/Hoppou/RevealStandart"
_LightCutoffDistance ("Light Cutoff Distance", Float) = 100 _LightCutoffDistance ("Light Cutoff Distance", Float) = 100
//Moonlight END //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/Lambert.hlsl"
#include "Includes/DefaultSetup.hlsl" #include "Includes/DefaultSetup.hlsl"
#include "Includes/Variables.hlsl" #include "Includes/Variables.hlsl"
#include "Includes/Shadowcaster.cginc"
//Moonlight Defines //Moonlight Defines
#define MAX_LIGHTS 80 // >= maxPlayers in script #define MAX_LIGHTS 80 // >= maxPlayers in script
@@ -84,6 +91,12 @@ Shader "DeMuenu/World/Hoppou/RevealStandart"
MoonlightGlobalVariables MoonlightGlobalVariables
float4x4 _Udon_WorldToLocal;
sampler2D _shadowCasterTex;
float4 _shadowCasterColor;
float4 _OutSideColor;
float _MinBrightnessShadow;
v2f vert (appdata v) 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); 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);
} }

View File

@@ -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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 407fd8c92bce1a84ab69e3abad2320b0
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -8,6 +8,7 @@
float4 _Udon_LightColors[MAX_LIGHTS]; /* xyz = position */ \ float4 _Udon_LightColors[MAX_LIGHTS]; /* xyz = position */ \
float4 _Udon_LightDirections[MAX_LIGHTS]; /* xyz = direction, w = cos(halfAngle) */ \ float4 _Udon_LightDirections[MAX_LIGHTS]; /* xyz = direction, w = cos(halfAngle) */ \
float _Udon_LightType[MAX_LIGHTS]; /* 0 = sphere, 1 = cone */ \ float _Udon_LightType[MAX_LIGHTS]; /* 0 = sphere, 1 = cone */ \
float _Udon_ShadowMapIndex[MAX_LIGHTS];\
float _Udon_PlayerCount; /* set via SetFloat */ \ float _Udon_PlayerCount; /* set via SetFloat */ \
#endif #endif