Light2D.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine.Serialization;
  4. using UnityEngine.Rendering;
  5. #if UNITY_EDITOR
  6. using UnityEditor.Experimental.SceneManagement;
  7. #endif
  8. namespace UnityEngine.Experimental.Rendering.Universal
  9. {
  10. class Light2DManager : IDisposable
  11. {
  12. const int k_BlendStyleCount = 4; // This must match the array size of m_LightBlendStyles in _2DRendererData.
  13. static Light2DManager s_Instance = new Light2DManager();
  14. Light2DManager m_PrevInstance;
  15. List<Light2D>[] m_Lights;
  16. CullingGroup m_CullingGroup;
  17. BoundingSphere[] m_BoundingSpheres;
  18. internal static List<Light2D>[] lights => s_Instance.m_Lights;
  19. internal static CullingGroup cullingGroup
  20. {
  21. get => s_Instance.m_CullingGroup;
  22. set => s_Instance.m_CullingGroup = value;
  23. }
  24. internal static BoundingSphere[] boundingSpheres
  25. {
  26. get => s_Instance.m_BoundingSpheres;
  27. set => s_Instance.m_BoundingSpheres = value;
  28. }
  29. internal static bool GetGlobalColor(int sortingLayerIndex, int blendStyleIndex, out Color color)
  30. {
  31. bool foundGlobalColor = false;
  32. color = Color.black;
  33. // This should be rewritten to search only global lights
  34. List<Light2D> lights = s_Instance.m_Lights[blendStyleIndex];
  35. for (int i = 0; i < lights.Count; ++i)
  36. {
  37. Light2D light = lights[i];
  38. if (light.lightType == Light2D.LightType.Global && light.IsLitLayer(sortingLayerIndex))
  39. {
  40. bool inCurrentPrefabStage = true;
  41. #if UNITY_EDITOR
  42. // If we found the first global light in our prefab stage
  43. inCurrentPrefabStage = PrefabStageUtility.GetPrefabStage(light.gameObject) == PrefabStageUtility.GetCurrentPrefabStage();
  44. #endif
  45. if (inCurrentPrefabStage)
  46. {
  47. color = light.color * light.intensity;
  48. return true;
  49. }
  50. else
  51. {
  52. if (!foundGlobalColor)
  53. {
  54. color = light.color * light.intensity;
  55. foundGlobalColor = true;
  56. }
  57. }
  58. }
  59. }
  60. return foundGlobalColor;
  61. }
  62. internal static bool ContainsDuplicateGlobalLight(int sortingLayerIndex, int blendStyleIndex)
  63. {
  64. int globalLightCount = 0;
  65. // This should be rewritten to search only global lights
  66. List<Light2D> lights = s_Instance.m_Lights[blendStyleIndex];
  67. for (int i = 0; i < lights.Count; i++)
  68. {
  69. Light2D light = lights[i];
  70. if (light.lightType == Light2D.LightType.Global && light.IsLitLayer(sortingLayerIndex))
  71. {
  72. #if UNITY_EDITOR
  73. // If we found the first global light in our prefab stage
  74. if (PrefabStageUtility.GetPrefabStage(light.gameObject) == PrefabStageUtility.GetCurrentPrefabStage())
  75. #endif
  76. {
  77. if (globalLightCount > 0)
  78. return true;
  79. globalLightCount++;
  80. }
  81. }
  82. }
  83. return false;
  84. }
  85. internal Light2DManager()
  86. {
  87. m_PrevInstance = s_Instance;
  88. s_Instance = this;
  89. m_Lights = new List<Light2D>[k_BlendStyleCount];
  90. for (int i = 0; i < m_Lights.Length; ++i)
  91. m_Lights[i] = new List<Light2D>();
  92. }
  93. public void Dispose()
  94. {
  95. s_Instance = m_PrevInstance;
  96. }
  97. }
  98. /// <summary>
  99. /// Class <c>Light2D</c> is a 2D light which can be used with the 2D Renderer.
  100. /// </summary>
  101. ///
  102. [ExecuteAlways, DisallowMultipleComponent]
  103. [AddComponentMenu("Rendering/2D/Light 2D (Experimental)")]
  104. sealed public partial class Light2D : MonoBehaviour
  105. {
  106. /// <summary>
  107. /// an enumeration of the types of light
  108. /// </summary>
  109. public enum LightType
  110. {
  111. Parametric = 0,
  112. Freeform = 1,
  113. Sprite = 2,
  114. Point = 3,
  115. Global = 4
  116. }
  117. [UnityEngine.Animations.NotKeyable]
  118. [SerializeField]
  119. LightType m_LightType = LightType.Parametric;
  120. LightType m_PreviousLightType = (LightType)LightType.Parametric;
  121. [SerializeField, FormerlySerializedAs("m_LightOperationIndex")]
  122. int m_BlendStyleIndex = 0;
  123. [SerializeField]
  124. float m_FalloffIntensity = 0.5f;
  125. [ColorUsage(false)]
  126. [SerializeField]
  127. Color m_Color = Color.white;
  128. [SerializeField]
  129. float m_Intensity = 1;
  130. [SerializeField] float m_LightVolumeOpacity = 0.0f;
  131. [SerializeField] int[] m_ApplyToSortingLayers = new int[1]; // These are sorting layer IDs. If we need to update this at runtime make sure we add code to update global lights
  132. [SerializeField] Sprite m_LightCookieSprite = null;
  133. [SerializeField] bool m_UseNormalMap = false;
  134. [SerializeField] int m_LightOrder = 0;
  135. [SerializeField] bool m_AlphaBlendOnOverlap = false;
  136. int m_PreviousLightOrder = -1;
  137. int m_PreviousBlendStyleIndex;
  138. float m_PreviousLightVolumeOpacity;
  139. bool m_PreviousLightCookieSpriteExists = false;
  140. Sprite m_PreviousLightCookieSprite = null;
  141. Mesh m_Mesh;
  142. int m_LightCullingIndex = -1;
  143. Bounds m_LocalBounds;
  144. [Range(0,1)]
  145. [SerializeField] float m_ShadowIntensity = 0.0f;
  146. [Range(0,1)]
  147. [SerializeField] float m_ShadowVolumeIntensity = 0.0f;
  148. internal struct LightStats
  149. {
  150. public int totalLights;
  151. public int totalNormalMapUsage;
  152. public int totalVolumetricUsage;
  153. public uint blendStylesUsed;
  154. }
  155. /// <summary>
  156. /// The lights current type
  157. /// </summary>
  158. public LightType lightType
  159. {
  160. get => m_LightType;
  161. set => m_LightType = value;
  162. }
  163. /// <summary>
  164. /// The lights current operation index
  165. /// </summary>
  166. public int blendStyleIndex { get => m_BlendStyleIndex; set => m_BlendStyleIndex = value; }
  167. /// <summary>
  168. /// Specifies the darkness of the shadow
  169. /// </summary>
  170. public float shadowIntensity { get => m_ShadowIntensity; set => m_ShadowIntensity = Mathf.Clamp01(value); }
  171. /// <summary>
  172. /// Specifies the darkness of the shadow
  173. /// </summary>
  174. public float shadowVolumeIntensity { get => m_ShadowVolumeIntensity; set => m_ShadowVolumeIntensity = Mathf.Clamp01(value); }
  175. /// <summary>
  176. /// The lights current color
  177. /// </summary>
  178. public Color color
  179. {
  180. get { return m_Color; }
  181. set { m_Color = value; }
  182. }
  183. /// <summary>
  184. /// The lights current intensity
  185. /// </summary>
  186. public float intensity
  187. {
  188. get { return m_Intensity; }
  189. set { m_Intensity = value; }
  190. }
  191. /// <summary>
  192. /// The lights current intensity
  193. /// </summary>
  194. public float volumeOpacity => m_LightVolumeOpacity;
  195. public Sprite lightCookieSprite => m_LightCookieSprite;
  196. public float falloffIntensity => m_FalloffIntensity;
  197. public bool useNormalMap => m_UseNormalMap;
  198. public bool alphaBlendOnOverlap => m_AlphaBlendOnOverlap;
  199. public int lightOrder { get => m_LightOrder; set => m_LightOrder = value; }
  200. internal int lightCullingIndex => m_LightCullingIndex;
  201. static SortingLayer[] s_SortingLayers;
  202. #if UNITY_EDITOR
  203. public static string s_IconsPath = "Packages/com.unity.render-pipelines.universal/Editor/2D/Resources/SceneViewIcons/";
  204. public static string s_ParametricLightIconPath = s_IconsPath + "ParametricLight.png";
  205. public static string s_FreeformLightIconPath = s_IconsPath + "FreeformLight.png";
  206. public static string s_SpriteLightIconPath = s_IconsPath + "SpriteLight.png";
  207. public static string s_PointLightIconPath = s_IconsPath + "PointLight.png";
  208. public static string s_GlobalLightIconPath = s_IconsPath + "GlobalLight.png";
  209. public static string[] s_LightIconPaths = new string[] { s_ParametricLightIconPath, s_FreeformLightIconPath, s_SpriteLightIconPath, s_PointLightIconPath, s_GlobalLightIconPath };
  210. #endif
  211. internal static void SetupCulling(ScriptableRenderContext context, Camera camera)
  212. {
  213. if (Light2DManager.cullingGroup == null)
  214. return;
  215. Light2DManager.cullingGroup.targetCamera = camera;
  216. int totalLights = 0;
  217. for (int blendStyleIndex = 0; blendStyleIndex < Light2DManager.lights.Length; ++blendStyleIndex)
  218. totalLights += Light2DManager.lights[blendStyleIndex].Count;
  219. if (Light2DManager.boundingSpheres == null)
  220. Light2DManager.boundingSpheres = new BoundingSphere[Mathf.Max(1024, 2 * totalLights)];
  221. else if (totalLights > Light2DManager.boundingSpheres.Length)
  222. Light2DManager.boundingSpheres = new BoundingSphere[2 * totalLights];
  223. int currentLightCullingIndex = 0;
  224. for (int blendStyleIndex = 0; blendStyleIndex < Light2DManager.lights.Length; ++blendStyleIndex)
  225. {
  226. var lightsPerBlendStyle = Light2DManager.lights[blendStyleIndex];
  227. for (int lightIndex = 0; lightIndex < lightsPerBlendStyle.Count; ++lightIndex)
  228. {
  229. Light2D light = lightsPerBlendStyle[lightIndex];
  230. if (light == null)
  231. continue;
  232. Light2DManager.boundingSpheres[currentLightCullingIndex] = light.GetBoundingSphere();
  233. light.m_LightCullingIndex = currentLightCullingIndex++;
  234. }
  235. }
  236. Light2DManager.cullingGroup.SetBoundingSpheres(Light2DManager.boundingSpheres);
  237. Light2DManager.cullingGroup.SetBoundingSphereCount(currentLightCullingIndex);
  238. }
  239. internal static bool IsSceneLit(Camera camera)
  240. {
  241. for (int layer = 0; layer < Light2DManager.lights.Length; layer++)
  242. {
  243. List<Light2D> lightList = Light2DManager.lights[layer];
  244. for (int lightIndex = 0; lightIndex < lightList.Count; lightIndex++)
  245. {
  246. if (lightList[lightIndex].lightType == LightType.Global || lightList[lightIndex].IsLightVisible(camera))
  247. return true;
  248. }
  249. }
  250. return false;
  251. }
  252. internal static List<Light2D> GetLightsByBlendStyle(int blendStyleIndex)
  253. {
  254. return Light2DManager.lights[blendStyleIndex];
  255. }
  256. internal int GetTopMostLitLayer()
  257. {
  258. int largestIndex = -1;
  259. int largestLayer = 0;
  260. SortingLayer[] layers;
  261. if (Application.isPlaying)
  262. {
  263. if (s_SortingLayers == null)
  264. s_SortingLayers = SortingLayer.layers;
  265. layers = s_SortingLayers;
  266. }
  267. else
  268. layers = SortingLayer.layers;
  269. for (int i = 0; i < m_ApplyToSortingLayers.Length; ++i)
  270. {
  271. for(int layer = layers.Length - 1; layer >= largestLayer; --layer)
  272. {
  273. if (layers[layer].id == m_ApplyToSortingLayers[i])
  274. {
  275. largestIndex = i;
  276. largestLayer = layer;
  277. }
  278. }
  279. }
  280. if (largestIndex >= 0)
  281. return m_ApplyToSortingLayers[largestIndex];
  282. else
  283. return -1;
  284. }
  285. void UpdateMesh()
  286. {
  287. GetMesh(true);
  288. }
  289. internal bool IsLitLayer(int layer)
  290. {
  291. return m_ApplyToSortingLayers != null ? Array.IndexOf(m_ApplyToSortingLayers, layer) >= 0 : false;
  292. }
  293. void InsertLight()
  294. {
  295. var lightList = Light2DManager.lights[m_BlendStyleIndex];
  296. int index = 0;
  297. while (index < lightList.Count && m_LightOrder > lightList[index].m_LightOrder)
  298. index++;
  299. lightList.Insert(index, this);
  300. }
  301. void UpdateBlendStyle()
  302. {
  303. if (m_BlendStyleIndex == m_PreviousBlendStyleIndex)
  304. return;
  305. Light2DManager.lights[m_PreviousBlendStyleIndex].Remove(this);
  306. m_PreviousBlendStyleIndex = m_BlendStyleIndex;
  307. InsertLight();
  308. if (m_LightType == LightType.Global)
  309. ErrorIfDuplicateGlobalLight();
  310. }
  311. internal BoundingSphere GetBoundingSphere()
  312. {
  313. return IsShapeLight() ? GetShapeLightBoundingSphere() : GetPointLightBoundingSphere();
  314. }
  315. internal Mesh GetMesh(bool forceUpdate = false)
  316. {
  317. if (m_Mesh != null && !forceUpdate)
  318. return m_Mesh;
  319. if (m_Mesh == null)
  320. m_Mesh = new Mesh();
  321. Color combinedColor = m_Intensity * m_Color;
  322. switch (m_LightType)
  323. {
  324. case LightType.Freeform:
  325. m_LocalBounds = LightUtility.GenerateShapeMesh(ref m_Mesh, m_ShapePath, m_ShapeLightFalloffSize);
  326. break;
  327. case LightType.Parametric:
  328. m_LocalBounds = LightUtility.GenerateParametricMesh(ref m_Mesh, m_ShapeLightParametricRadius, m_ShapeLightFalloffSize, m_ShapeLightParametricAngleOffset, m_ShapeLightParametricSides);
  329. break;
  330. case LightType.Sprite:
  331. m_Mesh.Clear();
  332. m_LocalBounds = LightUtility.GenerateSpriteMesh(ref m_Mesh, m_LightCookieSprite, 1);
  333. break;
  334. case LightType.Point:
  335. m_LocalBounds = LightUtility.GenerateParametricMesh(ref m_Mesh, 1.412135f, 0, 0, 4);
  336. break;
  337. }
  338. return m_Mesh;
  339. }
  340. internal bool IsLightVisible(Camera camera)
  341. {
  342. bool isVisible = (Light2DManager.cullingGroup == null || Light2DManager.cullingGroup.IsVisible(m_LightCullingIndex)) && isActiveAndEnabled;
  343. #if UNITY_EDITOR
  344. isVisible &= UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(gameObject, camera);
  345. #endif
  346. return isVisible;
  347. }
  348. internal void ErrorIfDuplicateGlobalLight()
  349. {
  350. for (int i = 0; i < m_ApplyToSortingLayers.Length; ++i)
  351. {
  352. int sortingLayer = m_ApplyToSortingLayers[i];
  353. if(Light2DManager.ContainsDuplicateGlobalLight(sortingLayer, blendStyleIndex))
  354. Debug.LogError("More than one global light on layer " + SortingLayer.IDToName(sortingLayer) + " for light blend style index " + m_BlendStyleIndex);
  355. }
  356. }
  357. private void Awake()
  358. {
  359. GetMesh();
  360. }
  361. void OnEnable()
  362. {
  363. // This has to stay in OnEnable() because we need to re-initialize the static variables after a domain reload.
  364. if (Light2DManager.cullingGroup == null)
  365. {
  366. Light2DManager.cullingGroup = new CullingGroup();
  367. RenderPipelineManager.beginCameraRendering += SetupCulling;
  368. }
  369. if (!Light2DManager.lights[m_BlendStyleIndex].Contains(this))
  370. InsertLight();
  371. m_PreviousBlendStyleIndex = m_BlendStyleIndex;
  372. if (m_LightType == LightType.Global)
  373. ErrorIfDuplicateGlobalLight();
  374. m_PreviousLightType = m_LightType;
  375. }
  376. private void OnDisable()
  377. {
  378. bool anyLightLeft = false;
  379. for (int i = 0; i < Light2DManager.lights.Length; ++i)
  380. {
  381. Light2DManager.lights[i].Remove(this);
  382. if (Light2DManager.lights[i].Count > 0)
  383. anyLightLeft = true;
  384. }
  385. if (!anyLightLeft && Light2DManager.cullingGroup != null)
  386. {
  387. Light2DManager.cullingGroup.Dispose();
  388. Light2DManager.cullingGroup = null;
  389. RenderPipelineManager.beginCameraRendering -= SetupCulling;
  390. }
  391. }
  392. internal List<Vector2> GetFalloffShape()
  393. {
  394. List<Vector2> shape = new List<Vector2>();
  395. List<Vector2> extrusionDir = new List<Vector2>();
  396. LightUtility.GetFalloffShape(m_ShapePath, ref extrusionDir);
  397. for (int i = 0; i < m_ShapePath.Length; i++)
  398. {
  399. Vector2 position = new Vector2();
  400. position.x = m_ShapePath[i].x + this.shapeLightFalloffSize * extrusionDir[i].x;
  401. position.y = m_ShapePath[i].y + this.shapeLightFalloffSize * extrusionDir[i].y;
  402. shape.Add(position);
  403. }
  404. return shape;
  405. }
  406. static internal LightStats GetLightStatsByLayer(int layer)
  407. {
  408. LightStats returnStats = new LightStats();
  409. for(int blendStyleIndex = 0; blendStyleIndex < Light2DManager.lights.Length; blendStyleIndex++)
  410. {
  411. List<Light2D> lights = Light2DManager.lights[blendStyleIndex];
  412. for (int lightIndex = 0; lightIndex < lights.Count; lightIndex++)
  413. {
  414. Light2D light = lights[lightIndex];
  415. if (light.IsLitLayer(layer))
  416. {
  417. returnStats.totalLights++;
  418. if (light.useNormalMap)
  419. returnStats.totalNormalMapUsage++;
  420. if (light.volumeOpacity > 0)
  421. returnStats.totalVolumetricUsage++;
  422. }
  423. uint blendStyleUsed = (uint)(1 << light.blendStyleIndex);
  424. returnStats.blendStylesUsed |= blendStyleUsed;
  425. }
  426. }
  427. return returnStats;
  428. }
  429. private void LateUpdate()
  430. {
  431. UpdateBlendStyle();
  432. bool rebuildMesh = false;
  433. // Sorting. InsertLight() will make sure the lights are sorted.
  434. if (LightUtility.CheckForChange(m_LightOrder, ref m_PreviousLightOrder))
  435. {
  436. Light2DManager.lights[(int)m_BlendStyleIndex].Remove(this);
  437. InsertLight();
  438. }
  439. if (m_LightType != m_PreviousLightType)
  440. {
  441. if (m_LightType == LightType.Global)
  442. ErrorIfDuplicateGlobalLight();
  443. else
  444. rebuildMesh = true;
  445. m_PreviousLightType = m_LightType;
  446. }
  447. // Mesh Rebuilding
  448. rebuildMesh |= LightUtility.CheckForChange(m_ShapeLightFalloffSize, ref m_PreviousShapeLightFalloffSize);
  449. rebuildMesh |= LightUtility.CheckForChange(m_ShapeLightParametricRadius, ref m_PreviousShapeLightParametricRadius);
  450. rebuildMesh |= LightUtility.CheckForChange(m_ShapeLightParametricSides, ref m_PreviousShapeLightParametricSides);
  451. rebuildMesh |= LightUtility.CheckForChange(m_LightVolumeOpacity, ref m_PreviousLightVolumeOpacity);
  452. rebuildMesh |= LightUtility.CheckForChange(m_ShapeLightParametricAngleOffset, ref m_PreviousShapeLightParametricAngleOffset);
  453. rebuildMesh |= LightUtility.CheckForChange(m_LightCookieSprite != null, ref m_PreviousLightCookieSpriteExists);
  454. rebuildMesh |= LightUtility.CheckForChange(m_LightCookieSprite, ref m_PreviousLightCookieSprite);
  455. rebuildMesh |= LightUtility.CheckForChange(m_ShapeLightFalloffOffset, ref m_PreviousShapeLightFalloffOffset);
  456. #if UNITY_EDITOR
  457. rebuildMesh |= LightUtility.CheckForChange(LightUtility.GetShapePathHash(m_ShapePath), ref m_PreviousShapePathHash);
  458. #endif
  459. if(rebuildMesh && m_LightType != LightType.Global)
  460. UpdateMesh();
  461. }
  462. #if UNITY_EDITOR
  463. private void OnDrawGizmos()
  464. {
  465. Gizmos.color = Color.blue;
  466. Gizmos.DrawIcon(transform.position, s_LightIconPaths[(int)m_LightType], true);
  467. }
  468. void Reset()
  469. {
  470. m_ShapePath = new Vector3[] { new Vector3(-0.5f, -0.5f), new Vector3(0.5f, -0.5f), new Vector3(0.5f, 0.5f), new Vector3(-0.5f, 0.5f) };
  471. }
  472. #endif
  473. }
  474. }