Nessuna descrizione
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AdditionalLightsShadowAtlasLayout.cs 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Collections;
  4. namespace UnityEngine.Rendering.Universal
  5. {
  6. internal struct AdditionalLightsShadowAtlasLayout
  7. {
  8. internal struct ShadowResolutionRequest
  9. {
  10. public ushort visibleLightIndex;
  11. public ushort perLightShadowSliceIndex;
  12. public ushort requestedResolution;
  13. public ushort offsetX; // x coordinate of the square area allocated in the atlas for this shadow map
  14. public ushort offsetY; // y coordinate of the square area allocated in the atlas for this shadow map
  15. public ushort allocatedResolution; // width of the square area allocated in the atlas for this shadow map
  16. [Flags]
  17. private enum SettingsOptions : ushort
  18. {
  19. None = 0,
  20. SoftShadow = (1 << 0),
  21. PointLightShadow = (1 << 1),
  22. All = 0xFFFF
  23. }
  24. private SettingsOptions m_ShadowProperties;
  25. public bool softShadow
  26. {
  27. get => m_ShadowProperties.HasFlag(SettingsOptions.SoftShadow); // otherwise it's hard-shadow (no filtering)
  28. set
  29. {
  30. if (value)
  31. m_ShadowProperties |= SettingsOptions.SoftShadow;
  32. else
  33. m_ShadowProperties &= ~SettingsOptions.SoftShadow;
  34. }
  35. }
  36. public bool pointLightShadow
  37. {
  38. get => m_ShadowProperties.HasFlag(SettingsOptions.PointLightShadow); // otherwise it's spot light shadow (1 shadow slice instead of 6)
  39. set
  40. {
  41. if (value)
  42. m_ShadowProperties |= SettingsOptions.PointLightShadow;
  43. else
  44. m_ShadowProperties &= ~SettingsOptions.PointLightShadow;
  45. }
  46. }
  47. }
  48. // Static fields used to avoid GC allocs of intermediate computations
  49. static List<RectInt> s_UnusedAtlasSquareAreas; // This list tracks space available in the atlas
  50. static List<ShadowResolutionRequest> s_ShadowResolutionRequests; // intermediate array used to compute the final resolution of each shadow slice rendered in the frame
  51. static float[] s_VisibleLightIndexToCameraSquareDistance; // stores for each shadowed additional light its (squared) distance to camera ; used to sub-sort shadow requests according to how close their casting light is
  52. static Func<ShadowResolutionRequest, ShadowResolutionRequest, int> s_CompareShadowResolutionRequest;
  53. static ShadowResolutionRequest[] s_SortedShadowResolutionRequests;
  54. NativeArray<ShadowResolutionRequest> m_SortedShadowResolutionRequests;
  55. NativeArray<int> m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex; // for each visible light, store the index of its first shadow slice in m_SortedShadowResolutionRequests (for quicker access)
  56. int m_TotalShadowSlicesCount;
  57. int m_TotalShadowResolutionRequestCount;
  58. bool m_TooManyShadowMaps;
  59. int m_ShadowSlicesScaleFactor;
  60. int m_AtlasSize;
  61. public AdditionalLightsShadowAtlasLayout(UniversalLightData lightData, UniversalShadowData shadowData, UniversalCameraData cameraData)
  62. {
  63. bool useStructuredBuffer = RenderingUtils.useStructuredBuffer;
  64. NativeArray<VisibleLight> visibleLights = lightData.visibleLights;
  65. int numberOfVisibleLights = visibleLights.Length;
  66. if (s_UnusedAtlasSquareAreas == null)
  67. s_UnusedAtlasSquareAreas = new List<RectInt>();
  68. if (s_ShadowResolutionRequests == null)
  69. s_ShadowResolutionRequests = new List<ShadowResolutionRequest>();
  70. if (s_VisibleLightIndexToCameraSquareDistance == null || s_VisibleLightIndexToCameraSquareDistance.Length < numberOfVisibleLights)
  71. s_VisibleLightIndexToCameraSquareDistance = new float[numberOfVisibleLights];
  72. if (s_CompareShadowResolutionRequest == null)
  73. s_CompareShadowResolutionRequest = CreateCompareShadowResolutionRequesPredicate();
  74. if (!useStructuredBuffer)
  75. {
  76. int newCapacity = UniversalRenderPipeline.maxVisibleAdditionalLights;
  77. if (s_UnusedAtlasSquareAreas.Capacity < newCapacity)
  78. s_UnusedAtlasSquareAreas.Capacity = newCapacity;
  79. if (s_ShadowResolutionRequests.Count < numberOfVisibleLights)
  80. {
  81. s_ShadowResolutionRequests.Capacity = numberOfVisibleLights;
  82. int diff = numberOfVisibleLights - s_ShadowResolutionRequests.Count + 1;
  83. for (int i = 0; i < diff; i++)
  84. s_ShadowResolutionRequests.Add(new ShadowResolutionRequest());
  85. }
  86. }
  87. s_UnusedAtlasSquareAreas.Clear();
  88. ushort totalShadowResolutionRequestsCount = 0; // Number of shadow slices that we would need for all shadowed additional (punctual) lights in the scene. We might have to ignore some of those requests if they do not fit in the shadow atlas.
  89. for (int visibleLightIndex = 0; visibleLightIndex < visibleLights.Length; ++visibleLightIndex)
  90. {
  91. // Skip main directional light as it is not packed into the shadow atlas
  92. if (visibleLightIndex == lightData.mainLightIndex)
  93. {
  94. s_VisibleLightIndexToCameraSquareDistance[visibleLightIndex] = float.MaxValue;
  95. continue;
  96. }
  97. ref VisibleLight vl = ref visibleLights.UnsafeElementAt(visibleLightIndex);
  98. Light light = vl.light;
  99. LightType lightType = vl.lightType;
  100. LightShadows lightShadows = light.shadows;
  101. float shadowStrength = light.shadowStrength;
  102. if (!ShadowUtils.IsValidShadowCastingLight(lightData, visibleLightIndex, lightType, lightShadows, shadowStrength))
  103. {
  104. s_VisibleLightIndexToCameraSquareDistance[visibleLightIndex] = float.MaxValue;
  105. continue;
  106. }
  107. bool softShadows = (lightShadows == LightShadows.Soft);
  108. bool pointLightShadow = (lightType == LightType.Point);
  109. ushort visibleLightIndexUshort = (ushort)visibleLightIndex;
  110. ushort requestedResolution = (ushort)shadowData.resolution[visibleLightIndex];
  111. int shadowSlicesCountForThisLight = ShadowUtils.GetPunctualLightShadowSlicesCount(lightType);
  112. for (ushort perLightShadowSliceIndex = 0; perLightShadowSliceIndex < shadowSlicesCountForThisLight; ++perLightShadowSliceIndex)
  113. {
  114. if (totalShadowResolutionRequestsCount >= s_ShadowResolutionRequests.Count)
  115. s_ShadowResolutionRequests.Add(new ShadowResolutionRequest());
  116. ShadowResolutionRequest request = s_ShadowResolutionRequests[totalShadowResolutionRequestsCount];
  117. request.visibleLightIndex = visibleLightIndexUshort;
  118. request.perLightShadowSliceIndex = perLightShadowSliceIndex;
  119. request.requestedResolution = requestedResolution;
  120. request.softShadow = softShadows;
  121. request.pointLightShadow = pointLightShadow;
  122. s_ShadowResolutionRequests[totalShadowResolutionRequestsCount] = request;
  123. totalShadowResolutionRequestsCount++;
  124. }
  125. // mark this light as casting shadows
  126. s_VisibleLightIndexToCameraSquareDistance[visibleLightIndex] = (cameraData.worldSpaceCameraPos - light.transform.position).sqrMagnitude;
  127. }
  128. if (s_SortedShadowResolutionRequests == null || s_SortedShadowResolutionRequests.Length < totalShadowResolutionRequestsCount)
  129. s_SortedShadowResolutionRequests = new ShadowResolutionRequest[totalShadowResolutionRequestsCount];
  130. for (int i = 0; i < totalShadowResolutionRequestsCount; ++i)
  131. s_SortedShadowResolutionRequests[i] = s_ShadowResolutionRequests[i];
  132. using (new ProfilingScope(Sorting.s_QuickSortSampler))
  133. {
  134. Sorting.QuickSort(s_SortedShadowResolutionRequests, 0, totalShadowResolutionRequestsCount - 1, s_CompareShadowResolutionRequest);
  135. }
  136. m_SortedShadowResolutionRequests = new NativeArray<ShadowResolutionRequest>(s_SortedShadowResolutionRequests, Allocator.Temp);
  137. // To avoid visual artifacts when there is not enough place in the atlas, we remove shadow slices that would be allocated a too small resolution.
  138. // When not using structured buffers, m_AdditionalLightShadowSliceIndexTo_WorldShadowMatrix.Length maps to _AdditionalLightsWorldToShadow in Shadows.hlsl
  139. // In that case we have to limit its size because uniform buffers cannot be higher than 64kb for some platforms.
  140. int totalShadowSlicesCount = useStructuredBuffer ? totalShadowResolutionRequestsCount : Math.Min(totalShadowResolutionRequestsCount, UniversalRenderPipeline.maxVisibleAdditionalLights); // Number of shadow slices that we will actually be able to fit in the shadow atlas without causing visual artifacts.
  141. int atlasSize = shadowData.additionalLightsShadowmapWidth;
  142. // Find biggest end index in m_SortedShadowResolutionRequests array, under which all shadow requests can be allocated a big enough shadow atlas slot, to not cause rendering artifacts
  143. bool allShadowsAfterStartIndexHaveEnoughResolution = false;
  144. int estimatedScaleFactor = 1;
  145. while (!allShadowsAfterStartIndexHaveEnoughResolution && totalShadowSlicesCount > 0)
  146. {
  147. ShadowResolutionRequest request = m_SortedShadowResolutionRequests[totalShadowSlicesCount - 1];
  148. estimatedScaleFactor = EstimateScaleFactorNeededToFitAllShadowsInAtlas(m_SortedShadowResolutionRequests, totalShadowSlicesCount, atlasSize);
  149. // check if resolution of the least priority shadow slice request would be acceptable
  150. if (request.requestedResolution >= estimatedScaleFactor * ShadowUtils.MinimalPunctualLightShadowResolution(request.softShadow))
  151. allShadowsAfterStartIndexHaveEnoughResolution = true;
  152. else // Skip shadow requests for this light ; their resolution is too small to look any good
  153. totalShadowSlicesCount -= ShadowUtils.GetPunctualLightShadowSlicesCount(request.pointLightShadow ? LightType.Point : LightType.Spot);
  154. }
  155. for (int sortedArrayIndex = totalShadowSlicesCount; sortedArrayIndex < m_SortedShadowResolutionRequests.Length; ++sortedArrayIndex)
  156. m_SortedShadowResolutionRequests[sortedArrayIndex] = default; // Reset entries that we cannot fit in the atlas
  157. m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex = new NativeArray<int>(visibleLights.Length, Allocator.Temp);
  158. // Reset the reverse lookup array
  159. for (int visibleLightIndex = 0; visibleLightIndex < m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex.Length; ++visibleLightIndex)
  160. m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex[visibleLightIndex] = -1;
  161. // Update the reverse lookup array (starting from the end of the array, in order to use index of slice#0 in case a same visibleLight has several shadowSlices)
  162. for (int sortedArrayIndex = totalShadowSlicesCount - 1; sortedArrayIndex >= 0; --sortedArrayIndex)
  163. {
  164. int visibleLightIndex = s_SortedShadowResolutionRequests[sortedArrayIndex].visibleLightIndex;
  165. m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex[visibleLightIndex] = sortedArrayIndex;
  166. }
  167. // Assigns to each of the first totalShadowSlicesCount items in m_SortedShadowResolutionRequests a location in the shadow atlas based on requested resolutions.
  168. // If necessary, scales down shadow maps active in the frame, to make all of them fit in the atlas.
  169. bool allShadowSlicesFitInAtlas = false;
  170. bool tooManyShadows = false;
  171. int shadowSlicesScaleFactor = estimatedScaleFactor;
  172. while (!allShadowSlicesFitInAtlas && !tooManyShadows)
  173. {
  174. s_UnusedAtlasSquareAreas.Clear();
  175. s_UnusedAtlasSquareAreas.Add(new RectInt(0, 0, atlasSize, atlasSize));
  176. allShadowSlicesFitInAtlas = true;
  177. for (int shadowRequestIndex = 0; shadowRequestIndex < totalShadowSlicesCount; ++shadowRequestIndex)
  178. {
  179. var resolution = m_SortedShadowResolutionRequests[shadowRequestIndex].requestedResolution / shadowSlicesScaleFactor;
  180. if (resolution < ShadowUtils.MinimalPunctualLightShadowResolution(m_SortedShadowResolutionRequests[shadowRequestIndex].softShadow))
  181. {
  182. tooManyShadows = true;
  183. break;
  184. }
  185. bool foundSpaceInAtlas = false;
  186. // Try to find free space in the atlas
  187. for (int unusedAtlasSquareAreaIndex = 0; unusedAtlasSquareAreaIndex < s_UnusedAtlasSquareAreas.Count; ++unusedAtlasSquareAreaIndex)
  188. {
  189. RectInt atlasArea = s_UnusedAtlasSquareAreas[unusedAtlasSquareAreaIndex];
  190. int atlasAreaWidth = atlasArea.width;
  191. if (atlasAreaWidth < resolution)
  192. continue;
  193. int atlasAreaHeight = atlasArea.height;
  194. int atlasAreaX = atlasArea.x;
  195. int atlasAreaY = atlasArea.y;
  196. // we can use this atlas area for the shadow request
  197. ref ShadowResolutionRequest shadowRequest = ref m_SortedShadowResolutionRequests.UnsafeElementAtMutable(shadowRequestIndex);
  198. shadowRequest.offsetX = (ushort)atlasAreaX;
  199. shadowRequest.offsetY = (ushort)atlasAreaY;
  200. shadowRequest.allocatedResolution = (ushort) resolution;
  201. // this atlas space is not available anymore, so remove it from the list
  202. s_UnusedAtlasSquareAreas.RemoveAt(unusedAtlasSquareAreaIndex);
  203. // make sure to split space so that the rest of this square area can be used
  204. int remainingShadowRequestsCount = totalShadowSlicesCount - shadowRequestIndex - 1; // (no need to add more than that)
  205. int newSquareAreasCount = 0;
  206. int newSquareAreaWidth = resolution; // we split the area in squares of same size
  207. int newSquareAreaHeight = resolution;
  208. int newSquareAreaX = atlasAreaX;
  209. int newSquareAreaY = atlasAreaY;
  210. while (newSquareAreasCount < remainingShadowRequestsCount)
  211. {
  212. newSquareAreaX += newSquareAreaWidth;
  213. if (newSquareAreaX + newSquareAreaWidth > (atlasAreaX + atlasAreaWidth))
  214. {
  215. newSquareAreaX = atlasAreaX;
  216. newSquareAreaY += newSquareAreaHeight;
  217. if (newSquareAreaY + newSquareAreaHeight > (atlasAreaY + atlasAreaHeight))
  218. break;
  219. }
  220. // replace the space we removed previously by new smaller squares (inserting them in this order ensures shadow maps will be packed at the side of the atlas, without gaps)
  221. s_UnusedAtlasSquareAreas.Insert(unusedAtlasSquareAreaIndex + newSquareAreasCount, new RectInt(newSquareAreaX, newSquareAreaY, newSquareAreaWidth, newSquareAreaHeight));
  222. ++newSquareAreasCount;
  223. }
  224. foundSpaceInAtlas = true;
  225. break;
  226. }
  227. if (!foundSpaceInAtlas)
  228. {
  229. allShadowSlicesFitInAtlas = false;
  230. break;
  231. }
  232. }
  233. if (!allShadowSlicesFitInAtlas && !tooManyShadows)
  234. shadowSlicesScaleFactor *= 2;
  235. }
  236. m_TooManyShadowMaps = tooManyShadows;
  237. m_ShadowSlicesScaleFactor = shadowSlicesScaleFactor;
  238. m_TotalShadowSlicesCount = totalShadowSlicesCount;
  239. m_TotalShadowResolutionRequestCount = totalShadowResolutionRequestsCount;
  240. m_AtlasSize = atlasSize;
  241. }
  242. public int GetTotalShadowSlicesCount() => m_TotalShadowSlicesCount;
  243. public int GetTotalShadowResolutionRequestCount() => m_TotalShadowResolutionRequestCount;
  244. public bool HasTooManyShadowMaps() => m_TooManyShadowMaps;
  245. public int GetShadowSlicesScaleFactor() => m_ShadowSlicesScaleFactor;
  246. public int GetAtlasSize() => m_AtlasSize;
  247. public bool HasSpaceForLight(int originalVisibleLightIndex)
  248. {
  249. return m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex[originalVisibleLightIndex] != -1;
  250. }
  251. public ShadowResolutionRequest GetSortedShadowResolutionRequest(int sortedShadowResolutionRequestIndex)
  252. {
  253. return m_SortedShadowResolutionRequests[sortedShadowResolutionRequestIndex];
  254. }
  255. public ShadowResolutionRequest GetSliceShadowResolutionRequest(int originalVisibleLightIndex, int sliceIndex)
  256. {
  257. int sortedShadowResolutionRequestIndex = m_VisibleLightIndexToSortedShadowResolutionRequestsFirstSliceIndex[originalVisibleLightIndex];
  258. return m_SortedShadowResolutionRequests[sortedShadowResolutionRequestIndex + sliceIndex];
  259. }
  260. public static void ClearStaticCaches()
  261. {
  262. s_UnusedAtlasSquareAreas = null;
  263. s_ShadowResolutionRequests = null;
  264. s_VisibleLightIndexToCameraSquareDistance = null;
  265. s_CompareShadowResolutionRequest = null;
  266. s_SortedShadowResolutionRequests = null;
  267. }
  268. static int EstimateScaleFactorNeededToFitAllShadowsInAtlas(in NativeArray<ShadowResolutionRequest> shadowResolutionRequests, int endIndex, int atlasSize)
  269. {
  270. long totalTexelsInShadowAtlas = atlasSize * atlasSize;
  271. long totalTexelsInShadowRequests = 0;
  272. for (int shadowRequestIndex = 0; shadowRequestIndex < endIndex; ++shadowRequestIndex)
  273. totalTexelsInShadowRequests += shadowResolutionRequests[shadowRequestIndex].requestedResolution * shadowResolutionRequests[shadowRequestIndex].requestedResolution;
  274. int estimatedScaleFactor = 1;
  275. while (totalTexelsInShadowRequests > totalTexelsInShadowAtlas * estimatedScaleFactor * estimatedScaleFactor)
  276. estimatedScaleFactor *= 2;
  277. return estimatedScaleFactor;
  278. }
  279. // Sort array in decreasing requestedResolution order,
  280. // sub-sorting in "HardShadow > SoftShadow",
  281. // i.e place last requests that will be removed in priority to make room for the others,
  282. // because their resolution is too small to produce good-looking shadows ; or because they take relatively more space in the atlas )
  283. // sub-sub-sorting in light distance to camera
  284. // then grouping in increasing visibleIndex (and sub-sorting each group in ShadowSliceIndex order)
  285. static Func<ShadowResolutionRequest, ShadowResolutionRequest, int> CreateCompareShadowResolutionRequesPredicate()
  286. {
  287. return (ShadowResolutionRequest curr, ShadowResolutionRequest other) =>
  288. {
  289. return (((curr.requestedResolution > other.requestedResolution)
  290. || (curr.requestedResolution == other.requestedResolution && !curr.softShadow && other.softShadow)
  291. || (curr.requestedResolution == other.requestedResolution && curr.softShadow == other.softShadow && s_VisibleLightIndexToCameraSquareDistance[curr.visibleLightIndex] < s_VisibleLightIndexToCameraSquareDistance[other.visibleLightIndex])
  292. || (curr.requestedResolution == other.requestedResolution && curr.softShadow == other.softShadow && s_VisibleLightIndexToCameraSquareDistance[curr.visibleLightIndex] == s_VisibleLightIndexToCameraSquareDistance[other.visibleLightIndex] && curr.visibleLightIndex < other.visibleLightIndex)
  293. || (curr.requestedResolution == other.requestedResolution && curr.softShadow == other.softShadow && s_VisibleLightIndexToCameraSquareDistance[curr.visibleLightIndex] == s_VisibleLightIndexToCameraSquareDistance[other.visibleLightIndex] && curr.visibleLightIndex == other.visibleLightIndex && curr.perLightShadowSliceIndex < other.perLightShadowSliceIndex)))
  294. ? -1 : 1;
  295. };
  296. }
  297. }
  298. }