using System; using UnityEngine.Serialization; using UnityEngine.Scripting.APIUpdating; using UnityEngine.U2D; using UnityEngine.Rendering.RenderGraphModule; using System.Collections.Generic; namespace UnityEngine.Rendering.Universal { /// /// Class Light2D is a 2D light which can be used with the 2D Renderer. /// /// [ExecuteAlways, DisallowMultipleComponent] [Icon("UnityEngine/Light Icon")] [MovedFrom(true, "UnityEngine.Experimental.Rendering.Universal", "Unity.RenderPipelines.Universal.Runtime")] [AddComponentMenu("Rendering/2D/Light 2D")] [HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2DLightProperties.html")] public sealed partial class Light2D : Light2DBase, ISerializationCallbackReceiver { /// /// Deprecated Light types that are no supported. Please migrate to either Freeform or Point lights. /// public enum DeprecatedLightType { /// /// N-gon shaped lights. /// Parametric = 0, } /// /// An enumeration of the types of light /// public enum LightType { /// /// N-gon shaped lights. Deprecated. /// Parametric = 0, /// /// The shape of the light is based on a user defined closed shape with multiple points. /// Freeform = 1, /// /// The shape of the light is based on a Sprite. /// Sprite = 2, /// /// The shape of light is circular and can also be configured into a pizza shape. /// Point = 3, /// /// Shapeless light that affects the entire screen. /// Global = 4, /// /// Light completely supplied by a provider /// Provider = 5, } /// /// The accuracy of how the normal map calculation. /// public enum NormalMapQuality { /// /// Normal map not used. /// Disabled = 2, /// /// Faster calculation with less accuracy suited for small shapes on screen. /// Fast = 0, /// /// Accurate calculation useful for better output on bigger shapes on screen. /// Accurate = 1 } /// /// Determines how the final color is calculated when multiple lights overlap each other /// public enum OverlapOperation { /// /// Colors are added together /// Additive, /// /// Colors are blended using standard blending (alpha, 1-alpha) /// AlphaBlend } private enum ComponentVersions { Version_Unserialized = 0, Version_1 = 1, Version_2 = 2, Version_3 = 3, Version_4 = 4 } const ComponentVersions k_CurrentComponentVersion = ComponentVersions.Version_4; [SerializeField] ComponentVersions m_ComponentVersion = ComponentVersions.Version_Unserialized; static Bounds kEmptyBounds = new Bounds(Vector3.zero, Vector3.zero); #if USING_ANIMATION_MODULE [UnityEngine.Animations.NotKeyable] #endif [SerializeField] LightType m_LightType = LightType.Point; [SerializeField, FormerlySerializedAs("m_LightOperationIndex")] int m_BlendStyleIndex = 0; [SerializeReference] Light2DProvider m_Light2DProvider; #if UNITY_EDITOR [SerializeReference] Light2DProviderSources m_SelectionSources = new Light2DProviderSources(); #endif [SerializeField] float m_FalloffIntensity = 0.5f; [ColorUsage(true)] [SerializeField] Color m_Color = Color.white; [SerializeField] float m_Intensity = 1; [FormerlySerializedAs("m_LightVolumeOpacity")] [SerializeField] float m_LightVolumeIntensity = 1.0f; [FormerlySerializedAs("m_LightVolumeIntensityEnabled")] [SerializeField] bool m_LightVolumeEnabled = false; [SerializeField] int[] m_ApplyToSortingLayers; // These are sorting layer IDs. If we need to update this at runtime make sure we add code to update global lights [Reload("Textures/2D/Sparkle.png")] [SerializeField] Sprite m_LightCookieSprite; [Obsolete("Use m_LightCookieSprite instead")] [SerializeField] Sprite m_DeprecatedPointLightCookieSprite; [SerializeField] int m_LightOrder = 0; [SerializeField] bool m_AlphaBlendOnOverlap = false; // This is now deprecated. Keep it here for backwards compatibility. [SerializeField] OverlapOperation m_OverlapOperation = OverlapOperation.Additive; [FormerlySerializedAs("m_PointLightDistance")] [SerializeField] float m_NormalMapDistance = 3.0f; #if USING_ANIMATION_MODULE [UnityEngine.Animations.NotKeyable] #endif [FormerlySerializedAs("m_PointLightQuality")] [SerializeField] NormalMapQuality m_NormalMapQuality = NormalMapQuality.Disabled; [SerializeField] bool m_UseNormalMap = false; // This is now deprecated. Keep it here for backwards compatibility. [FormerlySerializedAs("m_ShadowIntensityEnabled")] [SerializeField] bool m_ShadowsEnabled = true; [Range(0, 1)] [SerializeField] float m_ShadowIntensity = 0.75f; [Range(0, 1)] [SerializeField] float m_ShadowSoftness = 0.3f; [Range(0, 1)] [SerializeField] float m_ShadowSoftnessFalloffIntensity = 0.5f; [SerializeField] bool m_ShadowVolumeIntensityEnabled = false; [Range(0, 1)] [SerializeField] float m_ShadowVolumeIntensity = 0.75f; [SerializeField] RenderingLayerMask m_RenderingLayersMask = RenderingLayerMask.defaultRenderingLayerMask; Mesh m_Mesh; // m_Mesh either points at a Mesh this component allocated through lightMesh, or at one handed over by // Light2DProvider.GetMesh(). That contract only returns a mesh to render and does not transfer ownership, // so provider meshes may be shared between lights and must never be destroyed from here. bool m_OwnsMesh; [NonSerialized] private LightUtility.LightMeshVertex[] m_Vertices = new LightUtility.LightMeshVertex[1]; [NonSerialized] private ushort[] m_Triangles = new ushort[1]; internal LightUtility.LightMeshVertex[] vertices { get { return m_Vertices; } set { m_Vertices = value; } } internal ushort[] indices { get { return m_Triangles; } set { m_Triangles = value; } } // Transients EntityId m_PreviousLightCookieSprite; internal Vector3 m_CachedPosition; // We use Blue Channel of LightMesh's vertex color to indicate Slot Index. int m_BatchSlotIndex = 0; internal int batchSlotIndex { get { return m_BatchSlotIndex; } set { m_BatchSlotIndex = value; } } private EntityId lightCookieSpriteEntityId => lightCookieSprite?.GetEntityId() ?? EntityId.None; internal bool useCookieSprite => (lightType == LightType.Point || lightType == LightType.Sprite) && (lightCookieSprite != null && lightCookieSprite.texture != null); internal RTHandle m_CookieSpriteTexture = null; internal TextureHandle m_CookieSpriteTextureHandle; [SerializeField] Bounds m_LocalBounds; internal BoundingSphere boundingSphere { get; private set; } internal Mesh lightMesh { get { if (null == m_Mesh) { m_Mesh = new Mesh(); m_OwnsMesh = true; } return m_Mesh; } } // Takes the mesh a Light2DProvider handed us, releasing any mesh we allocated ourselves first so replacing // it does not leak. void SetProviderMesh(Mesh providerMesh) { // A provider is free to hand back the mesh we already hold, in which case ownership is unchanged. if (m_Mesh == providerMesh) return; if (m_OwnsMesh) CoreUtils.Destroy(m_Mesh); m_Mesh = providerMesh; m_OwnsMesh = false; } internal bool hasCachedMesh => (vertices.Length > 1 && indices.Length > 1); internal bool forceUpdate = false; /// /// The light's current type /// public LightType lightType { get { return (LightType)m_LightType; } set { m_LightType = value; #if UNITY_EDITOR if((int)m_LightType != (int)LightType.Provider) m_SelectionSources.selectedHashCode = (int)m_LightType; #endif } } /// /// Gets or sets the current light2DProvider /// public Light2DProvider light2DProvider { get { return m_Light2DProvider; } set { m_Light2DProvider = value; } } /// /// The lights current operation index /// public int blendStyleIndex { get => m_BlendStyleIndex; set => m_BlendStyleIndex = value; } /// /// Specifies the darkness of the shadow /// public float shadowIntensity { get => m_ShadowIntensity; set => m_ShadowIntensity = Mathf.Clamp01(value); } /// /// Specifies the softness of the soft shadow /// public float shadowSoftness { get => m_ShadowSoftness; set => m_ShadowSoftness = value; } /// /// Specifies that the shadows are enabled /// public bool shadowsEnabled { get => m_ShadowsEnabled; set => m_ShadowsEnabled = value; } /// /// Specifies the darkness of the shadow /// public float shadowVolumeIntensity { get => m_ShadowVolumeIntensity; set => m_ShadowVolumeIntensity = Mathf.Clamp01(value); } /// /// Specifies that the volumetric shadows are enabled /// public bool volumetricShadowsEnabled { get => m_ShadowVolumeIntensityEnabled; set => m_ShadowVolumeIntensityEnabled = value; } /// /// The lights current color /// public Color color { get => m_Color; set => m_Color = value; } /// /// The lights current intensity /// public float intensity { get => m_Intensity; set => m_Intensity = value; } /// /// The lights current intensity /// /// [Obsolete("#from(2021.1)")] public float volumeOpacity => m_LightVolumeIntensity; /// /// Controls the visibility of the light's volume /// public float volumeIntensity { get => m_LightVolumeIntensity; set => m_LightVolumeIntensity = value; } /// /// Enables or disables the light's volume /// /// [Obsolete("#from(2023.1)")] public bool volumeIntensityEnabled { get => m_LightVolumeEnabled; set => m_LightVolumeEnabled = value; } /// /// Enables or disables the light's volume /// /// public bool volumetricEnabled { get => m_LightVolumeEnabled; set => m_LightVolumeEnabled = value; } /// /// The Sprite that's used by the Sprite Light type to control the shape light /// public Sprite lightCookieSprite { get { return m_LightCookieSprite; } set => m_LightCookieSprite = value; } /// /// Controls the brightness and distance of the fall off (edge) of the light /// public float falloffIntensity { get => m_FalloffIntensity; set => m_FalloffIntensity = Mathf.Clamp(value, 0, 1); } /// /// Controls the falloff for soft shadows /// public float shadowSoftnessFalloffIntensity { get => m_ShadowSoftnessFalloffIntensity; set => m_ShadowSoftnessFalloffIntensity = Mathf.Clamp(value, 0, 1); } /// /// Checks if the alpha overlap operation is alpha blend. /// This is obsolete. /// [Obsolete("#from(2021.1)")] public bool alphaBlendOnOverlap { get { return m_OverlapOperation == OverlapOperation.AlphaBlend; } } /// /// Controls the overlap operation mode. /// public OverlapOperation overlapOperation { get => m_OverlapOperation; set => m_OverlapOperation = value; } /// /// Gets or sets the light order. The lightOrder determines the order in which the lights are rendered onto the light textures. /// public int lightOrder { get => m_LightOrder; set => m_LightOrder = value; } /// /// The simulated z distance of the light from the surface used in normal map calculation. /// public float normalMapDistance => m_NormalMapDistance; /// /// Returns the calculation quality for the normal map rendering. Please refer to NormalMapQuality. /// public NormalMapQuality normalMapQuality => m_NormalMapQuality; /// /// Returns if volumetric shadows should be rendered. /// public bool renderVolumetricShadows => volumetricShadowsEnabled && shadowVolumeIntensity > 0; /// /// Gets or sets the target sorting layers for the light. Contains an array of sorting layer IDs. /// public int[] targetSortingLayers { get => m_ApplyToSortingLayers; set { var layers = new List(); foreach (var layerID in value) { if (SortingLayer.IsValid(layerID)) layers.Add(layerID); } m_ApplyToSortingLayers = layers.ToArray(); } } /// /// Gets or sets the rendering layer mask for the light. /// public RenderingLayerMask renderingLayerMask { get { return m_RenderingLayersMask; } set { m_RenderingLayersMask = value; } } bool IsValidLayer(string name) { // Have this check as SortingLayer.NameToID returns 0 (default layer) if layer is not found foreach (var layer in Light2DManager.GetCachedSortingLayer()) { if (layer.name == name) return true; } return false; } /// /// Adds a target sorting layer to the light. /// /// The sorting layer name to be added. /// Returns true if the sorting layer is added. Returns false if the layer name is invalid or has already been added. public bool AddTargetSortingLayer(string layerName) { var layers = new List(m_ApplyToSortingLayers); var id = SortingLayer.NameToID(layerName); // Invalid or duplicate layerID if (!IsValidLayer(layerName) || layers.Contains(id)) return false; layers.Add(id); m_ApplyToSortingLayers = layers.ToArray(); return true; } /// /// Adds a target sorting layer to the light. /// /// The sorting layer ID to be added. /// Returns true if the sorting layer is added. Returns false if the layer ID is invalid or has already been added. public bool AddTargetSortingLayer(int layerID) { return AddTargetSortingLayer(SortingLayer.IDToName(layerID)); } /// /// Removes a target sorting layer from the light. /// /// The sorting layer name to be removed. /// Returns true if the sorting layer is removed. Returns false if the layer name is invalid or doesn't exist. public bool RemoveTargetSortingLayer(string layerName) { var layers = new List(m_ApplyToSortingLayers); var id = SortingLayer.NameToID(layerName); // Invalid or layerID does not exist if (!IsValidLayer(layerName) || !layers.Contains(id)) return false; layers.Remove(id); m_ApplyToSortingLayers = layers.ToArray(); return true; } /// /// Removes a target sorting layer from the light. /// /// The sorting layer ID to be removed. /// Returns true if the sorting layer is removed. Returns false if the layer ID is invalid or doesn't exist. public bool RemoveTargetSortingLayer(int layerID) { return RemoveTargetSortingLayer(SortingLayer.IDToName(layerID)); } internal void MarkForUpdate() { forceUpdate = true; } internal void CacheValues() { m_CachedPosition = transform.position; } internal int GetTopMostLitLayer() { var largestIndex = Int32.MinValue; var largestLayer = 0; var layers = Light2DManager.GetCachedSortingLayer(); for (var i = 0; i < m_ApplyToSortingLayers.Length; ++i) { for (var layer = layers.Length - 1; layer >= largestLayer; --layer) { if (layers[layer].id == m_ApplyToSortingLayers[i]) { largestIndex = layers[layer].value; largestLayer = layer; } } } return largestIndex; } internal Bounds UpdateSpriteMesh() { if (m_LightCookieSprite == null && (m_Vertices.Length != 1 || m_Triangles.Length != 1)) { m_Vertices = new LightUtility.LightMeshVertex[1]; m_Triangles = new ushort[1]; } return LightUtility.GenerateSpriteMesh(this, m_LightCookieSprite, LightBatch.GetBatchColor()); } internal void UpdateBatchSlotIndex() { if (lightMesh && lightMesh.colors != null && lightMesh.colors.Length != 0) m_BatchSlotIndex = LightBatch.GetBatchSlotIndex(lightMesh.colors[0].b); } internal bool NeedsColorIndexBaking() { if (lightMesh && LightBatch.isBatchingSupported) { if (lightMesh.colors.Length != 0) return lightMesh.colors[0].b == 0; } return false; } internal Bounds UpdateProviderMesh() { SetProviderMesh(light2DProvider.GetMesh()); if (m_Mesh != null) return m_Mesh.bounds; else return kEmptyBounds; } internal void UpdateCookieSpriteTexture() { m_CookieSpriteTexture?.Release(); if (useCookieSprite) m_CookieSpriteTexture = RTHandles.Alloc(lightCookieSprite.texture); } internal void UpdateMesh(bool forceUpdate = false) { var lightTypeChanged = LightUtility.CheckForChange(lightType, ref m_PreviousLightType); if (lightType != LightType.Provider) { var shapePathHash = LightUtility.GetShapePathHash(shapePath); var fallOffSizeChanged = LightUtility.CheckForChange(m_ShapeLightFalloffSize, ref m_PreviousShapeLightFalloffSize); var parametricRadiusChanged = LightUtility.CheckForChange(m_ShapeLightParametricRadius, ref m_PreviousShapeLightParametricRadius); var parametricSidesChanged = LightUtility.CheckForChange(m_ShapeLightParametricSides, ref m_PreviousShapeLightParametricSides); var parametricAngleOffsetChanged = LightUtility.CheckForChange(m_ShapeLightParametricAngleOffset, ref m_PreviousShapeLightParametricAngleOffset); var spriteInstanceChanged = LightUtility.CheckForChange(lightCookieSpriteEntityId, ref m_PreviousLightCookieSprite); var shapePathHashChanged = LightUtility.CheckForChange(shapePathHash, ref m_PreviousShapePathHash); var hashChanged = fallOffSizeChanged || parametricRadiusChanged || parametricSidesChanged || parametricAngleOffsetChanged || spriteInstanceChanged || shapePathHashChanged || lightTypeChanged || NeedsColorIndexBaking(); // Mesh Rebuilding if (hashChanged || forceUpdate) { var batchChannelColor = LightBatch.GetBatchColor(); switch (lightType) { case LightType.Freeform: m_LocalBounds = LightUtility.GenerateShapeMesh(this, m_ShapePath, m_ShapeLightFalloffSize, batchChannelColor); break; case LightType.Parametric: m_LocalBounds = LightUtility.GenerateParametricMesh(this, m_ShapeLightParametricRadius, m_ShapeLightFalloffSize, m_ShapeLightParametricAngleOffset, m_ShapeLightParametricSides, batchChannelColor); break; case LightType.Sprite: m_LocalBounds = UpdateSpriteMesh(); break; case LightType.Point: m_LocalBounds = LightUtility.GenerateParametricMesh(this, 1.412135f, 0, 0, 4, batchChannelColor); break; } UpdateCookieSpriteTexture(); UpdateBatchSlotIndex(); } } else { if (light2DProvider != null) { if (m_Mesh != null) { SetProviderMesh(light2DProvider.GetMesh()); m_LocalBounds = m_Mesh.bounds; } } else { m_Mesh.Clear(); m_LocalBounds = kEmptyBounds; } } } internal void UpdateBoundingSphere() { if (isPointLight) { boundingSphere = new BoundingSphere(transform.position, m_PointLightOuterRadius); return; } var maxBound = transform.TransformPoint(Vector3.Max(m_LocalBounds.max, m_LocalBounds.max + (Vector3)m_ShapeLightFalloffOffset)); var minBound = transform.TransformPoint(Vector3.Min(m_LocalBounds.min, m_LocalBounds.min + (Vector3)m_ShapeLightFalloffOffset)); var center = 0.5f * (maxBound + minBound); var radius = Vector3.Magnitude(maxBound - center); boundingSphere = new BoundingSphere(center, radius); } internal bool IsLitLayer(int layer) { if (m_ApplyToSortingLayers == null) return false; for (var i = 0; i < m_ApplyToSortingLayers.Length; i++) if (m_ApplyToSortingLayers[i] == layer) return true; return false; } internal Matrix4x4 GetMatrix() { var matrix = transform.localToWorldMatrix; if (lightType == Light2D.LightType.Point) { var scale = new Vector3(pointLightOuterRadius, pointLightOuterRadius, pointLightOuterRadius); matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale); } return matrix; } private void Awake() { // Default target sorting layers to "All" if (m_ApplyToSortingLayers == null) { m_ApplyToSortingLayers = new int[SortingLayer.layers.Length]; for (int i = 0; i < m_ApplyToSortingLayers.Length; ++i) m_ApplyToSortingLayers[i] = SortingLayer.layers[i].id; } } void OnEnable() { m_PreviousLightCookieSprite = lightCookieSpriteEntityId; Light2DManager.RegisterLight(this); UpdateCookieSpriteTexture(); #if UNITY_EDITOR SortingLayer.onLayerAdded += OnSortingLayerAdded; SortingLayer.onLayerRemoved += OnSortingLayerRemoved; #endif } private void OnDestroy() { // Only the mesh we allocated ourselves is ours to destroy; a provider owns the one it returned. if (m_OwnsMesh) CoreUtils.Destroy(m_Mesh); m_Mesh = null; m_OwnsMesh = false; } private void OnDisable() { Light2DManager.DeregisterLight(this); m_CookieSpriteTexture?.Release(); #if UNITY_EDITOR SortingLayer.onLayerAdded -= OnSortingLayerAdded; SortingLayer.onLayerRemoved -= OnSortingLayerRemoved; #endif } private void LateUpdate() { if (lightType == LightType.Global) return; UpdateMesh(forceUpdate); UpdateBoundingSphere(); forceUpdate = false; } #if UNITY_EDITOR private void OnSortingLayerAdded(SortingLayer layer) { var newArray = new int[m_ApplyToSortingLayers.Length + 1]; for (int i = 0; i < m_ApplyToSortingLayers.Length; i++) { newArray[i] = m_ApplyToSortingLayers[i]; } newArray[m_ApplyToSortingLayers.Length] = layer.id; m_ApplyToSortingLayers = newArray; } private void OnSortingLayerRemoved(SortingLayer layer) { var tempList = new List(); foreach (var x in m_ApplyToSortingLayers) { if (x != layer.id && SortingLayer.IsValid(x)) tempList.Add(x); } m_ApplyToSortingLayers = tempList.ToArray(); } #endif /// /// OnBeforeSerialize implementation. /// public void OnBeforeSerialize() { m_ComponentVersion = k_CurrentComponentVersion; } /// /// OnAfterSerialize implementation. /// public void OnAfterDeserialize() { // Upgrade from no serialized version if (m_ComponentVersion == ComponentVersions.Version_Unserialized) { m_ShadowVolumeIntensityEnabled = m_ShadowVolumeIntensity > 0; m_ShadowsEnabled = m_ShadowIntensity > 0; m_LightVolumeEnabled = m_LightVolumeIntensity > 0; m_NormalMapQuality = !m_UseNormalMap ? NormalMapQuality.Disabled : m_NormalMapQuality; m_OverlapOperation = m_AlphaBlendOnOverlap ? OverlapOperation.AlphaBlend : m_OverlapOperation; m_ComponentVersion = ComponentVersions.Version_1; } if (m_ComponentVersion < ComponentVersions.Version_2) { m_ShadowSoftness = 0; } if (m_ComponentVersion < ComponentVersions.Version_3) { #if UNITY_EDITOR m_SelectionSources.selectedHashCode = (int)m_LightType; #endif } if (m_ComponentVersion < ComponentVersions.Version_4) { #pragma warning disable CS0618 if (m_LightType == LightType.Point && (object)m_DeprecatedPointLightCookieSprite != null) { m_LightCookieSprite = m_DeprecatedPointLightCookieSprite; } #pragma warning restore CS0618 } } } }