optimisations from gemini (not tested)

This commit is contained in:
DeMuenu
2026-03-24 15:45:31 +01:00
parent 9f986c3eb1
commit a1e808ad92
12 changed files with 231 additions and 117 deletions

View File

@@ -21,38 +21,37 @@ public partial class LightUpdater
shadowMapIndices = new float[max];
count = 0;
if (otherLightSources == null) return;
LightdataStorage[] sceneLights = Object.FindObjectsOfType<LightdataStorage>();
for (int i = 0; i < otherLightSources.Length && count < max; i++)
for (int i = 0; i < sceneLights.Length && count < max; i++)
{
Transform t = otherLightSources[i];
if (t == null || !t.gameObject.activeInHierarchy) continue;
LightdataStorage data = sceneLights[i];
if (data == null || !data.gameObject.activeInHierarchy) continue;
LightdataStorage data = t.GetComponent<LightdataStorage>();
Transform t = data.transform;
// w = cosHalfAngle (0 for omni)
float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f;
float cosHalf = data.GetCosHalfAngle();
Vector3 pos = t.position;
float range = 0;
if (data.lightType == LightType.Sphere)
{
range = (data != null) ? data.range * t.localScale.x : t.localScale.x;
range = data.range * t.localScale.x;
}
else
{
range = (data != null) ? Mathf.Cos(Mathf.Deg2Rad * ((data.spotAngleDeg * 0.5f) + Mathf.Max(data.range, 0))): 0f;
range = Mathf.Cos(Mathf.Deg2Rad * ((data.spotAngleDeg * 0.5f) + Mathf.Max(data.range, 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;
Vector4 col = data.GetFinalColor();
float intensity = data.intensity * t.localScale.x;
// 0=Omni, 1=Spot, 2=Directional (your custom enum)
int typeId = (data != null) ? data.GetTypeId() : 0;
int typeId = data.GetTypeId();
float shIndex = (data != null) ? data.shadowMapIndex : 0f;
float shIndex = data.shadowMapIndex;
Quaternion rot = t.rotation;
Vector3 fwd = rot * Vector3.down;

View File

@@ -40,7 +40,7 @@ On PC, I haven't encountered any frame drops in the editor at all, even with 400
3. For lights, attach `LightdataStorage` to a Transform and configure:
- `range`, `type`, `color`, `intensity`, and `spotAngleDeg`.
4. Add the light transform to your `LightUpdater` component's `otherLightSources` array.
4. On your light's `LightdataStorage` component, assign your scene's `LightUpdater` to the `Light Updater` field. This allows lights to be added or removed dynamically at runtime.
5. Use one of the premade shaders on your material. Or, if you feel like it, use the provided .hlsl/.cginc in your own shader. You just need to copy everything surrounded by Moonlight comments, and apply it at the end of your shader.

View File

@@ -1,4 +1,5 @@
using System;
using System;
using UdonSharp;
using Unity.Mathematics;
using UnityEngine;
@@ -9,9 +10,6 @@ using VRC.SDK3.Rendering;
public partial class LightUpdater : UdonSharpBehaviour
{
[Header("Lightsources")]
[Tooltip("Place Transforms here which should also emit Light (attach LightdataStorage to them).")]
public Transform[] otherLightSources;
[Header("Strength")]
[Tooltip("Local player light range")]
@@ -68,6 +66,9 @@ public partial class LightUpdater : UdonSharpBehaviour
private float[] _ShadowMapArray;
private bool _ShadowMap_isDirty = false;
private LightdataStorage[] _sceneLights;
private int _sceneLightCount = 0;
private VRCPlayerApi[] _players;
@@ -91,6 +92,7 @@ public partial class LightUpdater : UdonSharpBehaviour
_directions = new Vector4[maxLights];
_TypeArray = new float[maxLights];
_ShadowMapArray = new float[maxLights];
_sceneLights = new LightdataStorage[maxLights];
_players = new VRCPlayerApi[maxLights];
@@ -101,11 +103,56 @@ public partial class LightUpdater : UdonSharpBehaviour
UdonID_LightType = VRCShader.PropertyToID(typeProperty);
UdonID_ShadowMapIndex = VRCShader.PropertyToID(shadowMapIndexProperty);
UpdateData();
PushToRenderers();
}
public void RegisterLight(LightdataStorage light)
{
if (light == null) return;
// Prevent duplicates
for (int i = 0; i < _sceneLightCount; i++)
{
if (_sceneLights[i] == light) return;
}
if (_sceneLightCount < _sceneLights.Length)
{
_sceneLights[_sceneLightCount] = light;
_sceneLightCount++;
}
else
{
Debug.LogError($"[MoonlightVRC] Cannot register new light, scene light limit reached ({_sceneLights.Length})");
}
}
public void DeregisterLight(LightdataStorage light)
{
if (light == null) return;
int foundIndex = -1;
for (int i = 0; i < _sceneLightCount; i++)
{
if (_sceneLights[i] == light)
{
foundIndex = i;
break;
}
}
if (foundIndex != -1)
{
// Shift elements down to fill the gap
for (int i = foundIndex; i < _sceneLightCount - 1; i++)
{
_sceneLights[i] = _sceneLights[i + 1];
}
_sceneLightCount--;
_sceneLights[_sceneLightCount] = null; // Clear the last element
}
}
void LateUpdate()
{
if (Time.time < _nextUpdate) return;
@@ -154,6 +201,7 @@ public partial class LightUpdater : UdonSharpBehaviour
if (_directions[i] != TempDir)
{
_directions[i] = new Vector4(TempDir.x, TempDir.y, TempDir.z, 10f);
_directions[i] = TempDir;
_directions_isDirty = true;
}
@@ -201,14 +249,13 @@ public partial class LightUpdater : UdonSharpBehaviour
}
// --- Scene light sources ---
if (otherLightSources != null)
if (_sceneLights != null)
{
for (int j = 0; j < otherLightSources.Length && currentCount < maxLights; j++)
for (int j = 0; j < _sceneLightCount && currentCount < maxLights; j++)
{
Transform t = otherLightSources[j];
if (t == null || !t.gameObject.activeInHierarchy) continue;
LightdataStorage data = t.GetComponent<LightdataStorage>();
LightdataStorage data = _sceneLights[j];
if (data == null || !data.gameObject.activeInHierarchy) continue;
Transform t = data.transform;
Vector3 pos = t.position;
float range = (data != null) ? data.range * t.localScale.x: t.localScale.x;

View File

@@ -1,4 +1,4 @@
using UdonSharp;
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
@@ -10,6 +10,10 @@ public enum LightType { Sphere, Spot }
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LightdataStorage : UdonSharpBehaviour
{
[Header("System")]
[Tooltip("The main LightUpdater in the scene. This is required for dynamic lights.")]
public LightUpdater lightUpdater;
[Header("Type")]
[Tooltip("Select the logical light type for this source.")]
@@ -36,6 +40,22 @@ public class LightdataStorage : UdonSharpBehaviour
[Tooltip("0 = no shadows, 1-4 = shadow map index")]
public float shadowMapIndex = 0f; // 0 = no shadows, 1-4 = shadow map index
void OnEnable()
{
if (lightUpdater != null)
{
lightUpdater.RegisterLight(this);
}
}
void OnDisable()
{
if (lightUpdater != null)
{
lightUpdater.DeregisterLight(this);
}
}
// Convert to a Vector4 for your shader upload
public Vector4 GetFinalColor()
{

View File

@@ -1,4 +1,4 @@


using System.Security.Permissions;
using UdonSharp;
using UnityEngine;
@@ -18,10 +18,30 @@ public class ShadowcasterUpdater : UdonSharpBehaviour
private MaterialPropertyBlock _mpb;
private int _propShadowTex;
private int _propShadowColor;
private int _propOutsideColor;
private int _propMinBrightness;
private int _propPlaneOrigin;
private int _propPlaneUinv;
private int _propPlaneVinv;
private int _propPlaneNormal;
void Start()
{
_mpb = new MaterialPropertyBlock();
string suf = "_" + shadowcasterIndex.ToString();
_propShadowTex = VRCShader.PropertyToID("_Udon_shadowCasterTex" + suf);
_propShadowColor = VRCShader.PropertyToID("_Udon_shadowCasterColor" + suf);
_propOutsideColor = VRCShader.PropertyToID("_Udon_OutSideColor" + suf);
_propMinBrightness = VRCShader.PropertyToID("_Udon_MinBrightnessShadow" + suf);
_propPlaneOrigin = VRCShader.PropertyToID("_Udon_Plane_Origin" + suf);
_propPlaneUinv = VRCShader.PropertyToID("_Udon_Plane_Uinv" + suf);
_propPlaneVinv = VRCShader.PropertyToID("_Udon_Plane_Vinv" + suf);
_propPlaneNormal = VRCShader.PropertyToID("_Udon_Plane_Normal" + suf);
ApplyTextureData();
}
@@ -31,21 +51,15 @@ public class ShadowcasterUpdater : UdonSharpBehaviour
{
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);
if (ShadowcasterTexture != null) _mpb.SetTexture(_propShadowTex, ShadowcasterTexture);
_mpb.SetColor(_propShadowColor, TextureColor);
_mpb.SetColor(_propOutsideColor, OutsideColor);
_mpb.SetFloat(_propMinBrightness, 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;
@@ -64,10 +78,20 @@ public class ShadowcasterUpdater : UdonSharpBehaviour
// 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));
Vector4 originVec = new Vector4(transform.position.x, transform.position.y, transform.position.z, 0);
Vector4 uinvVec = new Vector4(Uinv.x, Uinv.y, Uinv.z, 0);
Vector4 vinvVec = new Vector4(Vinv.x, Vinv.y, Vinv.z, 0);
Vector4 nVec = new Vector4(N.x, N.y, N.z, 0);
foreach (Renderer mat in rendererTargets)
{
if (mat == null) continue;
mat.GetPropertyBlock(_mpb);
_mpb.SetVector(_propPlaneOrigin, originVec);
_mpb.SetVector(_propPlaneUinv, uinvVec);
_mpb.SetVector(_propPlaneVinv, vinvVec);
_mpb.SetVector(_propPlaneNormal, nVec);
mat.SetPropertyBlock(_mpb);
}

View File

@@ -2,16 +2,20 @@
#define InLoopSetup(_Udon_LightPositions, LightCounter, count, i) \
if (LightCounter >= count) break; \
\
float distanceFromLight = length(i.worldPos - _Udon_LightPositions[LightCounter].xyz); \
float3 lightVec = _Udon_LightPositions[LightCounter].xyz - i.worldPos; \
float distanceFromLight = length(lightVec); \
if (distanceFromLight > _LightCutoffDistance) continue; \
\
float contrib = 0.0;
float contrib = 0.0; \
float3 L = lightVec / max(distanceFromLight, 1e-4);
#endif
#ifndef OutLoopSetup
#define OutLoopSetup(i, _Udon_PlayerCount) \
int count = (int)_Udon_PlayerCount; \
float invSqMul = max(1e-4, _InverseSqareMultiplier); \
bool shadowCastingEnabled = _EnableShadowCasting > 0.5; \
\
float4 dmax = float4(0,0,0,1); \
float dIntensity = 0;

View File

@@ -1,7 +1,7 @@
#ifndef Lambert
#define Lambert(q ,i, N) \
float3 L = normalize(q - i.worldPos); /* q = light position */ \
float NdotL = saturate(dot(N, L) * 0.5 + 0.5); /* one-sided Lambert */ \
/* 'L' is inherited from InLoopSetup, avoiding an expensive normalize() */ \
half NdotL = saturate(dot(N, L) * 0.5 + 0.5); /* one-sided Lambert */ \
if (NdotL <= 0) continue; \
#endif

View File

@@ -1,24 +1,26 @@
#ifndef LightTypeCalculations
#define LightTypeCalculations(_Udon_LightColors ,LightCounter, i, NdotL, dIntensity, radius, Lightposition) \
float invSqMul = max(1e-4, _InverseSqareMultiplier); \
half typeId = _Udon_LightType[LightCounter]; \
\
if(_Udon_LightType[LightCounter] == 0) \
if(typeId == 0) \
{ \
contrib = _Udon_LightColors[LightCounter].a / max(1e-4, max(0, max(1, distanceFromLight - radius) * invSqMul) * max(0, max(1, distanceFromLight - radius) * invSqMul)); \
float distAtten = max(1.0, distanceFromLight - radius) * invSqMul; \
contrib = _Udon_LightColors[LightCounter].a / max(1e-4, distAtten * distAtten); \
\
dIntensity += contrib; \
} \
else if (_Udon_LightType[LightCounter] == 1) \
else if (typeId == 1) \
{ \
float invSq = _Udon_LightColors[LightCounter].a / max(1e-4, (distanceFromLight * invSqMul) * (distanceFromLight * invSqMul)); \
float distAtten = distanceFromLight * invSqMul; \
float invSq = _Udon_LightColors[LightCounter].a / max(1e-4, distAtten * distAtten); \
\
contrib = dot(normalize(i.worldPos - Lightposition), normalize(_Udon_LightDirections[LightCounter].xyz)); \
contrib = dot(-L, normalize(_Udon_LightDirections[LightCounter].xyz)); \
contrib = smoothstep(radius,_Udon_LightDirections[LightCounter].w, contrib); \
\
contrib = contrib * invSq; \
dIntensity += contrib; \
} \
float3 LightColor = _Udon_LightColors[LightCounter].xyz; \
half3 LightColor = _Udon_LightColors[LightCounter].xyz; \
#endif

View File

@@ -149,7 +149,10 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles_2SP"
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))
if (shadowCastingEnabled)
{
half smIndex = _Udon_ShadowMapIndex[LightCounter];
if ((smIndex > 0.5 && smIndex < 1.5) || smIndex > 2.5)
{
float4 sc1 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -157,13 +160,15 @@ Shader "DeMuenu/World/Hoppou/Particles/LitParticles_2SP"
_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)) {
if (smIndex > 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, _BlurPixels, _Udon_shadowCasterTex_2_TexelSize.xy);
ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2);
}
}
dmax = dmax + contrib * float4(LightColor, 1) * ShadowCasterMult_1 * ShadowCasterMult_2;

View File

@@ -176,7 +176,10 @@ Shader "DeMuenu/World/Hoppou/Standard_2SP"
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))
if (shadowCastingEnabled)
{
half smIndex = _Udon_ShadowMapIndex[LightCounter];
if ((smIndex > 0.5 && smIndex < 1.5) || smIndex > 2.5)
{
float4 sc1 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -184,7 +187,7 @@ Shader "DeMuenu/World/Hoppou/Standard_2SP"
_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))
if (smIndex > 1.5)
{
float4 sc2 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -192,6 +195,7 @@ Shader "DeMuenu/World/Hoppou/Standard_2SP"
_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;

View File

@@ -190,7 +190,10 @@ Shader "DeMuenu/World/Hoppou/Standard_Lightmap_2SP"
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))
if (shadowCastingEnabled)
{
half smIndex = _Udon_ShadowMapIndex[LightCounter];
if ((smIndex > 0.5 && smIndex < 1.5) || smIndex > 2.5)
{
float4 sc1 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -198,7 +201,7 @@ Shader "DeMuenu/World/Hoppou/Standard_Lightmap_2SP"
_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))
if (smIndex > 1.5)
{
float4 sc2 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -206,6 +209,7 @@ Shader "DeMuenu/World/Hoppou/Standard_Lightmap_2SP"
_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;

View File

@@ -219,7 +219,10 @@ Shader "DeMuenu/World/Hoppou/WaterFlat_2SP"
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))
if (shadowCastingEnabled)
{
half smIndex = _Udon_ShadowMapIndex[LightCounter];
if ((smIndex > 0.5 && smIndex < 1.5) || smIndex > 2.5)
{
float4 sc1 = SampleShadowcasterPlaneWS_Basis(
_Udon_LightPositions[LightCounter].xyz, i.worldPos,
@@ -227,13 +230,15 @@ Shader "DeMuenu/World/Hoppou/WaterFlat_2SP"
_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)) {
if (smIndex > 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, _BlurPixels, _Udon_shadowCasterTex_2_TexelSize.xy);
ShadowCasterMult_2 = max(sc2, _Udon_MinBrightnessShadow_2);
}
}
//Watershader specific
//float fres = Schlick(saturate(dot(N, V)), _F0, _FresnelPower);