AdditionalLightsShadowCasterPass.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Collections;
  4. namespace UnityEngine.Rendering.Universal.Internal
  5. {
  6. /// <summary>
  7. /// Renders a shadow map atlas for additional shadow-casting Lights.
  8. /// </summary>
  9. public class AdditionalLightsShadowCasterPass : ScriptableRenderPass
  10. {
  11. private static class AdditionalShadowsConstantBuffer
  12. {
  13. public static int _AdditionalLightsWorldToShadow;
  14. public static int _AdditionalShadowParams;
  15. public static int _AdditionalShadowOffset0;
  16. public static int _AdditionalShadowOffset1;
  17. public static int _AdditionalShadowOffset2;
  18. public static int _AdditionalShadowOffset3;
  19. public static int _AdditionalShadowmapSize;
  20. }
  21. public static int m_AdditionalShadowsBufferId;
  22. public static int m_AdditionalShadowsIndicesId;
  23. bool m_UseStructuredBuffer;
  24. const int k_ShadowmapBufferBits = 16;
  25. private RenderTargetHandle m_AdditionalLightsShadowmap;
  26. RenderTexture m_AdditionalLightsShadowmapTexture;
  27. int m_ShadowmapWidth;
  28. int m_ShadowmapHeight;
  29. ShadowSliceData[] m_AdditionalLightSlices = null;
  30. // Shader data for UBO path
  31. Matrix4x4[] m_AdditionalLightsWorldToShadow = null;
  32. Vector4[] m_AdditionalLightsShadowParams = null;
  33. // Shader data for SSBO
  34. ShaderInput.ShadowData[] m_AdditionalLightsShadowData = null;
  35. List<int> m_AdditionalShadowCastingLightIndices = new List<int>();
  36. List<int> m_AdditionalShadowCastingLightIndicesMap = new List<int>();
  37. bool m_SupportsBoxFilterForShadows;
  38. const string m_ProfilerTag = "Render Additional Shadows";
  39. ProfilingSampler m_ProfilingSampler = new ProfilingSampler(m_ProfilerTag);
  40. public AdditionalLightsShadowCasterPass(RenderPassEvent evt)
  41. {
  42. renderPassEvent = evt;
  43. AdditionalShadowsConstantBuffer._AdditionalLightsWorldToShadow = Shader.PropertyToID("_AdditionalLightsWorldToShadow");
  44. AdditionalShadowsConstantBuffer._AdditionalShadowParams = Shader.PropertyToID("_AdditionalShadowParams");
  45. AdditionalShadowsConstantBuffer._AdditionalShadowOffset0 = Shader.PropertyToID("_AdditionalShadowOffset0");
  46. AdditionalShadowsConstantBuffer._AdditionalShadowOffset1 = Shader.PropertyToID("_AdditionalShadowOffset1");
  47. AdditionalShadowsConstantBuffer._AdditionalShadowOffset2 = Shader.PropertyToID("_AdditionalShadowOffset2");
  48. AdditionalShadowsConstantBuffer._AdditionalShadowOffset3 = Shader.PropertyToID("_AdditionalShadowOffset3");
  49. AdditionalShadowsConstantBuffer._AdditionalShadowmapSize = Shader.PropertyToID("_AdditionalShadowmapSize");
  50. m_AdditionalLightsShadowmap.Init("_AdditionalLightsShadowmapTexture");
  51. m_AdditionalShadowsBufferId = Shader.PropertyToID("_AdditionalShadowsBuffer");
  52. m_AdditionalShadowsIndicesId = Shader.PropertyToID("_AdditionalShadowsIndices");
  53. m_UseStructuredBuffer = RenderingUtils.useStructuredBuffer;
  54. m_SupportsBoxFilterForShadows = Application.isMobilePlatform || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Switch;
  55. if (!m_UseStructuredBuffer)
  56. {
  57. // Preallocated a fixed size. CommandBuffer.SetGlobal* does allow this data to grow.
  58. int maxLights = UniversalRenderPipeline.maxVisibleAdditionalLights;
  59. m_AdditionalLightsWorldToShadow = new Matrix4x4[maxLights];
  60. m_AdditionalLightsShadowParams = new Vector4[maxLights];
  61. }
  62. }
  63. public bool Setup(ref RenderingData renderingData)
  64. {
  65. Clear();
  66. m_ShadowmapWidth = renderingData.shadowData.additionalLightsShadowmapWidth;
  67. m_ShadowmapHeight = renderingData.shadowData.additionalLightsShadowmapHeight;
  68. var visibleLights = renderingData.lightData.visibleLights;
  69. int additionalLightsCount = renderingData.lightData.additionalLightsCount;
  70. if (m_AdditionalLightSlices == null || m_AdditionalLightSlices.Length < additionalLightsCount)
  71. m_AdditionalLightSlices = new ShadowSliceData[additionalLightsCount];
  72. if (m_AdditionalLightsShadowData == null || m_AdditionalLightsShadowData.Length < additionalLightsCount)
  73. m_AdditionalLightsShadowData = new ShaderInput.ShadowData[additionalLightsCount];
  74. int validShadowCastingLights = 0;
  75. bool supportsSoftShadows = renderingData.shadowData.supportsSoftShadows;
  76. for (int i = 0; i < visibleLights.Length && m_AdditionalShadowCastingLightIndices.Count < additionalLightsCount; ++i)
  77. {
  78. VisibleLight shadowLight = visibleLights[i];
  79. // Skip main directional light as it is not packed into the shadow atlas
  80. if (i == renderingData.lightData.mainLightIndex)
  81. continue;
  82. int shadowCastingLightIndex = m_AdditionalShadowCastingLightIndices.Count;
  83. bool isValidShadowSlice = false;
  84. if (renderingData.cullResults.GetShadowCasterBounds(i, out var bounds))
  85. {
  86. // We need to iterate the lights even though additional lights are disabled because
  87. // cullResults.GetShadowCasterBounds() does the fence sync for the shadow culling jobs.
  88. if (!renderingData.shadowData.supportsAdditionalLightShadows)
  89. {
  90. continue;
  91. }
  92. if (IsValidShadowCastingLight(ref renderingData.lightData, i))
  93. {
  94. bool success = ShadowUtils.ExtractSpotLightMatrix(ref renderingData.cullResults,
  95. ref renderingData.shadowData,
  96. i,
  97. out var shadowTransform,
  98. out m_AdditionalLightSlices[shadowCastingLightIndex].viewMatrix,
  99. out m_AdditionalLightSlices[shadowCastingLightIndex].projectionMatrix);
  100. if (success)
  101. {
  102. m_AdditionalShadowCastingLightIndices.Add(i);
  103. var light = shadowLight.light;
  104. float shadowStrength = light.shadowStrength;
  105. float softShadows = (supportsSoftShadows && light.shadows == LightShadows.Soft) ? 1.0f : 0.0f;
  106. Vector4 shadowParams = new Vector4(shadowStrength, softShadows, 0.0f, 0.0f);
  107. if (m_UseStructuredBuffer)
  108. {
  109. m_AdditionalLightsShadowData[shadowCastingLightIndex].worldToShadowMatrix = shadowTransform;
  110. m_AdditionalLightsShadowData[shadowCastingLightIndex].shadowParams = shadowParams;
  111. }
  112. else
  113. {
  114. m_AdditionalLightsWorldToShadow[shadowCastingLightIndex] = shadowTransform;
  115. m_AdditionalLightsShadowParams[shadowCastingLightIndex] = shadowParams;
  116. }
  117. isValidShadowSlice = true;
  118. validShadowCastingLights++;
  119. }
  120. }
  121. }
  122. if (m_UseStructuredBuffer)
  123. {
  124. // When using StructuredBuffers all the valid shadow casting slices data
  125. // are stored in a the ShadowData buffer and then we setup a index map to
  126. // map from light indices to shadow buffer index. A index map of -1 means
  127. // the light is not a valid shadow casting light and there's no data for it
  128. // in the shadow buffer.
  129. int indexMap = (isValidShadowSlice) ? shadowCastingLightIndex : -1;
  130. m_AdditionalShadowCastingLightIndicesMap.Add(indexMap);
  131. }
  132. else if (!isValidShadowSlice)
  133. {
  134. // When NOT using structured buffers we have no performant way to sample the
  135. // index map as int[]. Unity shader compiler converts int[] to float4[] to force memory alignment.
  136. // This makes indexing int[] arrays very slow. So, in order to avoid indexing shadow lights we
  137. // setup slice data and reserve shadow map space even for invalid shadow slices.
  138. // The data is setup with zero shadow strength. This has the same visual effect of no shadow
  139. // attenuation contribution from this light.
  140. // This makes sampling shadow faster but introduces waste in shadow map atlas.
  141. // The waste increases with the amount of additional lights to shade.
  142. // Therefore Universal RP try to keep the limit at sane levels when using uniform buffers.
  143. Matrix4x4 identity = Matrix4x4.identity;
  144. m_AdditionalShadowCastingLightIndices.Add(i);
  145. m_AdditionalLightsWorldToShadow[shadowCastingLightIndex] = identity;
  146. m_AdditionalLightsShadowParams[shadowCastingLightIndex] = Vector4.zero;
  147. m_AdditionalLightSlices[shadowCastingLightIndex].viewMatrix = identity;
  148. m_AdditionalLightSlices[shadowCastingLightIndex].projectionMatrix = identity;
  149. }
  150. }
  151. // Lights that need to be rendered in the shadow map atlas
  152. if (validShadowCastingLights == 0)
  153. return false;
  154. int atlasWidth = renderingData.shadowData.additionalLightsShadowmapWidth;
  155. int atlasHeight = renderingData.shadowData.additionalLightsShadowmapHeight;
  156. int sliceResolution = ShadowUtils.GetMaxTileResolutionInAtlas(atlasWidth, atlasHeight, validShadowCastingLights);
  157. // In the UI we only allow for square shadow map atlas. Here we check if we can fit
  158. // all shadow slices into half resolution of the atlas and adjust height to have tighter packing.
  159. int maximumSlices = (m_ShadowmapWidth / sliceResolution) * (m_ShadowmapHeight / sliceResolution);
  160. if (validShadowCastingLights <= (maximumSlices / 2))
  161. m_ShadowmapHeight /= 2;
  162. int shadowSlicesPerRow = (atlasWidth / sliceResolution);
  163. float oneOverAtlasWidth = 1.0f / m_ShadowmapWidth;
  164. float oneOverAtlasHeight = 1.0f / m_ShadowmapHeight;
  165. int sliceIndex = 0;
  166. int shadowCastingLightsBufferCount = m_AdditionalShadowCastingLightIndices.Count;
  167. Matrix4x4 sliceTransform = Matrix4x4.identity;
  168. sliceTransform.m00 = sliceResolution * oneOverAtlasWidth;
  169. sliceTransform.m11 = sliceResolution * oneOverAtlasHeight;
  170. for (int i = 0; i < shadowCastingLightsBufferCount; ++i)
  171. {
  172. // we can skip the slice if strength is zero. Some slices with zero
  173. // strength exists when using uniform array path.
  174. if (!m_UseStructuredBuffer && Mathf.Approximately(m_AdditionalLightsShadowParams[i].x, 0.0f))
  175. continue;
  176. m_AdditionalLightSlices[i].offsetX = (sliceIndex % shadowSlicesPerRow) * sliceResolution;
  177. m_AdditionalLightSlices[i].offsetY = (sliceIndex / shadowSlicesPerRow) * sliceResolution;
  178. m_AdditionalLightSlices[i].resolution = sliceResolution;
  179. sliceTransform.m03 = m_AdditionalLightSlices[i].offsetX * oneOverAtlasWidth;
  180. sliceTransform.m13 = m_AdditionalLightSlices[i].offsetY * oneOverAtlasHeight;
  181. // We bake scale and bias to each shadow map in the atlas in the matrix.
  182. // saves some instructions in shader.
  183. if (m_UseStructuredBuffer)
  184. m_AdditionalLightsShadowData[i].worldToShadowMatrix = sliceTransform * m_AdditionalLightsShadowData[i].worldToShadowMatrix;
  185. else
  186. m_AdditionalLightsWorldToShadow[i] = sliceTransform * m_AdditionalLightsWorldToShadow[i];
  187. sliceIndex++;
  188. }
  189. return true;
  190. }
  191. public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
  192. {
  193. m_AdditionalLightsShadowmapTexture = ShadowUtils.GetTemporaryShadowTexture(m_ShadowmapWidth, m_ShadowmapHeight, k_ShadowmapBufferBits);
  194. ConfigureTarget(new RenderTargetIdentifier(m_AdditionalLightsShadowmapTexture));
  195. ConfigureClear(ClearFlag.All, Color.black);
  196. }
  197. /// <inheritdoc/>
  198. public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
  199. {
  200. if (renderingData.shadowData.supportsAdditionalLightShadows)
  201. RenderAdditionalShadowmapAtlas(ref context, ref renderingData.cullResults, ref renderingData.lightData, ref renderingData.shadowData);
  202. }
  203. public override void FrameCleanup(CommandBuffer cmd)
  204. {
  205. if (cmd == null)
  206. throw new ArgumentNullException("cmd");
  207. if (m_AdditionalLightsShadowmapTexture)
  208. {
  209. RenderTexture.ReleaseTemporary(m_AdditionalLightsShadowmapTexture);
  210. m_AdditionalLightsShadowmapTexture = null;
  211. }
  212. }
  213. void Clear()
  214. {
  215. m_AdditionalShadowCastingLightIndices.Clear();
  216. m_AdditionalShadowCastingLightIndicesMap.Clear();
  217. m_AdditionalLightsShadowmapTexture = null;
  218. }
  219. void RenderAdditionalShadowmapAtlas(ref ScriptableRenderContext context, ref CullingResults cullResults, ref LightData lightData, ref ShadowData shadowData)
  220. {
  221. NativeArray<VisibleLight> visibleLights = lightData.visibleLights;
  222. bool additionalLightHasSoftShadows = false;
  223. CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
  224. using (new ProfilingScope(cmd, m_ProfilingSampler))
  225. {
  226. bool anyShadowSliceRenderer = false;
  227. int shadowSlicesCount = m_AdditionalShadowCastingLightIndices.Count;
  228. for (int i = 0; i < shadowSlicesCount; ++i)
  229. {
  230. // we do the shadow strength check here again here because when using
  231. // the uniform array path we might have zero strength shadow lights.
  232. // In that case we need the shadow data buffer but we can skip
  233. // rendering them to shadowmap.
  234. if (!m_UseStructuredBuffer && Mathf.Approximately(m_AdditionalLightsShadowParams[i].x, 0.0f))
  235. continue;
  236. // Index of the VisibleLight
  237. int shadowLightIndex = m_AdditionalShadowCastingLightIndices[i];
  238. VisibleLight shadowLight = visibleLights[shadowLightIndex];
  239. ShadowSliceData shadowSliceData = m_AdditionalLightSlices[i];
  240. var settings = new ShadowDrawingSettings(cullResults, shadowLightIndex);
  241. Vector4 shadowBias = ShadowUtils.GetShadowBias(ref shadowLight, shadowLightIndex,
  242. ref shadowData, shadowSliceData.projectionMatrix, shadowSliceData.resolution);
  243. ShadowUtils.SetupShadowCasterConstantBuffer(cmd, ref shadowLight, shadowBias);
  244. ShadowUtils.RenderShadowSlice(cmd, ref context, ref shadowSliceData, ref settings);
  245. additionalLightHasSoftShadows |= shadowLight.light.shadows == LightShadows.Soft;
  246. anyShadowSliceRenderer = true;
  247. }
  248. // We share soft shadow settings for main light and additional lights to save keywords.
  249. // So we check here if pipeline supports soft shadows and either main light or any additional light has soft shadows
  250. // to enable the keyword.
  251. // TODO: In PC and Consoles we can upload shadow data per light and branch on shader. That will be more likely way faster.
  252. bool mainLightHasSoftShadows = shadowData.supportsMainLightShadows &&
  253. lightData.mainLightIndex != -1 &&
  254. visibleLights[lightData.mainLightIndex].light.shadows ==
  255. LightShadows.Soft;
  256. bool softShadows = shadowData.supportsSoftShadows &&
  257. (mainLightHasSoftShadows || additionalLightHasSoftShadows);
  258. CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightShadows, anyShadowSliceRenderer);
  259. CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.SoftShadows, softShadows);
  260. if (anyShadowSliceRenderer)
  261. SetupAdditionalLightsShadowReceiverConstants(cmd, ref shadowData, softShadows);
  262. }
  263. context.ExecuteCommandBuffer(cmd);
  264. CommandBufferPool.Release(cmd);
  265. }
  266. void SetupAdditionalLightsShadowReceiverConstants(CommandBuffer cmd, ref ShadowData shadowData, bool softShadows)
  267. {
  268. int shadowLightsCount = m_AdditionalShadowCastingLightIndices.Count;
  269. float invShadowAtlasWidth = 1.0f / shadowData.additionalLightsShadowmapWidth;
  270. float invShadowAtlasHeight = 1.0f / shadowData.additionalLightsShadowmapHeight;
  271. float invHalfShadowAtlasWidth = 0.5f * invShadowAtlasWidth;
  272. float invHalfShadowAtlasHeight = 0.5f * invShadowAtlasHeight;
  273. cmd.SetGlobalTexture(m_AdditionalLightsShadowmap.id, m_AdditionalLightsShadowmapTexture);
  274. if (m_UseStructuredBuffer)
  275. {
  276. NativeArray<ShaderInput.ShadowData> shadowBufferData = new NativeArray<ShaderInput.ShadowData>(shadowLightsCount, Allocator.Temp);
  277. for (int i = 0; i < shadowLightsCount; ++i)
  278. {
  279. ShaderInput.ShadowData data;
  280. data.worldToShadowMatrix = m_AdditionalLightsShadowData[i].worldToShadowMatrix;
  281. data.shadowParams = m_AdditionalLightsShadowData[i].shadowParams;
  282. shadowBufferData[i] = data;
  283. }
  284. var shadowBuffer = ShaderData.instance.GetShadowDataBuffer(shadowLightsCount);
  285. shadowBuffer.SetData(shadowBufferData);
  286. var shadowIndicesMapBuffer = ShaderData.instance.GetShadowIndicesBuffer(m_AdditionalShadowCastingLightIndicesMap.Count);
  287. shadowIndicesMapBuffer.SetData(m_AdditionalShadowCastingLightIndicesMap, 0, 0,
  288. m_AdditionalShadowCastingLightIndicesMap.Count);
  289. cmd.SetGlobalBuffer(m_AdditionalShadowsBufferId, shadowBuffer);
  290. cmd.SetGlobalBuffer(m_AdditionalShadowsIndicesId, shadowIndicesMapBuffer);
  291. shadowBufferData.Dispose();
  292. }
  293. else
  294. {
  295. cmd.SetGlobalMatrixArray(AdditionalShadowsConstantBuffer._AdditionalLightsWorldToShadow, m_AdditionalLightsWorldToShadow);
  296. cmd.SetGlobalVectorArray(AdditionalShadowsConstantBuffer._AdditionalShadowParams, m_AdditionalLightsShadowParams);
  297. }
  298. if (softShadows)
  299. {
  300. if (m_SupportsBoxFilterForShadows)
  301. {
  302. cmd.SetGlobalVector(AdditionalShadowsConstantBuffer._AdditionalShadowOffset0,
  303. new Vector4(-invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight, 0.0f, 0.0f));
  304. cmd.SetGlobalVector(AdditionalShadowsConstantBuffer._AdditionalShadowOffset1,
  305. new Vector4(invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight, 0.0f, 0.0f));
  306. cmd.SetGlobalVector(AdditionalShadowsConstantBuffer._AdditionalShadowOffset2,
  307. new Vector4(-invHalfShadowAtlasWidth, invHalfShadowAtlasHeight, 0.0f, 0.0f));
  308. cmd.SetGlobalVector(AdditionalShadowsConstantBuffer._AdditionalShadowOffset3,
  309. new Vector4(invHalfShadowAtlasWidth, invHalfShadowAtlasHeight, 0.0f, 0.0f));
  310. }
  311. // Currently only used when !SHADER_API_MOBILE but risky to not set them as it's generic
  312. // enough so custom shaders might use it.
  313. cmd.SetGlobalVector(AdditionalShadowsConstantBuffer._AdditionalShadowmapSize, new Vector4(invShadowAtlasWidth, invShadowAtlasHeight,
  314. shadowData.additionalLightsShadowmapWidth, shadowData.additionalLightsShadowmapHeight));
  315. }
  316. }
  317. bool IsValidShadowCastingLight(ref LightData lightData, int i)
  318. {
  319. if (i == lightData.mainLightIndex)
  320. return false;
  321. VisibleLight shadowLight = lightData.visibleLights[i];
  322. // Directional and Point light shadows are not supported in the shadow map atlas
  323. if (shadowLight.lightType == LightType.Point || shadowLight.lightType == LightType.Directional)
  324. return false;
  325. Light light = shadowLight.light;
  326. return light != null && light.shadows != LightShadows.None && !Mathf.Approximately(light.shadowStrength, 0.0f);
  327. }
  328. }
  329. }