using System; using UnityEngine; using UnityEngine.Scripting.APIUpdating; using Unity.Collections; #if UNITY_EDITOR using UnityEditor; using UnityEditor.EditorTools; #endif namespace UnityEngine.Rendering.Universal { /// /// Class ShadowCaster2D contains properties used for shadow casting /// [CoreRPHelpURL("2DShadows", "com.unity.render-pipelines.universal")] [ExecuteInEditMode] [DisallowMultipleComponent] [Icon("UnityEngine/UI/Shadow Icon")] [AddComponentMenu("Rendering/2D/Shadow Caster 2D")] [MovedFrom(false, "UnityEngine.Experimental.Rendering.Universal", "com.unity.render-pipelines.universal")] public class ShadowCaster2D : ShadowCasterGroup2D, ISerializationCallbackReceiver { internal enum ComponentVersions { Version_Unserialized = 0, Version_1 = 1, Version_2 = 2, Version_3 = 3, Version_4 = 4, Version_5 = 5, Version_6 = 6, } const ComponentVersions k_CurrentComponentVersion = ComponentVersions.Version_6; [SerializeField] ComponentVersions m_ComponentVersion = ComponentVersions.Version_Unserialized; internal enum ShadowCastingSources { None, ShapeEditor, ShapeProvider } /// /// Options for what type of shadows are cast. /// public enum ShadowCastingOptions { /// /// Renders a shadows only for the sprite. /// SelfShadow, /// /// Renders a shadows only a cast shadow. /// CastShadow, /// /// Renders both a shadows for the sprite and a cast shadow. /// CastAndSelfShadow, /// /// Renders a sprite without shadow casting correctly on top of other shadow casting sprites /// NoShadow } internal enum EdgeProcessing { None = ShadowMesh2D.EdgeProcessing.None, Clipping = ShadowMesh2D.EdgeProcessing.Clipping, } [SerializeField] bool m_HasRenderer = false; [SerializeField] bool m_UseRendererSilhouette = true; [SerializeField] bool m_CastsShadows = true; [SerializeField] bool m_SelfShadows = false; [Range(0, 1)] [SerializeField] float m_AlphaCutoff = 0.1f; [SerializeField] int[] m_ApplyToSortingLayers = null; [SerializeField] Vector3[] m_ShapePath = null; [SerializeField] int m_ShapePathHash = 0; [SerializeField] int m_InstanceId; [SerializeField] Component m_ShadowShape2DComponent; [SerializeReference] ShadowShape2DProvider m_ShadowShape2DProvider; [SerializeField] ShadowCastingSources m_ShadowCastingSource = (ShadowCastingSources)(-1); [SerializeReference] internal ShadowMesh2D m_ShadowMesh; [SerializeField] ShadowCastingOptions m_CastingOption = ShadowCastingOptions.CastShadow; [SerializeField] internal float m_PreviousTrimEdge = 0; [SerializeField] internal int m_PreviousEdgeProcessing; [SerializeField] internal int m_PreviousShadowCastingSource; [SerializeField] internal Component m_PreviousShadowShape2DSource = null; [SerializeField] internal int m_PreviousShadowShape2DProviderHash; #if UNITY_EDITOR [SerializeReference] internal Shadow2DProviderSources m_SelectionSources = new Shadow2DProviderSources(); #endif internal ShadowCasterGroup2D m_ShadowCasterGroup = null; internal ShadowCasterGroup2D m_PreviousShadowCasterGroup = null; internal bool isTransformable { get { if (m_ShadowMesh != null) return m_ShadowMesh.isTransformable; else return true; // This is the old default } } internal bool m_ForceShadowMeshRebuild; internal EdgeProcessing edgeProcessing { get { return (EdgeProcessing)m_ShadowMesh.edgeProcessing; } set { m_ShadowMesh.edgeProcessing = (ShadowMesh2D.EdgeProcessing)value; } } /// /// The mesh to draw with. /// public Mesh mesh => m_ShadowMesh.mesh; /// /// The bounding sphere for the shadow caster /// public BoundingSphere boundingSphere => m_ShadowMesh.boundingSphere; /// /// The amount the shadow's edge is trimed /// public float trimEdge { get { return m_ShadowMesh.trimEdge; } set { m_ShadowMesh.trimEdge = value; } } /// /// The sets the renderer's shadow cutoff /// public float alphaCutoff { get { return m_AlphaCutoff; } set { m_AlphaCutoff = value; } } /// /// The path for the shape. /// public Vector3[] shapePath => m_ShapePath; internal int shapePathHash { get { return m_ShapePathHash; } set { m_ShapePathHash = value; } } internal ShadowCastingSources shadowCastingSource { get { return m_ShadowCastingSource; } set { m_ShadowCastingSource = value; } } // Make this public if possible... internal Component shadowShape2DComponent { get { return m_ShadowShape2DComponent; } set { m_ShadowShape2DComponent = value; } } internal ShadowShape2DProvider shadowShape2DProvider { get { return m_ShadowShape2DProvider; } set { m_ShadowShape2DProvider = value; } } int m_PreviousShadowGroup = 0; bool m_PreviousCastsShadows = true; int m_PreviousPathHash = 0; int m_SpriteMaterialCount; internal Vector3 m_CachedPosition; internal Vector3 m_CachedLossyScale; internal Quaternion m_CachedRotation; internal Matrix4x4 m_CachedShadowMatrix; internal Matrix4x4 m_CachedInverseShadowMatrix; internal Matrix4x4 m_CachedLocalToWorldMatrix; internal int spriteMaterialCount => m_SpriteMaterialCount; internal override void CacheValues() { m_CachedPosition = transform.position; m_CachedLossyScale = transform.lossyScale; m_CachedRotation = transform.rotation; bool flipX, flipY; m_ShadowMesh.GetFlip(out flipX, out flipY); Vector3 scale = new Vector3(flipX ? -1 : 1, flipY ? -1 : 1, 1); m_CachedShadowMatrix = Matrix4x4.TRS(m_CachedPosition, m_CachedRotation, scale); m_CachedInverseShadowMatrix = m_CachedShadowMatrix.inverse; m_CachedLocalToWorldMatrix = transform.localToWorldMatrix; } /// /// Sets the type of shadow cast. /// public ShadowCastingOptions castingOption { set { m_CastingOption = value; } get { return m_CastingOption; } } /// /// This property is obsolete and no longer has any effect. Its functionality has been removed because it is no longer required. /// To achieve similar behavior, add a ShadowCaster2D component to an empty parent GameObject instead. /// [Obsolete("useRendererSilhouette is obsolete and no longer has any effect. To achieve similar behavior, add a ShadowCaster2D component to an empty parent GameObject. #from(2023.1)")] public bool useRendererSilhouette { set { m_UseRendererSilhouette = value; } get { return m_UseRendererSilhouette && m_HasRenderer; } } /// /// If true, the shadow casting shape is included as part of the shadow. If false, the shadow casting shape is excluded from the shadow. /// public bool selfShadows { set { if (value) { if (castingOption == ShadowCastingOptions.CastShadow) castingOption = ShadowCastingOptions.CastAndSelfShadow; else if (castingOption == ShadowCastingOptions.NoShadow) castingOption = ShadowCastingOptions.SelfShadow; } else { if (castingOption == ShadowCastingOptions.CastAndSelfShadow ) castingOption = ShadowCastingOptions.CastShadow; else if(castingOption == ShadowCastingOptions.SelfShadow) castingOption = ShadowCastingOptions.NoShadow; } } get { return castingOption == ShadowCastingOptions.CastAndSelfShadow || castingOption == ShadowCastingOptions.SelfShadow; } } /// /// Specifies if shadows will be cast. /// /// public bool castsShadows { set { if(value) { if (castingOption == ShadowCastingOptions.SelfShadow) castingOption = ShadowCastingOptions.CastAndSelfShadow; else if (castingOption == ShadowCastingOptions.NoShadow) castingOption = ShadowCastingOptions.CastShadow; } else { if (castingOption == ShadowCastingOptions.CastAndSelfShadow) castingOption = ShadowCastingOptions.SelfShadow; else if (castingOption == ShadowCastingOptions.CastShadow) castingOption = ShadowCastingOptions.NoShadow; } } get { return castingOption == ShadowCastingOptions.CastShadow || castingOption == ShadowCastingOptions.CastAndSelfShadow; } } static int[] SetDefaultSortingLayers() { int layerCount = SortingLayer.layers.Length; int[] allLayers = new int[layerCount]; for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) { allLayers[layerIndex] = SortingLayer.layers[layerIndex].id; } return allLayers; } internal bool IsLit(Light2D light) { // Global lights do not cast shadows if (light.lightType == Light2D.LightType.Global) return false; // Oddly adding and subtracting vectors is expensive here because of the new structures created... Vector3 deltaPos; deltaPos.x = light.boundingSphere.position.x - boundingSphere.position.x; deltaPos.y = light.boundingSphere.position.y - boundingSphere.position.y; deltaPos.z = light.boundingSphere.position.z - boundingSphere.position.z; float distanceSq = Vector3.SqrMagnitude(deltaPos); float radiiLength = light.boundingSphere.radius + boundingSphere.radius; return distanceSq <= (radiiLength * radiiLength); } internal bool IsShadowedLayer(int layer) { return m_ApplyToSortingLayers != null ? Array.IndexOf(m_ApplyToSortingLayers, layer) >= 0 : false; } void SetShadowShape(ShadowMesh2D shadowMesh) { m_ForceShadowMeshRebuild = false; if (m_ShadowCastingSource == ShadowCastingSources.ShapeEditor) { NativeArray nativePath = new NativeArray(m_ShapePath, Allocator.Temp); NativeArray nativeIndices = new NativeArray(2 * m_ShapePath.Length, Allocator.Temp); int lastIndex = m_ShapePath.Length - 1; for (int i = 0; i < m_ShapePath.Length; i++) { int startingIndex = i << 1; nativeIndices[startingIndex] = lastIndex; nativeIndices[startingIndex + 1] = i; lastIndex = i; } shadowMesh.SetShapeWithLines(nativePath, nativeIndices, false); nativePath.Dispose(); nativeIndices.Dispose(); } if (m_ShadowCastingSource == ShadowCastingSources.ShapeProvider) { ShapeProviderUtility.PersistantDataCreated(m_ShadowShape2DProvider, m_ShadowShape2DComponent, shadowMesh); } } private void Awake() { if (m_ShadowCastingSource < 0) { #if UNITY_EDITOR ShapeProviderUtility.TryGetDefaultShadowShapeProviderSource(gameObject, out var component, out var provider); if (component != null && provider != null && (shapePath == null || shapePath.Length == 0)) { m_ShadowShape2DComponent = component; m_ShadowShape2DProvider = provider; m_ShadowCastingSource = ShadowCastingSources.ShapeProvider; } else { m_ShadowCastingSource = ShadowCastingSources.ShapeEditor; } #else m_ShadowCastingSource = ShadowCastingSources.ShapeEditor; #endif } Vector3 inverseScale = Vector3.zero; Vector3 relOffset = transform.position; if (transform.lossyScale.x != 0 && transform.lossyScale.y != 0) { inverseScale = new Vector3(1 / transform.lossyScale.x, 1 / transform.lossyScale.y); relOffset = new Vector3(inverseScale.x * -transform.position.x, inverseScale.y * -transform.position.y); } if (m_ApplyToSortingLayers == null) m_ApplyToSortingLayers = SetDefaultSortingLayers(); Bounds bounds = new Bounds(transform.position, Vector3.one); Renderer renderer = GetComponent(); if (renderer != null) { bounds = renderer.bounds; m_SpriteMaterialCount = renderer.sharedMaterials.Length; } if (m_ShapePath == null || m_ShapePath.Length == 0) { m_ShapePath = new Vector3[] { relOffset + new Vector3(inverseScale.x * bounds.min.x, inverseScale.y * bounds.min.y), relOffset + new Vector3(inverseScale.x * bounds.min.x, inverseScale.y * bounds.max.y), relOffset + new Vector3(inverseScale.x * bounds.max.x, inverseScale.y * bounds.max.y), relOffset + new Vector3(inverseScale.x * bounds.max.x, inverseScale.y * bounds.min.y), }; } if (m_ShadowMesh == null) { m_ShadowMesh = new ShadowMesh2D(); } #if USING_PHYSICS2D_MODULE else { Collider2D collider = GetComponent(); if (collider != null) bounds = collider.bounds; } #endif if (m_ShadowMesh.trimEdge == ShadowMesh2D.k_TrimEdgeUninitialized) SetShadowShape(m_ShadowMesh); } internal void EnsureMeshInitialized() { if (m_ShadowMesh == null) m_ShadowMesh = new ShadowMesh2D(); if (m_ShadowMesh.trimEdge == ShadowMesh2D.k_TrimEdgeUninitialized) SetShadowShape(m_ShadowMesh); } /// /// This function is called when the object becomes enabled and active. /// protected void OnEnable() { if (m_ShadowShape2DProvider != null && m_ShadowShape2DComponent != null) m_ShadowShape2DProvider.Enabled(m_ShadowShape2DComponent, m_ShadowMesh); m_ShadowCasterGroup = null; #if UNITY_EDITOR SortingLayer.onLayerAdded += OnSortingLayerAdded; SortingLayer.onLayerRemoved += OnSortingLayerRemoved; #endif } /// /// This function is called when the behaviour becomes disabled. /// protected void OnDisable() { ShadowCasterGroup2DManager.RemoveFromShadowCasterGroup(this, m_ShadowCasterGroup); if (m_ShadowShape2DProvider != null && m_ShadowShape2DComponent != null) m_ShadowShape2DProvider.Disabled(m_ShadowShape2DComponent, m_ShadowMesh); #if UNITY_EDITOR SortingLayer.onLayerAdded -= OnSortingLayerAdded; SortingLayer.onLayerRemoved -= OnSortingLayerRemoved; #endif } /// /// Update is called every frame, if the MonoBehaviour is enabled. /// public void Update() { Renderer renderer; m_HasRenderer = TryGetComponent(out renderer); bool rebuildMesh = LightUtility.CheckForChange((int)m_ShadowCastingSource, ref m_PreviousShadowCastingSource); rebuildMesh |= LightUtility.CheckForChange((int)edgeProcessing, ref m_PreviousEdgeProcessing); rebuildMesh |= edgeProcessing != EdgeProcessing.None && LightUtility.CheckForChange(trimEdge, ref m_PreviousTrimEdge); int providerHash = m_ShadowShape2DProvider != null ? m_ShadowShape2DProvider.TypeIdentifierHash : 0; rebuildMesh |= LightUtility.CheckForChange(providerHash, ref m_PreviousShadowShape2DProviderHash); rebuildMesh |= m_ForceShadowMeshRebuild; if (m_ShadowCastingSource == ShadowCastingSources.ShapeEditor) { rebuildMesh |= LightUtility.CheckForChange(m_ShapePathHash, ref m_PreviousPathHash); if (rebuildMesh) { SetShadowShape(m_ShadowMesh); } } else { if ((rebuildMesh || LightUtility.CheckForChange(m_ShadowShape2DComponent, ref m_PreviousShadowShape2DSource)) && m_ShadowShape2DComponent != null) { SetShadowShape(m_ShadowMesh); } } m_PreviousShadowCasterGroup = m_ShadowCasterGroup; bool addedToNewGroup = ShadowCasterGroup2DManager.AddToShadowCasterGroup(this, ref m_ShadowCasterGroup, ref m_Priority); if (addedToNewGroup && m_ShadowCasterGroup != null) { if (m_PreviousShadowCasterGroup == this) ShadowCasterGroup2DManager.RemoveGroup(this); ShadowCasterGroup2DManager.RemoveFromShadowCasterGroup(this, m_PreviousShadowCasterGroup); if (m_ShadowCasterGroup == this) ShadowCasterGroup2DManager.AddGroup(this); } if (LightUtility.CheckForChange(m_ShadowGroup, ref m_PreviousShadowGroup)) { ShadowCasterGroup2DManager.RemoveGroup(this); ShadowCasterGroup2DManager.AddGroup(this); } if (LightUtility.CheckForChange(m_CastsShadows, ref m_PreviousCastsShadows)) { ShadowCasterGroup2DManager.AddGroup(this); } if(m_ShadowMesh != null) m_ShadowMesh.UpdateBoundingSphere(transform); } #if UNITY_EDITOR internal void DrawPreviewOutline(Matrix4x4 previewMat, float trimionDistance) { Vector3[] vertices = mesh.vertices; int[] triangles = mesh.triangles; Vector4[] tangents = mesh.tangents; Handles.color = Color.white; for (int i = 0; i < triangles.Length; i += 3) { int v0 = triangles[i]; int v1 = triangles[i + 1]; int v2 = triangles[i + 2]; Vector3 pt0 = vertices[v0]; Vector3 pt1 = vertices[v1]; Vector3 pt2 = vertices[v2]; Vector4 tan0 = tangents[v0]; Vector4 tan1 = tangents[v1]; Vector4 tan2 = tangents[v2]; Vector3 trimPt0 = new Vector3(pt0.x + trimionDistance * tan0.x, pt0.y + trimionDistance * tan0.y, 0); Vector3 trimPt1 = new Vector3(pt1.x + trimionDistance * tan1.x, pt1.y + trimionDistance * tan1.y, 0); Vector3 trimPt2 = new Vector3(pt2.x + trimionDistance * tan2.x, pt2.y + trimionDistance * tan2.y, 0); trimPt0 = previewMat.MultiplyPoint(trimPt0); trimPt1 = previewMat.MultiplyPoint(trimPt1); trimPt2 = previewMat.MultiplyPoint(trimPt2); if (pt0.z == 0 && pt1.z == 0) Handles.DrawAAPolyLine(4, new Vector3[] { trimPt0, trimPt1 }); if (pt1.z == 0 && pt2.z == 0) Handles.DrawAAPolyLine(4, new Vector3[] { trimPt1, trimPt2 }); if (pt2.z == 0 && pt0.z == 0) Handles.DrawAAPolyLine(4, new Vector3[] { trimPt2, trimPt0 }); } } internal void DrawPreviewOutline() { if (m_ShadowMesh != null && mesh != null && m_ShadowCastingSource != ShadowCastingSources.None && enabled) { Matrix4x4 outlineMat = Matrix4x4.identity; if (isTransformable) { bool flipX, flipY; m_ShadowMesh.GetFlip(out flipX, out flipY); Vector3 scale = new Vector3(transform.lossyScale.x * (flipX ? -1 : 1), transform.lossyScale.y * (flipY ? -1 : 1), 1); outlineMat = Matrix4x4.TRS(transform.position, transform.rotation, scale); } if (edgeProcessing == EdgeProcessing.None) DrawPreviewOutline(outlineMat, trimEdge); else DrawPreviewOutline(outlineMat, 0); } } void Reset() { ShadowCasterGroup2DManager.RemoveFromShadowCasterGroup(this, m_ShadowCasterGroup); m_ShadowCasterGroup = null; m_PreviousShadowCasterGroup = null; m_PreviousShadowCastingSource = -1; m_PreviousShadowShape2DSource = null; m_PreviousShadowShape2DProviderHash = 0; m_PreviousTrimEdge = 0; m_PreviousEdgeProcessing = -1; m_ForceShadowMeshRebuild = true; m_HasRenderer = false; m_UseRendererSilhouette = true; m_CastsShadows = true; m_SelfShadows = false; m_ApplyToSortingLayers = null; m_ShapePath = null; m_ShapePathHash = 0; m_ShadowShape2DComponent = null; m_ShadowShape2DProvider = null; m_ShadowCastingSource = (ShadowCastingSources)(-1); m_ShadowMesh = null; m_CastingOption = ShadowCastingOptions.CastShadow; ToolManager.RestorePreviousTool(); // This is needed in case you have the shape editor active Awake(); OnEnable(); } #endif #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 System.Collections.Generic.List(); foreach (var x in m_ApplyToSortingLayers) { if (x != layer.id && SortingLayer.IsValid(x)) tempList.Add(x); } m_ApplyToSortingLayers = tempList.ToArray(); } #endif /// public void OnBeforeSerialize() { m_ComponentVersion = k_CurrentComponentVersion; if (m_ShadowMesh != null) m_ShadowMesh.OnBeforeSerialize(); } /// public void OnAfterDeserialize() { if (m_ComponentVersion < ComponentVersions.Version_2) { // ---------------------------------------------------- // m_SelfShadows | m_CastsShadows | m_CastingOption // ---------------------------------------------------- // 0 | 0 | DontCast // 0 | 1 | Renderer Only // 1 | 0 | Cast Only // 1 | 1 | CastAndSelfShadow // ---------------------------------------------------- if (m_SelfShadows && m_CastsShadows) m_CastingOption = ShadowCastingOptions.CastAndSelfShadow; else if (m_SelfShadows) m_CastingOption = ShadowCastingOptions.SelfShadow; else if (m_CastsShadows) m_CastingOption = ShadowCastingOptions.CastShadow; else m_CastingOption = ShadowCastingOptions.NoShadow; } if (m_ComponentVersion < ComponentVersions.Version_3) { m_ShadowMesh = null; m_ForceShadowMeshRebuild = true; } if (m_ComponentVersion < ComponentVersions.Version_6) { #if UNITY_EDITOR if (m_ShadowCastingSource == ShadowCastingSources.ShapeProvider) m_SelectionSources.selectedHashCode = LightUtility.ProviderToHash(shadowShape2DProvider, shadowShape2DComponent); else m_SelectionSources.selectedHashCode = (int)m_ShadowCastingSource; #endif } if(m_ShadowMesh != null) m_ShadowMesh.OnAfterDeserialize(); } } }