diff --git a/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs b/EditorPreview/Editor/LightupdaterPreview.cs similarity index 86% rename from EditorPreview/Editor/PlayerPositionsToShaderPreview.cs rename to EditorPreview/Editor/LightupdaterPreview.cs index 716a6f1..e24f677 100644 --- a/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs +++ b/EditorPreview/Editor/LightupdaterPreview.cs @@ -1,15 +1,15 @@ -// Assets/Editor/PlayerPositionsToShaderPreview.cs +// Assets/Editor/LightUpdaterPreview.cs #if UNITY_EDITOR using UnityEditor; using UnityEngine; using System.Collections.Generic; [InitializeOnLoad] -public static class PlayerPositionsToShaderPreview +public static class LightUpdaterPreview { const double kTickInterval = 0.1; // seconds static double _nextTick; - static readonly Dictionary _cache = new Dictionary(); + static readonly Dictionary _cache = new Dictionary(); struct Cache { @@ -21,7 +21,7 @@ public static class PlayerPositionsToShaderPreview public int size; } - static PlayerPositionsToShaderPreview() + static LightUpdaterPreview() { EditorApplication.update += Update; EditorApplication.hierarchyChanged += ForceTick; @@ -53,18 +53,18 @@ public static class PlayerPositionsToShaderPreview SceneView.RepaintAll(); } - static PlayerPositionsToShader[] FindAllInScene() + static LightUpdater[] FindAllInScene() { #if UNITY_2023_1_OR_NEWER - return Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + return Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); #elif UNITY_2020_1_OR_NEWER - return Object.FindObjectsOfType(true); + return Object.FindObjectsOfType(true); #else - return Resources.FindObjectsOfTypeAll(); + return Resources.FindObjectsOfTypeAll(); #endif } - static void EnsureArrays(PlayerPositionsToShader src, int required) + static void EnsureArrays(LightUpdater src, int required) { if (!_cache.TryGetValue(src, out var c) || c.positions == null || c.colors == null || c.directions == null || c.types == null || c.shadowMapIndices == null || @@ -83,7 +83,7 @@ public static class PlayerPositionsToShaderPreview } } - static void PushFromBehaviour(PlayerPositionsToShader src) + static void PushFromBehaviour(LightUpdater src) { int max = Mathf.Max(1, src.maxLights); EnsureArrays(src, max); @@ -171,8 +171,8 @@ public static class PlayerPositionsToShaderPreview } } -[CustomEditor(typeof(PlayerPositionsToShader))] -public class PlayerPositionsToShaderInspector : Editor +[CustomEditor(typeof(LightUpdater))] +public class LightUpdaterInspector : Editor { public override void OnInspectorGUI() { @@ -186,7 +186,7 @@ public class PlayerPositionsToShaderInspector : Editor if (GUILayout.Button("Refresh Now")) { - PlayerPositionsToShaderPreview.ForceTick(); + LightUpdaterPreview.ForceTick(); EditorApplication.QueuePlayerLoopUpdate(); SceneView.RepaintAll(); } diff --git a/EditorPreview/Editor/PlayerPositionsToShaderPreview.cs.meta b/EditorPreview/Editor/LightupdaterPreview.cs.meta similarity index 100% rename from EditorPreview/Editor/PlayerPositionsToShaderPreview.cs.meta rename to EditorPreview/Editor/LightupdaterPreview.cs.meta diff --git a/EditorPreview/PlayerPositionsToShader.Editor.cs b/EditorPreview/LightUpdater.Editor.cs similarity index 81% rename from EditorPreview/PlayerPositionsToShader.Editor.cs rename to EditorPreview/LightUpdater.Editor.cs index bb57b34..3161938 100644 --- a/EditorPreview/PlayerPositionsToShader.Editor.cs +++ b/EditorPreview/LightUpdater.Editor.cs @@ -1,8 +1,8 @@ -// Assets/Lighting/Scripts/PlayerPositionsToShader.Editor.cs +// Assets/Lighting/Scripts/LightUpdater.Editor.cs #if UNITY_EDITOR using UnityEngine; -public partial class PlayerPositionsToShader +public partial class LightUpdater { public void Editor_BuildPreview( out Vector4[] positions, @@ -30,15 +30,24 @@ public partial class PlayerPositionsToShader LightdataStorage data = t.GetComponent(); + // w = cosHalfAngle (0 for omni) + float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f; + Vector3 pos = t.position; - float range = (data != null) ? data.range * t.localScale.x : t.localScale.x; + float range = 0; + if (data.lightType == LightType.Sphere) + { + range = (data != null) ? data.range * t.localScale.x : t.localScale.x; + } + else + { + range = (data != null) ? Mathf.Cos(Mathf.Deg2Rad * ((data.spotAngleDeg * 0.5f) + Mathf.Max(data.range, 0))): 0f; + } // 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; - // w = cosHalfAngle (0 for omni) - float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f; // 0=Omni, 1=Spot, 2=Directional (your custom enum) int typeId = (data != null) ? data.GetTypeId() : 0; diff --git a/EditorPreview/PlayerPositionsToShader.Editor.cs.meta b/EditorPreview/LightUpdater.Editor.cs.meta similarity index 100% rename from EditorPreview/PlayerPositionsToShader.Editor.cs.meta rename to EditorPreview/LightUpdater.Editor.cs.meta diff --git a/README.md b/README.md index 85745de..05017f3 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ On PC, I haven't encountered any frame drops in the editor at all, even with 400 1. Clone the code into your project. -2. Add the `PlayerPositionsToShader` component to a GameObject in your scene: +2. Add the `LightUpdater` component to a GameObject in your scene: - Tweak strength/intensity of the local and remote player if you want them to have an attached light. 3. For lights, attach `LightdataStorage` to a Transform and configure: - `range`, `type`, `color`, `intensity`, and `spotAngleDeg`. -4. Add the light transform to your `PlayerPositionsToShader` component's `otherLightSources` array. +4. Add the light transform to your `LightUpdater` component's `otherLightSources` array. 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. @@ -44,8 +44,8 @@ On PC, I haven't encountered any frame drops in the editor at all, even with 400 ## Editor preview -- While not in play mode, the editor helper `PlayerPositionsToShaderPreview` (EditorPreview/Editor/PlayerPositionsToShaderPreview.cs) and `ShadowcasterUpdaterPreview` (EditorPreview/Editor/ShadowcasterUpdaterPreview.cs) write the same property blocks to assigned Renderers so you can preview lighting effects in the Scene view. Those update 10 times a second. -- The editor partial helper for building preview arrays is in `EditorPreview/PlayerPositionsToShader.Editor.cs`. +- While not in play mode, the editor helper `LightUpdaterPreview` (EditorPreview/Editor/LightUpdaterPreview.cs) and `ShadowcasterUpdaterPreview` (EditorPreview/Editor/ShadowcasterUpdaterPreview.cs) write the same property blocks to assigned Renderers so you can preview lighting effects in the Scene view. Those update 10 times a second. +- The editor partial helper for building preview arrays is in `EditorPreview/LightUpdater.Editor.cs`. --- diff --git a/Scripts/PlayerPositionsToShader.cs b/Scripts/LightUpdater.cs similarity index 94% rename from Scripts/PlayerPositionsToShader.cs rename to Scripts/LightUpdater.cs index 5b8b2e5..bcecd95 100644 --- a/Scripts/PlayerPositionsToShader.cs +++ b/Scripts/LightUpdater.cs @@ -6,7 +6,7 @@ using VRC.SDKBase; using VRC.Udon; using VRC.SDK3.Rendering; -public partial class PlayerPositionsToShader : UdonSharpBehaviour +public partial class LightUpdater : UdonSharpBehaviour { [Header("Lightsources")] [Tooltip("Place Transforms here which should also emit Light (attach LightdataStorage to them).")] @@ -221,21 +221,29 @@ public partial class PlayerPositionsToShader : UdonSharpBehaviour Vector3 fwd = rot * Vector3.down; - float cosHalf = (data != null) ? data.GetCosHalfAngle() : 0f; + float Lightangle = (data != null) ? data.GetCosHalfAngle() : 0f; - Vector4 posTemp = new Vector4(pos.x, pos.y, pos.z, range); - if (_positions[currentCount] != posTemp) + Vector4 posTemp = Vector4.zero; + if (data.lightType == LightType.Sphere) { - _positions[currentCount] = posTemp; - _positons_isDirty = true; + posTemp = new Vector4(pos.x, pos.y, pos.z, range); } + else + { + posTemp = new Vector4(pos.x, pos.y, pos.z, Mathf.Cos(Mathf.Deg2Rad * ((data.spotAngleDeg * 0.5f) + Mathf.Max(data.range, 0)))); + } + if (_positions[currentCount] != posTemp) + { + _positions[currentCount] = posTemp; + _positons_isDirty = true; + } Vector4 colorTemp = new Vector4(col.x, col.y, col.z, intensity); if (_lightColors[currentCount] != colorTemp) { _lightColors[currentCount] = colorTemp; _lightColors_isDirty = true; } - Vector4 dirTemp = new Vector4(fwd.x, fwd.y, fwd.z, cosHalf); + Vector4 dirTemp = new Vector4(fwd.x, fwd.y, fwd.z, Lightangle); if (_directions[currentCount] != dirTemp) { _directions[currentCount] = dirTemp; diff --git a/Scripts/PlayerPositionsToShader.cs.meta b/Scripts/LightUpdater.cs.meta similarity index 100% rename from Scripts/PlayerPositionsToShader.cs.meta rename to Scripts/LightUpdater.cs.meta diff --git a/Shader/Includes/LightStrength.hlsl b/Shader/Includes/LightStrength.hlsl index d9119d0..cd53b39 100644 --- a/Shader/Includes/LightStrength.hlsl +++ b/Shader/Includes/LightStrength.hlsl @@ -11,10 +11,9 @@ else if (_Udon_LightType[LightCounter] == 1) \ { \ float invSq = _Udon_LightColors[LightCounter].a / max(1e-4, (distanceFromLight * invSqMul) * (distanceFromLight * invSqMul)); \ - float threshold = (-1 + _Udon_LightDirections[LightCounter].w / 180); \ \ - contrib = min(dot(normalize(i.worldPos - Lightposition), -normalize(_Udon_LightDirections[LightCounter].xyz)), 0); \ - contrib= 1 - step(threshold, contrib); \ + contrib = dot(normalize(i.worldPos - Lightposition), normalize(_Udon_LightDirections[LightCounter].xyz)); \ + contrib = smoothstep(radius,_Udon_LightDirections[LightCounter].w, contrib); \ \ contrib = contrib * invSq; \ dIntensity += contrib; \ diff --git a/Shader/Standard_Lightmap_2SP.shader b/Shader/Standard_Lightmap_2SP.shader new file mode 100644 index 0000000..51048fe --- /dev/null +++ b/Shader/Standard_Lightmap_2SP.shader @@ -0,0 +1,237 @@ +Shader "DeMuenu/World/Hoppou/Standard_Lightmap_2SP" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _NormalMap ("Normal Map", 2D) = "bump" {} + _NormalMapStrength ("Normal Map Strength", Range(0,1)) = 1 + _Color ("Color", Color) = (1,1,1,1) + + _EmmisiveText ("Emmissive Texture", 2D) = "white" {} + _EmmissiveColor ("Emmissive Color", Color) = (1,1,1,1) + _EmmissiveStrength ("Emmissive Strength", Range(0,10)) = 0 + + + //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 + + + + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + Cull[_Cull] + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile _ LIGHTMAP_ON + #pragma multi_compile _ DIRLIGHTMAP_COMBINED + + #include "UnityCG.cginc" + #include "Includes/LightStrength.hlsl" + #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 + //Moonlight Defines END + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + float3 normal : NORMAL; + float4 tangent : TANGENT; + float2 uv2 : TEXCOORD1; // Lightmap UV + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float2 uv2 : TEXCOORD1; + float2 uvEmmis : TEXCOORD4; + float4 vertex : SV_POSITION; + float2 normUV : TEXCOORD5; + float3 worldTangent : TEXCOORD6; + float3 worldBitangent : TEXCOORD7; + + //Moonlight + float3 worldPos : TEXCOORD2; + float3 worldNormal: TEXCOORD3; + //Moonlight END + + + #ifdef LIGHTMAP_ON + float2 lmuv : TEXCOORD8; + #endif + + + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + sampler2D _NormalMap; + float4 _NormalMap_ST; + float4 _Color; + float _NormalMapStrength; + + + + sampler2D _EmmisiveText; + float4 _EmmisiveText_ST; + float4 _EmmissiveColor; + float _EmmissiveStrength; + + + 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; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = TRANSFORM_TEX(v.uv, _MainTex); + o.normUV = TRANSFORM_TEX(v.uv, _NormalMap); + o.uvEmmis = TRANSFORM_TEX(v.uv, _EmmisiveText); + + float3 nWS = UnityObjectToWorldNormal(v.normal); + float3 tWS = normalize(UnityObjectToWorldDir(v.tangent.xyz)); + float3 bWS = normalize(cross(nWS, tWS) * v.tangent.w); + + o.worldNormal = nWS; + o.worldTangent = tWS; + o.worldBitangent= bWS; + + + //Moonlight Vertex + float4 wp = mul(unity_ObjectToWorld, v.vertex); + o.worldPos = wp.xyz; + //o.worldNormal = UnityObjectToWorldNormal(v.normal); + //Moonlight Vertex END + + #ifdef LIGHTMAP_ON + o.lmuv = v.uv2 * unity_LightmapST.xy + unity_LightmapST.zw; + #endif + + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + // sample the texture + fixed4 col = tex2D(_MainTex, i.uv); + fixed4 norm = tex2D(_NormalMap, i.normUV); + + fixed4 emmis = tex2D(_EmmisiveText, i.uvEmmis); + + + //Moonlight + float3 nTS = UnpackNormal(norm); + float3 NmapWS = normalize(i.worldTangent * nTS.x + + i.worldBitangent * nTS.y + + i.worldNormal * nTS.z); + float3 N = normalize(lerp(normalize(i.worldNormal), NmapWS, saturate(_NormalMapStrength))); + + OutLoopSetup(i, _Udon_PlayerCount) //defines count, N, dmax, dIntensity + + [loop] + for (int LightCounter = 0; LightCounter < MAX_LIGHTS; LightCounter++) + { + InLoopSetup(_Udon_LightPositions, LightCounter, count, i); //defines distanceFromLight, contrib + + + //Lambertian diffuse + 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; + + 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; + + } + //dmax.xyz = min(dmax * dIntensity, 1.0); + dmax.w = 1.0; + + //Moonlight END + + fixed3 lm = 0; + #ifdef LIGHTMAP_ON + // Decode handles RGBM/DoubleLDR and linear/gamma differences for you. + lm = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lmuv)); + + #ifdef DIRLIGHTMAP_COMBINED + // Directional lightmaps add dominant direction; improves shading on normal-mapped/curved surfaces + half4 dirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd,unity_Lightmap, i.lmuv); + lm = DecodeDirectionalLightmap(lm, dirTex, normalize(i.worldNormal)); + #endif + #endif + + return col * _Color * (dmax + float4(lm, 1)) + emmis * _EmmissiveStrength * _EmmissiveColor; + } + ENDCG + } + } + + FallBack "Diffuse" +} \ No newline at end of file diff --git a/Shader/Standard_Lightmap_2SP.shader.meta b/Shader/Standard_Lightmap_2SP.shader.meta new file mode 100644 index 0000000..b03f2c2 --- /dev/null +++ b/Shader/Standard_Lightmap_2SP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: dcd9aab68ad0b484f8382cdb61ef0cb9 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: