noradninja

ShadowAtlasManager

Mar 8th, 2026
42
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 37.49 KB | None | 0 0
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3.  
  4. [ExecuteAlways]
  5. public class ShadowAtlasManager : MonoBehaviour
  6. {
  7.     public const int AtlasWidth = 1024;
  8.     public const int AtlasHeight = 512;
  9.     public const int SlotCount = 5;
  10.  
  11.     [System.Serializable]
  12.     public struct ShadowAtlasSlot
  13.     {
  14.         public int index;
  15.         public RectInt pixelRect;
  16.         public Vector4 uvScaleOffset;
  17.         public int resolution;
  18.         public bool isDirectional;
  19.         public string debugName;
  20.     }
  21.  
  22.     [Header("References")]
  23.     public Transform playerTransform;
  24.     public Camera atlasCamera;
  25.  
  26.     [Header("Fixed Atlas Lights")]
  27.     public ShadowAtlasLight mainDirectional;
  28.     public ShadowAtlasLight playerSpotlight;
  29.  
  30.     [Header("Dynamic Atlas Slots")]
  31.     public ShadowAtlasLight extraSpotlight1;
  32.     public ShadowAtlasLight extraSpotlight2;
  33.     public ShadowAtlasLight extraSpotlight3;
  34.  
  35.     [Header("Atlas")]
  36.     public bool createOnEnable = true;
  37.     public bool clearAtlasOnCreate = true;
  38.     public FilterMode atlasFilterMode = FilterMode.Bilinear;
  39.     public TextureWrapMode atlasWrapMode = TextureWrapMode.Clamp;
  40.  
  41.     [Header("Debug Preview")]
  42.     public bool maintainDebugPreviewTexture = true;
  43.  
  44.     [Header("Culling")]
  45.     public LayerMask casterMask = ~0;
  46.  
  47.     [Header("Directional Shadow Framing")]
  48.     public float directionalOrthoSize = 15.0f;
  49.     public float directionalNear = 0.1f;
  50.     public float directionalFar = 40.0f;
  51.     public Vector3 directionalCenterOffset = Vector3.zero;
  52.  
  53.     [Header("Dynamic Slot Selection")]
  54.     public bool enableDynamicSpotSelection = true;
  55.     public int selectionUpdateIntervalFrames = 6;
  56.     public float selectionMaxDistance = 7.0f;
  57.     public float hysteresisDistanceBias = 2.0f;
  58.  
  59.     [Header("Caster Rendering")]
  60.     public Shader atlasShadowCasterShader;
  61.     public string replacementTag = "RenderType";
  62.  
  63.     [Header("Debug")]
  64.     public bool uploadGlobalsEveryFrame = true;
  65.     public bool forceFullRefresh = false;
  66.     public bool logRefreshes = false;
  67.     public bool logAssignments = false;
  68.     public bool logDynamicSelection = false;
  69.  
  70.     [SerializeField] RenderTexture _atlas;
  71.     [SerializeField] RenderTexture _atlasDebugTexture;
  72.     [SerializeField] ShadowAtlasSlot[] _slots = new ShadowAtlasSlot[SlotCount];
  73.  
  74.     int _frameCounter;
  75.     int _lastSelectionFrame = -999999;
  76.  
  77.     static readonly int ID_CustomShadowAtlas = Shader.PropertyToID("_CustomShadowAtlas");
  78.  
  79.     static readonly int ID_CustomDirWorldToShadow = Shader.PropertyToID("_CustomDirWorldToShadow");
  80.     static readonly int ID_CustomDirShadowRect = Shader.PropertyToID("_CustomDirShadowRect");
  81.     static readonly int ID_CustomDirShadowParams = Shader.PropertyToID("_CustomDirShadowParams");
  82.  
  83.     static readonly int ID_CustomSpotWorldToShadow0 = Shader.PropertyToID("_CustomSpotWorldToShadow0");
  84.     static readonly int ID_CustomSpotWorldToShadow1 = Shader.PropertyToID("_CustomSpotWorldToShadow1");
  85.     static readonly int ID_CustomSpotWorldToShadow2 = Shader.PropertyToID("_CustomSpotWorldToShadow2");
  86.     static readonly int ID_CustomSpotWorldToShadow3 = Shader.PropertyToID("_CustomSpotWorldToShadow3");
  87.  
  88.     static readonly int ID_CustomSpotShadowRect0 = Shader.PropertyToID("_CustomSpotShadowRect0");
  89.     static readonly int ID_CustomSpotShadowRect1 = Shader.PropertyToID("_CustomSpotShadowRect1");
  90.     static readonly int ID_CustomSpotShadowRect2 = Shader.PropertyToID("_CustomSpotShadowRect2");
  91.     static readonly int ID_CustomSpotShadowRect3 = Shader.PropertyToID("_CustomSpotShadowRect3");
  92.  
  93.     static readonly int ID_CustomSpotPosRange0 = Shader.PropertyToID("_CustomSpotPosRange0");
  94.     static readonly int ID_CustomSpotPosRange1 = Shader.PropertyToID("_CustomSpotPosRange1");
  95.     static readonly int ID_CustomSpotPosRange2 = Shader.PropertyToID("_CustomSpotPosRange2");
  96.     static readonly int ID_CustomSpotPosRange3 = Shader.PropertyToID("_CustomSpotPosRange3");
  97.  
  98.     static readonly int ID_CustomSpotDirCosOuter0 = Shader.PropertyToID("_CustomSpotDirCosOuter0");
  99.     static readonly int ID_CustomSpotDirCosOuter1 = Shader.PropertyToID("_CustomSpotDirCosOuter1");
  100.     static readonly int ID_CustomSpotDirCosOuter2 = Shader.PropertyToID("_CustomSpotDirCosOuter2");
  101.     static readonly int ID_CustomSpotDirCosOuter3 = Shader.PropertyToID("_CustomSpotDirCosOuter3");
  102.  
  103.     static readonly int ID_CustomSpotColorStrength0 = Shader.PropertyToID("_CustomSpotColorStrength0");
  104.     static readonly int ID_CustomSpotColorStrength1 = Shader.PropertyToID("_CustomSpotColorStrength1");
  105.     static readonly int ID_CustomSpotColorStrength2 = Shader.PropertyToID("_CustomSpotColorStrength2");
  106.     static readonly int ID_CustomSpotColorStrength3 = Shader.PropertyToID("_CustomSpotColorStrength3");
  107.  
  108.     static readonly int ID_CustomSpotShadowParams0 = Shader.PropertyToID("_CustomSpotShadowParams0");
  109.     static readonly int ID_CustomSpotShadowParams1 = Shader.PropertyToID("_CustomSpotShadowParams1");
  110.     static readonly int ID_CustomSpotShadowParams2 = Shader.PropertyToID("_CustomSpotShadowParams2");
  111.     static readonly int ID_CustomSpotShadowParams3 = Shader.PropertyToID("_CustomSpotShadowParams3");
  112.  
  113.     static readonly int ID_CustomShadowVP = Shader.PropertyToID("_CustomShadowVP");
  114.  
  115.     static readonly Matrix4x4 IdentityMatrix = Matrix4x4.identity;
  116.  
  117.     void OnEnable()
  118.     {
  119.         BuildFixedSlots();
  120.         EnsureAtlasCamera();
  121.  
  122.         if (createOnEnable)
  123.             EnsureAtlasResources();
  124.  
  125.         UpdateDynamicSpotSelection(true);
  126.         AssignSlots();
  127.  
  128.         if (uploadGlobalsEveryFrame)
  129.             UploadShaderGlobals();
  130.     }
  131.  
  132.     void OnDisable()
  133.     {
  134.         RestoreDynamicLightsToUnity();
  135.         RestoreFixedLightsToUnity();
  136.         ClearShaderGlobals();
  137.         ReleaseAtlasResources();
  138.         ReleaseHelperObjects();
  139.     }
  140.  
  141.     void OnValidate()
  142.     {
  143.         BuildFixedSlots();
  144.         AssignSlots();
  145.  
  146.         if (_atlas != null)
  147.             Shader.SetGlobalTexture(ID_CustomShadowAtlas, _atlas);
  148.     }
  149.  
  150.     void LateUpdate()
  151.     {
  152.         _frameCounter++;
  153.  
  154.         BuildFixedSlots();
  155.         EnsureAtlasCamera();
  156.         EnsureAtlasResources();
  157.  
  158.         UpdateDynamicSpotSelection(false);
  159.         AssignSlots();
  160.         RenderScheduledLights();
  161.  
  162.         if (maintainDebugPreviewTexture)
  163.             RefreshDebugPreviewTexture();
  164.  
  165.         if (uploadGlobalsEveryFrame)
  166.             UploadShaderGlobals();
  167.     }
  168.  
  169.     public RenderTexture AtlasTexture
  170.     {
  171.         get { return _atlas; }
  172.     }
  173.  
  174.     public RenderTexture AtlasDebugTexture
  175.     {
  176.         get { return _atlasDebugTexture; }
  177.     }
  178.  
  179.     public int CurrentFrameCounter
  180.     {
  181.         get { return _frameCounter; }
  182.     }
  183.  
  184.     /* Rebuilds our fixed atlas tile layout. */
  185.     void BuildFixedSlots()
  186.     {
  187.         if (_slots == null || _slots.Length != SlotCount)
  188.             _slots = new ShadowAtlasSlot[SlotCount];
  189.  
  190.         _slots[0] = CreateSlot(0, new RectInt(0, 0, 512, 512), true, "Directional");
  191.         _slots[1] = CreateSlot(1, new RectInt(512, 0, 256, 256), false, "PlayerSpot");
  192.         _slots[2] = CreateSlot(2, new RectInt(768, 0, 256, 256), false, "ExtraSpot1");
  193.         _slots[3] = CreateSlot(3, new RectInt(512, 256, 256, 256), false, "ExtraSpot2");
  194.         _slots[4] = CreateSlot(4, new RectInt(768, 256, 256, 256), false, "ExtraSpot3");
  195.     }
  196.  
  197.     /* Creates one atlas tile descriptor. */
  198.     ShadowAtlasSlot CreateSlot(int index, RectInt rect, bool isDirectional, string debugName)
  199.     {
  200.         ShadowAtlasSlot slot = new ShadowAtlasSlot();
  201.         slot.index = index;
  202.         slot.pixelRect = rect;
  203.         slot.resolution = rect.width;
  204.         slot.isDirectional = isDirectional;
  205.         slot.debugName = debugName;
  206.         slot.uvScaleOffset = new Vector4(
  207.             rect.width / (float)AtlasWidth,
  208.             rect.height / (float)AtlasHeight,
  209.             rect.x / (float)AtlasWidth,
  210.             rect.y / (float)AtlasHeight
  211.         );
  212.         return slot;
  213.     }
  214.  
  215.     /* Creates our hidden camera used to render atlas shadow tiles. */
  216.     void EnsureAtlasCamera()
  217.     {
  218.         if (atlasCamera != null)
  219.             return;
  220.  
  221.         GameObject go = new GameObject("Shadow Atlas Camera");
  222.         go.hideFlags = HideFlags.HideAndDontSave;
  223.  
  224.         atlasCamera = go.AddComponent<Camera>();
  225.         atlasCamera.enabled = false;
  226.         atlasCamera.allowHDR = false;
  227.         atlasCamera.allowMSAA = false;
  228.         atlasCamera.useOcclusionCulling = false;
  229.         atlasCamera.clearFlags = CameraClearFlags.SolidColor;
  230.         atlasCamera.backgroundColor = Color.white;
  231.         atlasCamera.orthographic = false;
  232.         atlasCamera.nearClipPlane = 0.05f;
  233.         atlasCamera.farClipPlane = 100.0f;
  234.         atlasCamera.depthTextureMode = DepthTextureMode.None;
  235.         atlasCamera.renderingPath = RenderingPath.Forward;
  236.         atlasCamera.cullingMask = casterMask;
  237.     }
  238.  
  239.     /* Ensures our atlas textures exist before we render or preview them. */
  240.     void EnsureAtlasResources()
  241.     {
  242.         EnsureShadowAtlas();
  243.  
  244.         if (maintainDebugPreviewTexture)
  245.             EnsureDebugAtlas();
  246.         else
  247.             ReleaseDebugAtlas();
  248.     }
  249.  
  250.     /* Creates our shadow atlas render target. */
  251.     void EnsureShadowAtlas()
  252.     {
  253.         if (_atlas != null && _atlas.width == AtlasWidth && _atlas.height == AtlasHeight)
  254.             return;
  255.  
  256.         ReleaseShadowAtlasOnly();
  257.  
  258.         _atlas = new RenderTexture(AtlasWidth, AtlasHeight, 16, RenderTextureFormat.Shadowmap);
  259.         _atlas.name = "CustomShadowAtlas_1024x512";
  260.         _atlas.useMipMap = false;
  261.         _atlas.autoGenerateMips = false;
  262.         _atlas.filterMode = atlasFilterMode;
  263.         _atlas.wrapMode = atlasWrapMode;
  264.         _atlas.Create();
  265.  
  266.         Shader.SetGlobalTexture(ID_CustomShadowAtlas, _atlas);
  267.  
  268.         if (clearAtlasOnCreate)
  269.             ClearWholeAtlas();
  270.     }
  271.  
  272.     /* Creates our debug preview copy of the atlas. */
  273.     void EnsureDebugAtlas()
  274.     {
  275.         if (_atlasDebugTexture != null &&
  276.             _atlasDebugTexture.width == AtlasWidth &&
  277.             _atlasDebugTexture.height == AtlasHeight)
  278.             return;
  279.  
  280.         ReleaseDebugAtlas();
  281.  
  282.         _atlasDebugTexture = new RenderTexture(AtlasWidth, AtlasHeight, 0, RenderTextureFormat.ARGB32);
  283.         _atlasDebugTexture.name = "CustomShadowAtlas_Debug_1024x512";
  284.         _atlasDebugTexture.useMipMap = false;
  285.         _atlasDebugTexture.autoGenerateMips = false;
  286.         _atlasDebugTexture.filterMode = FilterMode.Point;
  287.         _atlasDebugTexture.wrapMode = TextureWrapMode.Clamp;
  288.         _atlasDebugTexture.Create();
  289.     }
  290.  
  291.     /* Releases all atlas resources. */
  292.     void ReleaseAtlasResources()
  293.     {
  294.         ReleaseShadowAtlasOnly();
  295.         ReleaseDebugAtlas();
  296.     }
  297.  
  298.     /* Releases only the live shadow atlas. */
  299.     void ReleaseShadowAtlasOnly()
  300.     {
  301.         if (_atlas == null)
  302.             return;
  303.  
  304.         _atlas.Release();
  305.  
  306. #if UNITY_EDITOR
  307.         if (!Application.isPlaying)
  308.             DestroyImmediate(_atlas);
  309.         else
  310.             Destroy(_atlas);
  311. #else
  312.         Destroy(_atlas);
  313. #endif
  314.         _atlas = null;
  315.     }
  316.  
  317.     /* Releases only the debug preview atlas. */
  318.     void ReleaseDebugAtlas()
  319.     {
  320.         if (_atlasDebugTexture == null)
  321.             return;
  322.  
  323.         _atlasDebugTexture.Release();
  324.  
  325. #if UNITY_EDITOR
  326.         if (!Application.isPlaying)
  327.             DestroyImmediate(_atlasDebugTexture);
  328.         else
  329.             Destroy(_atlasDebugTexture);
  330. #else
  331.         Destroy(_atlasDebugTexture);
  332. #endif
  333.         _atlasDebugTexture = null;
  334.     }
  335.  
  336.     /* Releases our hidden helper camera when the manager shuts down. */
  337.     void ReleaseHelperObjects()
  338.     {
  339.         if (atlasCamera == null)
  340.             return;
  341.  
  342.         if (atlasCamera.gameObject.hideFlags != HideFlags.HideAndDontSave)
  343.             return;
  344.  
  345. #if UNITY_EDITOR
  346.         if (!Application.isPlaying)
  347.             DestroyImmediate(atlasCamera.gameObject);
  348.         else
  349.             Destroy(atlasCamera.gameObject);
  350. #else
  351.         Destroy(atlasCamera.gameObject);
  352. #endif
  353.         atlasCamera = null;
  354.     }
  355.  
  356.     /* Copies our atlas to a preview-friendly texture for the inspector. */
  357.     void RefreshDebugPreviewTexture()
  358.     {
  359.         if (_atlas == null || _atlasDebugTexture == null)
  360.             return;
  361.  
  362.         Graphics.Blit(_atlas, _atlasDebugTexture);
  363.     }
  364.  
  365.     /* Clears the entire atlas to white depth. */
  366.     void ClearWholeAtlas()
  367.     {
  368.         if (_atlas == null)
  369.             return;
  370.  
  371.         RenderTexture previous = RenderTexture.active;
  372.         RenderTexture.active = _atlas;
  373.         GL.Clear(true, true, Color.white);
  374.         RenderTexture.active = previous;
  375.     }
  376.  
  377.     /* Clears one atlas tile before we redraw it. */
  378.     void ClearSlot(ShadowAtlasSlot slot)
  379.     {
  380.         if (_atlas == null)
  381.             return;
  382.  
  383.         RenderTexture previous = RenderTexture.active;
  384.         RenderTexture.active = _atlas;
  385.  
  386.         GL.PushMatrix();
  387.         GL.Viewport(new Rect(slot.pixelRect.x, slot.pixelRect.y, slot.pixelRect.width, slot.pixelRect.height));
  388.         GL.Clear(true, true, Color.white);
  389.         GL.PopMatrix();
  390.  
  391.         RenderTexture.active = previous;
  392.     }
  393.  
  394.     /* Updates our three dynamic spotlight slots using distance, priority, and hysteresis. */
  395.     void UpdateDynamicSpotSelection(bool forceUpdate)
  396.     {
  397.         if (!enableDynamicSpotSelection)
  398.             return;
  399.  
  400.         if (!forceUpdate)
  401.         {
  402.             int interval = Mathf.Max(1, selectionUpdateIntervalFrames);
  403.             if ((_frameCounter - _lastSelectionFrame) < interval)
  404.                 return;
  405.         }
  406.  
  407.         _lastSelectionFrame = _frameCounter;
  408.  
  409.         List<ShadowAtlasLight> candidates = GatherDynamicSpotCandidates();
  410.  
  411.         ShadowAtlasLight slot2 = ChooseBestLightForSlot(extraSpotlight1, candidates);
  412.         if (slot2 != null)
  413.             candidates.Remove(slot2);
  414.  
  415.         ShadowAtlasLight slot3 = ChooseBestLightForSlot(extraSpotlight2, candidates);
  416.         if (slot3 != null)
  417.             candidates.Remove(slot3);
  418.  
  419.         ShadowAtlasLight slot4 = ChooseBestLightForSlot(extraSpotlight3, candidates);
  420.         if (slot4 != null)
  421.             candidates.Remove(slot4);
  422.  
  423.         SetDynamicSlot(ref extraSpotlight1, slot2, 2);
  424.         SetDynamicSlot(ref extraSpotlight2, slot3, 3);
  425.         SetDynamicSlot(ref extraSpotlight3, slot4, 4);
  426.     }
  427.  
  428.     /* Gathers atlas-eligible spotlight candidates within our dynamic range. */
  429.     List<ShadowAtlasLight> GatherDynamicSpotCandidates()
  430.     {
  431.         List<ShadowAtlasLight> results = new List<ShadowAtlasLight>();
  432.         ShadowAtlasLight[] allLights = FindObjectsOfType<ShadowAtlasLight>();
  433.  
  434.         float maxDistSq = selectionMaxDistance * selectionMaxDistance;
  435.  
  436.         for (int i = 0; i < allLights.Length; i++)
  437.         {
  438.             ShadowAtlasLight light = allLights[i];
  439.  
  440.             if (light == null)
  441.                 continue;
  442.  
  443.             if (!light.HasValidLight())
  444.                 continue;
  445.  
  446.             if (light == mainDirectional)
  447.                 continue;
  448.  
  449.             if (light == playerSpotlight)
  450.                 continue;
  451.  
  452.             if (light.lightType != ShadowAtlasLight.AtlasShadowLightType.Spot)
  453.                 continue;
  454.  
  455.             if (light.unityLight == null || light.unityLight.type != LightType.Spot)
  456.                 continue;
  457.  
  458.             if (playerTransform != null)
  459.             {
  460.                 float distSq = (playerTransform.position - light.transform.position).sqrMagnitude;
  461.                 if (distSq > maxDistSq)
  462.                     continue;
  463.             }
  464.  
  465.             results.Add(light);
  466.         }
  467.  
  468.         results.Sort(CompareCandidates);
  469.         return results;
  470.     }
  471.  
  472.     /* Sorts candidates by priority first, then by distance to the player. */
  473.     int CompareCandidates(ShadowAtlasLight a, ShadowAtlasLight b)
  474.     {
  475.         int priorityCompare = b.priority.CompareTo(a.priority);
  476.         if (priorityCompare != 0)
  477.             return priorityCompare;
  478.  
  479.         float da = GetPlayerDistanceSqr(a);
  480.         float db = GetPlayerDistanceSqr(b);
  481.         return da.CompareTo(db);
  482.     }
  483.  
  484.     /* Gets squared distance from one light to the player. */
  485.     float GetPlayerDistanceSqr(ShadowAtlasLight light)
  486.     {
  487.         if (light == null || playerTransform == null)
  488.             return float.MaxValue;
  489.  
  490.         return (playerTransform.position - light.transform.position).sqrMagnitude;
  491.     }
  492.  
  493.     /* Keeps our current owner unless a challenger is clearly better. */
  494.     ShadowAtlasLight ChooseBestLightForSlot(ShadowAtlasLight current, List<ShadowAtlasLight> candidates)
  495.     {
  496.         if (candidates == null || candidates.Count == 0)
  497.             return null;
  498.  
  499.         ShadowAtlasLight best = candidates[0];
  500.  
  501.         if (current == null)
  502.             return best;
  503.  
  504.         if (!candidates.Contains(current))
  505.             return best;
  506.  
  507.         int currentPriority = current.priority;
  508.         int bestPriority = best.priority;
  509.  
  510.         if (currentPriority > bestPriority)
  511.             return current;
  512.  
  513.         if (currentPriority < bestPriority)
  514.             return best;
  515.  
  516.         float currentDist = Mathf.Sqrt(GetPlayerDistanceSqr(current));
  517.         float bestDist = Mathf.Sqrt(GetPlayerDistanceSqr(best));
  518.  
  519.         if (bestDist + hysteresisDistanceBias < currentDist)
  520.             return best;
  521.  
  522.         return current;
  523.     }
  524.  
  525.     /* Updates one dynamic slot and keeps Unity suppression matched to active slot ownership. */
  526.     void SetDynamicSlot(ref ShadowAtlasLight slotField, ShadowAtlasLight newLight, int atlasSlotIndex)
  527.     {
  528.         ShadowAtlasLight oldLight = slotField;
  529.  
  530.         if (oldLight == newLight)
  531.         {
  532.             if (newLight != null)
  533.             {
  534.                 newLight.atlasSlot = atlasSlotIndex;
  535.                 newLight.registered = true;
  536.  
  537.                 if (!newLight.IsUnityLightingSuppressed())
  538.                     newLight.SuppressUnityLighting();
  539.             }
  540.  
  541.             return;
  542.         }
  543.  
  544.         if (oldLight != null)
  545.         {
  546.             oldLight.atlasSlot = -1;
  547.             oldLight.registered = false;
  548.             oldLight.RestoreUnityLighting();
  549.         }
  550.  
  551.         slotField = newLight;
  552.  
  553.         if (newLight != null)
  554.         {
  555.             newLight.atlasSlot = atlasSlotIndex;
  556.             newLight.registered = true;
  557.             newLight.SuppressUnityLighting();
  558.         }
  559.  
  560.         if (logDynamicSelection)
  561.         {
  562.             string oldName = oldLight != null ? oldLight.name : "None";
  563.             string newName = newLight != null ? newLight.name : "None";
  564.  
  565.             Debug.Log(
  566.                 "[ShadowAtlasManager] Dynamic slot " +
  567.                 atlasSlotIndex +
  568.                 " changed from " +
  569.                 oldName +
  570.                 " to " +
  571.                 newName +
  572.                 "."
  573.             );
  574.         }
  575.     }
  576.  
  577.     /* Restores dropped dynamic slot lights to normal Unity lighting. */
  578.     void RestoreDynamicLightsToUnity()
  579.     {
  580.         if (extraSpotlight1 != null) extraSpotlight1.RestoreUnityLighting();
  581.         if (extraSpotlight2 != null) extraSpotlight2.RestoreUnityLighting();
  582.         if (extraSpotlight3 != null) extraSpotlight3.RestoreUnityLighting();
  583.     }
  584.  
  585.     /* Restores fixed lights to Unity lighting when our manager shuts down. */
  586.     void RestoreFixedLightsToUnity()
  587.     {
  588.         if (mainDirectional != null) mainDirectional.RestoreUnityLighting();
  589.         if (playerSpotlight != null) playerSpotlight.RestoreUnityLighting();
  590.     }
  591.  
  592.     /* Assigns atlas slot metadata to all currently active atlas owners. */
  593.     void AssignSlots()
  594.     {
  595.         ResetRegistration(mainDirectional);
  596.         ResetRegistration(playerSpotlight);
  597.         ResetRegistration(extraSpotlight1);
  598.         ResetRegistration(extraSpotlight2);
  599.         ResetRegistration(extraSpotlight3);
  600.  
  601.         if (mainDirectional != null)
  602.             mainDirectional.SuppressUnityLighting();
  603.  
  604.         if (playerSpotlight != null)
  605.             playerSpotlight.SuppressUnityLighting();
  606.  
  607.         AssignLightToSlot(mainDirectional, 0, true);
  608.         AssignLightToSlot(playerSpotlight, 1, true);
  609.         AssignLightToSlot(extraSpotlight1, 2, true);
  610.         AssignLightToSlot(extraSpotlight2, 3, true);
  611.         AssignLightToSlot(extraSpotlight3, 4, true);
  612.     }
  613.  
  614.     /* Clears runtime slot registration data for one light. */
  615.     void ResetRegistration(ShadowAtlasLight light)
  616.     {
  617.         if (light == null)
  618.             return;
  619.  
  620.         light.registered = false;
  621.         light.atlasSlot = -1;
  622.     }
  623.  
  624.     /* Registers a light to a slot and suppresses Unity lighting for active atlas owners. */
  625.     void AssignLightToSlot(ShadowAtlasLight light, int slotIndex, bool suppressUnityLighting)
  626.     {
  627.         if (light == null)
  628.             return;
  629.  
  630.         light.registered = true;
  631.         light.atlasSlot = slotIndex;
  632.  
  633.         if (suppressUnityLighting)
  634.             light.SuppressUnityLighting();
  635.  
  636.         if (logAssignments)
  637.         {
  638.             Debug.Log(
  639.                 "[ShadowAtlasManager] Assigned " +
  640.                 light.name +
  641.                 " -> slot " +
  642.                 slotIndex +
  643.                 " (" +
  644.                 _slots[slotIndex].debugName +
  645.                 ")."
  646.             );
  647.         }
  648.     }
  649.  
  650.     /* Renders all active atlas owners that need a refresh this frame. */
  651.     void RenderScheduledLights()
  652.     {
  653.         RenderIfNeeded(mainDirectional, 0, true);
  654.         RenderIfNeeded(playerSpotlight, 1, true);
  655.         RenderIfNeeded(extraSpotlight1, 2, IsNearPlayer(extraSpotlight1));
  656.         RenderIfNeeded(extraSpotlight2, 3, IsNearPlayer(extraSpotlight2));
  657.         RenderIfNeeded(extraSpotlight3, 4, IsNearPlayer(extraSpotlight3));
  658.     }
  659.  
  660.     /* Renders one light into its atlas tile if its schedule says to update. */
  661.     void RenderIfNeeded(ShadowAtlasLight light, int slotIndex, bool isNearPlayer)
  662.     {
  663.         if (light == null || !light.HasValidLight())
  664.             return;
  665.  
  666.         bool refresh = ShouldRefreshLight(light, isNearPlayer);
  667.         if (!refresh)
  668.             return;
  669.  
  670.         if (logRefreshes)
  671.         {
  672.             Debug.Log(
  673.                 "[ShadowAtlasManager] Rendering " +
  674.                 light.name +
  675.                 " into slot " +
  676.                 slotIndex +
  677.                 " on frame " +
  678.                 _frameCounter +
  679.                 "."
  680.             );
  681.         }
  682.  
  683.         bool success = false;
  684.  
  685.         if (light.lightType == ShadowAtlasLight.AtlasShadowLightType.Directional)
  686.             success = RenderDirectionalToSlot(light, _slots[slotIndex]);
  687.         else
  688.             success = RenderSpotToSlot(light, _slots[slotIndex]);
  689.  
  690.         if (success)
  691.             MarkLightRendered(light);
  692.     }
  693.  
  694.     /* Decides whether one atlas light needs a shadow refresh this frame. */
  695.     public bool ShouldRefreshLight(ShadowAtlasLight light, bool isNearPlayer)
  696.     {
  697.         if (light == null)
  698.             return false;
  699.  
  700.         if (!light.HasValidLight())
  701.             return false;
  702.  
  703.         if (forceFullRefresh)
  704.             return true;
  705.  
  706.         if (light.forceRefresh)
  707.             return true;
  708.  
  709.         if (light.dirty)
  710.             return true;
  711.  
  712.         if (light.TransformChanged())
  713.             return true;
  714.  
  715.         switch (light.updateMode)
  716.         {
  717.             case ShadowUpdateMode.EveryFrame:
  718.                 return true;
  719.  
  720.             case ShadowUpdateMode.EveryOtherFrame:
  721.                 return ((_frameCounter + light.frameOffset) % 2) == 0;
  722.  
  723.             case ShadowUpdateMode.EveryThirdFrame:
  724.                 return ((_frameCounter + light.frameOffset) % 3) == 0;
  725.  
  726.             case ShadowUpdateMode.EveryFifthFrame:
  727.                 return ((_frameCounter + light.frameOffset) % 5) == 0;
  728.  
  729.             case ShadowUpdateMode.WhenCloseElseHold:
  730.                 return isNearPlayer;
  731.  
  732.             case ShadowUpdateMode.WhenCloseElseEveryFifthFrame:
  733.                 if (isNearPlayer)
  734.                     return true;
  735.  
  736.                 return ((_frameCounter + light.frameOffset) % 5) == 0;
  737.  
  738.             case ShadowUpdateMode.Manual:
  739.             default:
  740.                 return false;
  741.         }
  742.     }
  743.  
  744.     /* Checks whether a light is close enough to the player for near-based updates. */
  745.     public bool IsNearPlayer(ShadowAtlasLight light)
  746.     {
  747.         if (light == null || playerTransform == null)
  748.             return false;
  749.  
  750.         float sqrDist = (playerTransform.position - light.transform.position).sqrMagnitude;
  751.         return sqrDist <= light.GetCloseDistanceSqr();
  752.     }
  753.  
  754.     /* Marks one light as freshly rendered and updates its cached state. */
  755.     public void MarkLightRendered(ShadowAtlasLight light)
  756.     {
  757.         if (light == null)
  758.             return;
  759.  
  760.         light.lastRenderedFrame = _frameCounter;
  761.         light.ClearDirty();
  762.         light.CaptureCurrentTransform();
  763.     }
  764.  
  765.     /* Renders our fixed directional light into its atlas tile. */
  766.     bool RenderDirectionalToSlot(ShadowAtlasLight light, ShadowAtlasSlot slot)
  767.     {
  768.         if (!PrepareRender(light))
  769.             return false;
  770.  
  771.         Matrix4x4 gpuProj;
  772.         Matrix4x4 view;
  773.         Matrix4x4 rawVP;
  774.         Matrix4x4 worldToShadowTex;
  775.  
  776.         BuildDirectionalMatrices(light, out view, out gpuProj, out rawVP, out worldToShadowTex);
  777.  
  778.         atlasCamera.targetTexture = _atlas;
  779.         atlasCamera.pixelRect = new Rect(slot.pixelRect.x, slot.pixelRect.y, slot.pixelRect.width, slot.pixelRect.height);
  780.         atlasCamera.rect = new Rect(
  781.             slot.pixelRect.x / (float)AtlasWidth,
  782.             slot.pixelRect.y / (float)AtlasHeight,
  783.             slot.pixelRect.width / (float)AtlasWidth,
  784.             slot.pixelRect.height / (float)AtlasHeight
  785.         );
  786.  
  787.         atlasCamera.cullingMask = casterMask;
  788.         atlasCamera.clearFlags = CameraClearFlags.SolidColor;
  789.         atlasCamera.backgroundColor = Color.white;
  790.         atlasCamera.orthographic = true;
  791.         atlasCamera.orthographicSize = directionalOrthoSize;
  792.         atlasCamera.nearClipPlane = directionalNear;
  793.         atlasCamera.farClipPlane = directionalFar;
  794.         atlasCamera.worldToCameraMatrix = view;
  795.         atlasCamera.projectionMatrix = gpuProj;
  796.  
  797.         Shader.SetGlobalMatrix(ID_CustomShadowVP, rawVP);
  798.  
  799.         ClearSlot(slot);
  800.         atlasCamera.RenderWithShader(atlasShadowCasterShader, replacementTag);
  801.  
  802.         return true;
  803.     }
  804.  
  805.     /* Renders one spotlight into its atlas tile. */
  806.     bool RenderSpotToSlot(ShadowAtlasLight light, ShadowAtlasSlot slot)
  807.     {
  808.         if (!PrepareRender(light))
  809.             return false;
  810.  
  811.         Light l = light.unityLight;
  812.         if (l == null)
  813.             return false;
  814.  
  815.         Matrix4x4 gpuProj;
  816.         Matrix4x4 view;
  817.         Matrix4x4 rawVP;
  818.         Matrix4x4 worldToShadowTex;
  819.  
  820.         BuildSpotMatrices(l, out view, out gpuProj, out rawVP, out worldToShadowTex);
  821.  
  822.         atlasCamera.targetTexture = _atlas;
  823.         atlasCamera.pixelRect = new Rect(slot.pixelRect.x, slot.pixelRect.y, slot.pixelRect.width, slot.pixelRect.height);
  824.         atlasCamera.rect = new Rect(
  825.             slot.pixelRect.x / (float)AtlasWidth,
  826.             slot.pixelRect.y / (float)AtlasHeight,
  827.             slot.pixelRect.width / (float)AtlasWidth,
  828.             slot.pixelRect.height / (float)AtlasHeight
  829.         );
  830.  
  831.         atlasCamera.cullingMask = casterMask;
  832.         atlasCamera.clearFlags = CameraClearFlags.SolidColor;
  833.         atlasCamera.backgroundColor = Color.white;
  834.         atlasCamera.orthographic = false;
  835.         atlasCamera.fieldOfView = l.spotAngle;
  836.         atlasCamera.aspect = 1.0f;
  837.         atlasCamera.nearClipPlane = 0.05f;
  838.         atlasCamera.farClipPlane = Mathf.Max(0.06f, l.range);
  839.         atlasCamera.worldToCameraMatrix = view;
  840.         atlasCamera.projectionMatrix = gpuProj;
  841.  
  842.         Shader.SetGlobalMatrix(ID_CustomShadowVP, rawVP);
  843.  
  844.         ClearSlot(slot);
  845.         atlasCamera.RenderWithShader(atlasShadowCasterShader, replacementTag);
  846.  
  847.         return true;
  848.     }
  849.  
  850.     /* Verifies that our render prerequisites are valid. */
  851.     bool PrepareRender(ShadowAtlasLight light)
  852.     {
  853.         if (_atlas == null)
  854.             return false;
  855.  
  856.         if (atlasCamera == null)
  857.             return false;
  858.  
  859.         if (atlasShadowCasterShader == null)
  860.             return false;
  861.  
  862.         if (!light.HasValidLight())
  863.             return false;
  864.  
  865.         return true;
  866.     }
  867.  
  868.     /* Uploads current atlas and light data to the shader globals. */
  869.     public void UploadShaderGlobals()
  870.     {
  871.         if (_atlas != null)
  872.             Shader.SetGlobalTexture(ID_CustomShadowAtlas, _atlas);
  873.  
  874.         UploadDirectionalGlobals();
  875.         UploadSpotGlobals(0, playerSpotlight, 1);
  876.         UploadSpotGlobals(1, extraSpotlight1, 2);
  877.         UploadSpotGlobals(2, extraSpotlight2, 3);
  878.         UploadSpotGlobals(3, extraSpotlight3, 4);
  879.     }
  880.  
  881.     /* Uploads our directional atlas light state. */
  882.     void UploadDirectionalGlobals()
  883.     {
  884.         if (mainDirectional == null || !mainDirectional.HasValidLight())
  885.         {
  886.             Shader.SetGlobalMatrix(ID_CustomDirWorldToShadow, IdentityMatrix);
  887.             Shader.SetGlobalVector(ID_CustomDirShadowRect, Vector4.zero);
  888.             Shader.SetGlobalVector(ID_CustomDirShadowParams, Vector4.zero);
  889.             return;
  890.         }
  891.  
  892.         Matrix4x4 view;
  893.         Matrix4x4 gpuProj;
  894.         Matrix4x4 rawVP;
  895.         Matrix4x4 worldToShadowTex;
  896.  
  897.         BuildDirectionalMatrices(mainDirectional, out view, out gpuProj, out rawVP, out worldToShadowTex);
  898.  
  899.         ShadowAtlasSlot slot = _slots[0];
  900.  
  901.         Shader.SetGlobalMatrix(ID_CustomDirWorldToShadow, worldToShadowTex);
  902.         Shader.SetGlobalVector(ID_CustomDirShadowRect, slot.uvScaleOffset);
  903.         Shader.SetGlobalVector(
  904.             ID_CustomDirShadowParams,
  905.             new Vector4(mainDirectional.shadowStrength, mainDirectional.bias, mainDirectional.normalBias, 1.0f)
  906.         );
  907.     }
  908.  
  909.     /* Uploads one spotlight atlas light state. */
  910.     void UploadSpotGlobals(int spotIndex, ShadowAtlasLight light, int slotIndex)
  911.     {
  912.         int idWorldToShadow;
  913.         int idShadowRect;
  914.         int idPosRange;
  915.         int idDirCosOuter;
  916.         int idColorStrength;
  917.         int idShadowParams;
  918.  
  919.         GetSpotShaderIDs(
  920.             spotIndex,
  921.             out idWorldToShadow,
  922.             out idShadowRect,
  923.             out idPosRange,
  924.             out idDirCosOuter,
  925.             out idColorStrength,
  926.             out idShadowParams
  927.         );
  928.  
  929.         if (light == null || !light.HasValidLight())
  930.         {
  931.             Shader.SetGlobalMatrix(idWorldToShadow, IdentityMatrix);
  932.             Shader.SetGlobalVector(idShadowRect, Vector4.zero);
  933.             Shader.SetGlobalVector(idPosRange, Vector4.zero);
  934.             Shader.SetGlobalVector(idDirCosOuter, Vector4.zero);
  935.             Shader.SetGlobalVector(idColorStrength, Vector4.zero);
  936.             Shader.SetGlobalVector(idShadowParams, Vector4.zero);
  937.             return;
  938.         }
  939.  
  940.         Light l = light.unityLight;
  941.         ShadowAtlasSlot slot = _slots[slotIndex];
  942.  
  943.         Matrix4x4 view;
  944.         Matrix4x4 gpuProj;
  945.         Matrix4x4 rawVP;
  946.         Matrix4x4 worldToShadowTex;
  947.  
  948.         BuildSpotMatrices(l, out view, out gpuProj, out rawVP, out worldToShadowTex);
  949.  
  950.         Vector3 pos = l.transform.position;
  951.         Vector3 dir = l.transform.forward;
  952.         Color c = l.color;
  953.         float intensity = l.intensity;
  954.         float range = l.range;
  955.         float cosOuter = Mathf.Cos(0.5f * l.spotAngle * Mathf.Deg2Rad);
  956.  
  957.         Shader.SetGlobalMatrix(idWorldToShadow, worldToShadowTex);
  958.         Shader.SetGlobalVector(idShadowRect, slot.uvScaleOffset);
  959.         Shader.SetGlobalVector(idPosRange, new Vector4(pos.x, pos.y, pos.z, range));
  960.         Shader.SetGlobalVector(idDirCosOuter, new Vector4(dir.x, dir.y, dir.z, cosOuter));
  961.         Shader.SetGlobalVector(idColorStrength, new Vector4(c.r, c.g, c.b, intensity));
  962.         Shader.SetGlobalVector(idShadowParams, new Vector4(light.shadowStrength, light.bias, light.normalBias, 1.0f));
  963.     }
  964.  
  965.     /* Clears our custom shader globals when the manager is disabled. */
  966.     void ClearShaderGlobals()
  967.     {
  968.         Shader.SetGlobalVector(ID_CustomDirShadowRect, Vector4.zero);
  969.         Shader.SetGlobalVector(ID_CustomDirShadowParams, Vector4.zero);
  970.  
  971.         Shader.SetGlobalVector(ID_CustomSpotShadowRect0, Vector4.zero);
  972.         Shader.SetGlobalVector(ID_CustomSpotShadowRect1, Vector4.zero);
  973.         Shader.SetGlobalVector(ID_CustomSpotShadowRect2, Vector4.zero);
  974.         Shader.SetGlobalVector(ID_CustomSpotShadowRect3, Vector4.zero);
  975.  
  976.         Shader.SetGlobalVector(ID_CustomSpotPosRange0, Vector4.zero);
  977.         Shader.SetGlobalVector(ID_CustomSpotPosRange1, Vector4.zero);
  978.         Shader.SetGlobalVector(ID_CustomSpotPosRange2, Vector4.zero);
  979.         Shader.SetGlobalVector(ID_CustomSpotPosRange3, Vector4.zero);
  980.  
  981.         Shader.SetGlobalVector(ID_CustomSpotDirCosOuter0, Vector4.zero);
  982.         Shader.SetGlobalVector(ID_CustomSpotDirCosOuter1, Vector4.zero);
  983.         Shader.SetGlobalVector(ID_CustomSpotDirCosOuter2, Vector4.zero);
  984.         Shader.SetGlobalVector(ID_CustomSpotDirCosOuter3, Vector4.zero);
  985.  
  986.         Shader.SetGlobalVector(ID_CustomSpotColorStrength0, Vector4.zero);
  987.         Shader.SetGlobalVector(ID_CustomSpotColorStrength1, Vector4.zero);
  988.         Shader.SetGlobalVector(ID_CustomSpotColorStrength2, Vector4.zero);
  989.         Shader.SetGlobalVector(ID_CustomSpotColorStrength3, Vector4.zero);
  990.  
  991.         Shader.SetGlobalVector(ID_CustomSpotShadowParams0, Vector4.zero);
  992.         Shader.SetGlobalVector(ID_CustomSpotShadowParams1, Vector4.zero);
  993.         Shader.SetGlobalVector(ID_CustomSpotShadowParams2, Vector4.zero);
  994.         Shader.SetGlobalVector(ID_CustomSpotShadowParams3, Vector4.zero);
  995.     }
  996.  
  997.     /* Resolves shader property IDs for one spotlight slot. */
  998.     void GetSpotShaderIDs(
  999.         int spotIndex,
  1000.         out int idWorldToShadow,
  1001.         out int idShadowRect,
  1002.         out int idPosRange,
  1003.         out int idDirCosOuter,
  1004.         out int idColorStrength,
  1005.         out int idShadowParams)
  1006.     {
  1007.         switch (spotIndex)
  1008.         {
  1009.             case 0:
  1010.                 idWorldToShadow = ID_CustomSpotWorldToShadow0;
  1011.                 idShadowRect = ID_CustomSpotShadowRect0;
  1012.                 idPosRange = ID_CustomSpotPosRange0;
  1013.                 idDirCosOuter = ID_CustomSpotDirCosOuter0;
  1014.                 idColorStrength = ID_CustomSpotColorStrength0;
  1015.                 idShadowParams = ID_CustomSpotShadowParams0;
  1016.                 break;
  1017.  
  1018.             case 1:
  1019.                 idWorldToShadow = ID_CustomSpotWorldToShadow1;
  1020.                 idShadowRect = ID_CustomSpotShadowRect1;
  1021.                 idPosRange = ID_CustomSpotPosRange1;
  1022.                 idDirCosOuter = ID_CustomSpotDirCosOuter1;
  1023.                 idColorStrength = ID_CustomSpotColorStrength1;
  1024.                 idShadowParams = ID_CustomSpotShadowParams1;
  1025.                 break;
  1026.  
  1027.             case 2:
  1028.                 idWorldToShadow = ID_CustomSpotWorldToShadow2;
  1029.                 idShadowRect = ID_CustomSpotShadowRect2;
  1030.                 idPosRange = ID_CustomSpotPosRange2;
  1031.                 idDirCosOuter = ID_CustomSpotDirCosOuter2;
  1032.                 idColorStrength = ID_CustomSpotColorStrength2;
  1033.                 idShadowParams = ID_CustomSpotShadowParams2;
  1034.                 break;
  1035.  
  1036.             default:
  1037.                 idWorldToShadow = ID_CustomSpotWorldToShadow3;
  1038.                 idShadowRect = ID_CustomSpotShadowRect3;
  1039.                 idPosRange = ID_CustomSpotPosRange3;
  1040.                 idDirCosOuter = ID_CustomSpotDirCosOuter3;
  1041.                 idColorStrength = ID_CustomSpotColorStrength3;
  1042.                 idShadowParams = ID_CustomSpotShadowParams3;
  1043.                 break;
  1044.         }
  1045.     }
  1046.  
  1047.     /* Builds spotlight projection and shadow matrices. */
  1048.     void BuildSpotMatrices(
  1049.         Light spotLight,
  1050.         out Matrix4x4 view,
  1051.         out Matrix4x4 gpuProj,
  1052.         out Matrix4x4 rawVP,
  1053.         out Matrix4x4 worldToShadowTex)
  1054.     {
  1055.         view = spotLight.transform.worldToLocalMatrix;
  1056.  
  1057.         Matrix4x4 proj = Matrix4x4.Perspective(
  1058.             spotLight.spotAngle,
  1059.             1.0f,
  1060.             0.05f,
  1061.             Mathf.Max(0.06f, spotLight.range)
  1062.         );
  1063.  
  1064.         gpuProj = GL.GetGPUProjectionMatrix(proj, true);
  1065.         rawVP = gpuProj * view;
  1066.         worldToShadowTex = GetShadowTexScaleBias() * rawVP;
  1067.     }
  1068.  
  1069.     /* Builds directional projection and shadow matrices. */
  1070.     void BuildDirectionalMatrices(
  1071.         ShadowAtlasLight dirLight,
  1072.         out Matrix4x4 view,
  1073.         out Matrix4x4 gpuProj,
  1074.         out Matrix4x4 rawVP,
  1075.         out Matrix4x4 worldToShadowTex)
  1076.     {
  1077.         Transform t = dirLight.transform;
  1078.  
  1079.         Vector3 center = playerTransform != null
  1080.             ? playerTransform.position + directionalCenterOffset
  1081.             : directionalCenterOffset;
  1082.  
  1083.         Vector3 forward = t.forward;
  1084.         Vector3 cameraPos = center - forward * (directionalFar * 0.5f);
  1085.  
  1086.         view = Matrix4x4.TRS(cameraPos, t.rotation, Vector3.one).inverse;
  1087.  
  1088.         Matrix4x4 proj = Matrix4x4.Ortho(
  1089.             -directionalOrthoSize,
  1090.              directionalOrthoSize,
  1091.             -directionalOrthoSize,
  1092.              directionalOrthoSize,
  1093.              directionalNear,
  1094.              directionalFar
  1095.         );
  1096.  
  1097.         gpuProj = GL.GetGPUProjectionMatrix(proj, true);
  1098.         rawVP = gpuProj * view;
  1099.         worldToShadowTex = GetShadowTexScaleBias() * rawVP;
  1100.     }
  1101.  
  1102.     /* Builds the 0..1 shadow texture remap matrix. */
  1103.     static Matrix4x4 GetShadowTexScaleBias()
  1104.     {
  1105.         Matrix4x4 m = Matrix4x4.identity;
  1106.         m.m00 = 0.5f; m.m03 = 0.5f;
  1107.         m.m11 = 0.5f; m.m13 = 0.5f;
  1108.         m.m22 = 0.5f; m.m23 = 0.5f;
  1109.         return m;
  1110.     }
  1111. }
Add Comment
Please, Sign In to add comment