ForwardLights.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. using UnityEngine.Experimental.GlobalIllumination;
  2. using Unity.Collections;
  3. namespace UnityEngine.Rendering.Universal.Internal
  4. {
  5. /// <summary>
  6. /// Computes and submits lighting data to the GPU.
  7. /// </summary>
  8. public class ForwardLights
  9. {
  10. static class LightConstantBuffer
  11. {
  12. public static int _MainLightPosition;
  13. public static int _MainLightColor;
  14. public static int _AdditionalLightsCount;
  15. public static int _AdditionalLightsPosition;
  16. public static int _AdditionalLightsColor;
  17. public static int _AdditionalLightsAttenuation;
  18. public static int _AdditionalLightsSpotDir;
  19. public static int _AdditionalLightOcclusionProbeChannel;
  20. }
  21. int m_AdditionalLightsBufferId;
  22. int m_AdditionalLightsIndicesId;
  23. const string k_SetupLightConstants = "Setup Light Constants";
  24. MixedLightingSetup m_MixedLightingSetup;
  25. // Holds light direction for directional lights or position for punctual lights.
  26. // When w is set to 1.0, it means it's a punctual light.
  27. Vector4 k_DefaultLightPosition = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
  28. Vector4 k_DefaultLightColor = Color.black;
  29. // Default light attenuation is setup in a particular way that it causes
  30. // directional lights to return 1.0 for both distance and angle attenuation
  31. Vector4 k_DefaultLightAttenuation = new Vector4(0.0f, 1.0f, 0.0f, 1.0f);
  32. Vector4 k_DefaultLightSpotDirection = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
  33. Vector4 k_DefaultLightsProbeChannel = new Vector4(-1.0f, 1.0f, -1.0f, -1.0f);
  34. Vector4[] m_AdditionalLightPositions;
  35. Vector4[] m_AdditionalLightColors;
  36. Vector4[] m_AdditionalLightAttenuations;
  37. Vector4[] m_AdditionalLightSpotDirections;
  38. Vector4[] m_AdditionalLightOcclusionProbeChannels;
  39. bool m_UseStructuredBuffer;
  40. public ForwardLights()
  41. {
  42. m_UseStructuredBuffer = RenderingUtils.useStructuredBuffer;
  43. LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition");
  44. LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor");
  45. LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount");
  46. if (m_UseStructuredBuffer)
  47. {
  48. m_AdditionalLightsBufferId = Shader.PropertyToID("_AdditionalLightsBuffer");
  49. m_AdditionalLightsIndicesId = Shader.PropertyToID("_AdditionalLightsIndices");
  50. }
  51. else
  52. {
  53. LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition");
  54. LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor");
  55. LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation");
  56. LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir");
  57. LightConstantBuffer._AdditionalLightOcclusionProbeChannel = Shader.PropertyToID("_AdditionalLightsOcclusionProbes");
  58. int maxLights = UniversalRenderPipeline.maxVisibleAdditionalLights;
  59. m_AdditionalLightPositions = new Vector4[maxLights];
  60. m_AdditionalLightColors = new Vector4[maxLights];
  61. m_AdditionalLightAttenuations = new Vector4[maxLights];
  62. m_AdditionalLightSpotDirections = new Vector4[maxLights];
  63. m_AdditionalLightOcclusionProbeChannels = new Vector4[maxLights];
  64. }
  65. }
  66. public void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
  67. {
  68. int additionalLightsCount = renderingData.lightData.additionalLightsCount;
  69. bool additionalLightsPerVertex = renderingData.lightData.shadeAdditionalLightsPerVertex;
  70. CommandBuffer cmd = CommandBufferPool.Get(k_SetupLightConstants);
  71. SetupShaderLightConstants(cmd, ref renderingData);
  72. CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex,
  73. additionalLightsCount > 0 && additionalLightsPerVertex);
  74. CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel,
  75. additionalLightsCount > 0 && !additionalLightsPerVertex);
  76. CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MixedLightingSubtractive,
  77. renderingData.lightData.supportsMixedLighting &&
  78. m_MixedLightingSetup == MixedLightingSetup.Subtractive);
  79. context.ExecuteCommandBuffer(cmd);
  80. CommandBufferPool.Release(cmd);
  81. }
  82. void InitializeLightConstants(NativeArray<VisibleLight> lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out Vector4 lightOcclusionProbeChannel)
  83. {
  84. lightPos = k_DefaultLightPosition;
  85. lightColor = k_DefaultLightColor;
  86. lightAttenuation = k_DefaultLightAttenuation;
  87. lightSpotDir = k_DefaultLightSpotDirection;
  88. lightOcclusionProbeChannel = k_DefaultLightsProbeChannel;
  89. // When no lights are visible, main light will be set to -1.
  90. // In this case we initialize it to default values and return
  91. if (lightIndex < 0)
  92. return;
  93. VisibleLight lightData = lights[lightIndex];
  94. if (lightData.lightType == LightType.Directional)
  95. {
  96. Vector4 dir = -lightData.localToWorldMatrix.GetColumn(2);
  97. lightPos = new Vector4(dir.x, dir.y, dir.z, 0.0f);
  98. }
  99. else
  100. {
  101. Vector4 pos = lightData.localToWorldMatrix.GetColumn(3);
  102. lightPos = new Vector4(pos.x, pos.y, pos.z, 1.0f);
  103. }
  104. // VisibleLight.finalColor already returns color in active color space
  105. lightColor = lightData.finalColor;
  106. // Directional Light attenuation is initialize so distance attenuation always be 1.0
  107. if (lightData.lightType != LightType.Directional)
  108. {
  109. // Light attenuation in universal matches the unity vanilla one.
  110. // attenuation = 1.0 / distanceToLightSqr
  111. // We offer two different smoothing factors.
  112. // The smoothing factors make sure that the light intensity is zero at the light range limit.
  113. // The first smoothing factor is a linear fade starting at 80 % of the light range.
  114. // smoothFactor = (lightRangeSqr - distanceToLightSqr) / (lightRangeSqr - fadeStartDistanceSqr)
  115. // We rewrite smoothFactor to be able to pre compute the constant terms below and apply the smooth factor
  116. // with one MAD instruction
  117. // smoothFactor = distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)
  118. // distanceSqr * oneOverFadeRangeSqr + lightRangeSqrOverFadeRangeSqr
  119. // The other smoothing factor matches the one used in the Unity lightmapper but is slower than the linear one.
  120. // smoothFactor = (1.0 - saturate((distanceSqr * 1.0 / lightrangeSqr)^2))^2
  121. float lightRangeSqr = lightData.range * lightData.range;
  122. float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
  123. float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
  124. float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;
  125. float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
  126. float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);
  127. // On mobile and Nintendo Switch: Use the faster linear smoothing factor (SHADER_HINT_NICE_QUALITY).
  128. // On other devices: Use the smoothing factor that matches the GI.
  129. lightAttenuation.x = Application.isMobilePlatform || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Switch ? oneOverFadeRangeSqr : oneOverLightRangeSqr;
  130. lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;
  131. }
  132. if (lightData.lightType == LightType.Spot)
  133. {
  134. Vector4 dir = lightData.localToWorldMatrix.GetColumn(2);
  135. lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f);
  136. // Spot Attenuation with a linear falloff can be defined as
  137. // (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
  138. // This can be rewritten as
  139. // invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
  140. // SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
  141. // If we precompute the terms in a MAD instruction
  142. float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.spotAngle * 0.5f);
  143. // We neeed to do a null check for particle lights
  144. // This should be changed in the future
  145. // Particle lights will use an inline function
  146. float cosInnerAngle;
  147. if (lightData.light != null)
  148. cosInnerAngle = Mathf.Cos(lightData.light.innerSpotAngle * Mathf.Deg2Rad * 0.5f);
  149. else
  150. cosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(lightData.spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);
  151. float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);
  152. float invAngleRange = 1.0f / smoothAngleRange;
  153. float add = -cosOuterAngle * invAngleRange;
  154. lightAttenuation.z = invAngleRange;
  155. lightAttenuation.w = add;
  156. }
  157. Light light = lightData.light;
  158. // Set the occlusion probe channel.
  159. int occlusionProbeChannel = light != null ? light.bakingOutput.occlusionMaskChannel : -1;
  160. // If we have baked the light, the occlusion channel is the index we need to sample in 'unity_ProbesOcclusion'
  161. // If we have not baked the light, the occlusion channel is -1.
  162. // In case there is no occlusion channel is -1, we set it to zero, and then set the second value in the
  163. // input to one. We then, in the shader max with the second value for non-occluded lights.
  164. lightOcclusionProbeChannel.x = occlusionProbeChannel == -1 ? 0f : occlusionProbeChannel;
  165. lightOcclusionProbeChannel.y = occlusionProbeChannel == -1 ? 1f : 0f;
  166. // TODO: Add support to shadow mask
  167. if (light != null && light.bakingOutput.mixedLightingMode == MixedLightingMode.Subtractive && light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed)
  168. {
  169. if (m_MixedLightingSetup == MixedLightingSetup.None && lightData.light.shadows != LightShadows.None)
  170. {
  171. m_MixedLightingSetup = MixedLightingSetup.Subtractive;
  172. }
  173. }
  174. }
  175. void SetupShaderLightConstants(CommandBuffer cmd, ref RenderingData renderingData)
  176. {
  177. m_MixedLightingSetup = MixedLightingSetup.None;
  178. // Main light has an optimized shader path for main light. This will benefit games that only care about a single light.
  179. // Universal pipeline also supports only a single shadow light, if available it will be the main light.
  180. SetupMainLightConstants(cmd, ref renderingData.lightData);
  181. SetupAdditionalLightConstants(cmd, ref renderingData);
  182. }
  183. void SetupMainLightConstants(CommandBuffer cmd, ref LightData lightData)
  184. {
  185. Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir, lightOcclusionChannel;
  186. InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir, out lightOcclusionChannel);
  187. cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos);
  188. cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor);
  189. }
  190. void SetupAdditionalLightConstants(CommandBuffer cmd, ref RenderingData renderingData)
  191. {
  192. ref LightData lightData = ref renderingData.lightData;
  193. var cullResults = renderingData.cullResults;
  194. var lights = lightData.visibleLights;
  195. int maxAdditionalLightsCount = UniversalRenderPipeline.maxVisibleAdditionalLights;
  196. int additionalLightsCount = SetupPerObjectLightIndices(cullResults, ref lightData);
  197. if (additionalLightsCount > 0)
  198. {
  199. if (m_UseStructuredBuffer)
  200. {
  201. NativeArray<ShaderInput.LightData> additionalLightsData = new NativeArray<ShaderInput.LightData>(additionalLightsCount, Allocator.Temp);
  202. for (int i = 0, lightIter = 0; i < lights.Length && lightIter < maxAdditionalLightsCount; ++i)
  203. {
  204. VisibleLight light = lights[i];
  205. if (lightData.mainLightIndex != i)
  206. {
  207. ShaderInput.LightData data;
  208. InitializeLightConstants(lights, i,
  209. out data.position, out data.color, out data.attenuation,
  210. out data.spotDirection, out data.occlusionProbeChannels);
  211. additionalLightsData[lightIter] = data;
  212. lightIter++;
  213. }
  214. }
  215. var lightDataBuffer = ShaderData.instance.GetLightDataBuffer(additionalLightsCount);
  216. lightDataBuffer.SetData(additionalLightsData);
  217. int lightIndices = cullResults.lightAndReflectionProbeIndexCount;
  218. var lightIndicesBuffer = ShaderData.instance.GetLightIndicesBuffer(lightIndices);
  219. cmd.SetGlobalBuffer(m_AdditionalLightsBufferId, lightDataBuffer);
  220. cmd.SetGlobalBuffer(m_AdditionalLightsIndicesId, lightIndicesBuffer);
  221. additionalLightsData.Dispose();
  222. }
  223. else
  224. {
  225. for (int i = 0, lightIter = 0; i < lights.Length && lightIter < maxAdditionalLightsCount; ++i)
  226. {
  227. VisibleLight light = lights[i];
  228. if (lightData.mainLightIndex != i)
  229. {
  230. InitializeLightConstants(lights, i, out m_AdditionalLightPositions[lightIter],
  231. out m_AdditionalLightColors[lightIter],
  232. out m_AdditionalLightAttenuations[lightIter],
  233. out m_AdditionalLightSpotDirections[lightIter],
  234. out m_AdditionalLightOcclusionProbeChannels[lightIter]);
  235. lightIter++;
  236. }
  237. }
  238. cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions);
  239. cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors);
  240. cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations);
  241. cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections);
  242. cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightOcclusionProbeChannel, m_AdditionalLightOcclusionProbeChannels);
  243. }
  244. cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount,
  245. 0.0f, 0.0f, 0.0f));
  246. }
  247. else
  248. {
  249. cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero);
  250. }
  251. }
  252. int SetupPerObjectLightIndices(CullingResults cullResults, ref LightData lightData)
  253. {
  254. if (lightData.additionalLightsCount == 0)
  255. return lightData.additionalLightsCount;
  256. var visibleLights = lightData.visibleLights;
  257. var perObjectLightIndexMap = cullResults.GetLightIndexMap(Allocator.Temp);
  258. int globalDirectionalLightsCount = 0;
  259. int additionalLightsCount = 0;
  260. // Disable all directional lights from the perobject light indices
  261. // Pipeline handles main light globally and there's no support for additional directional lights atm.
  262. for (int i = 0; i < visibleLights.Length; ++i)
  263. {
  264. if (additionalLightsCount >= UniversalRenderPipeline.maxVisibleAdditionalLights)
  265. break;
  266. VisibleLight light = visibleLights[i];
  267. if (i == lightData.mainLightIndex)
  268. {
  269. perObjectLightIndexMap[i] = -1;
  270. ++globalDirectionalLightsCount;
  271. }
  272. else
  273. {
  274. perObjectLightIndexMap[i] -= globalDirectionalLightsCount;
  275. ++additionalLightsCount;
  276. }
  277. }
  278. // Disable all remaining lights we cannot fit into the global light buffer.
  279. for (int i = globalDirectionalLightsCount + additionalLightsCount; i < perObjectLightIndexMap.Length; ++i)
  280. perObjectLightIndexMap[i] = -1;
  281. cullResults.SetLightIndexMap(perObjectLightIndexMap);
  282. if (m_UseStructuredBuffer && additionalLightsCount > 0)
  283. {
  284. int lightAndReflectionProbeIndices = cullResults.lightAndReflectionProbeIndexCount;
  285. Assertions.Assert.IsTrue(lightAndReflectionProbeIndices > 0, "Pipelines configures additional lights but per-object light and probe indices count is zero.");
  286. cullResults.FillLightAndReflectionProbeIndices(ShaderData.instance.GetLightIndicesBuffer(lightAndReflectionProbeIndices));
  287. }
  288. perObjectLightIndexMap.Dispose();
  289. return additionalLightsCount;
  290. }
  291. }
  292. }