using System; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace BadDog.Rendering.AreaLight { /// /// Attach this component to a Light to enable area light calculations. /// [ExecuteAlways] [RequireComponent(typeof(Light))] [DisallowMultipleComponent] [AddComponentMenu("BadDog/Rendering/BGAreaLight")] public class BGAreaLight : MonoBehaviour { private Light m_Light; [SerializeField] [Tooltip("Enable shadow casting for this area light.")] private bool m_CastShadows = true; [SerializeField] [Tooltip("Enable custom shadow settings for this area light.")] private bool m_UseCustomShadow = false; [SerializeField] [Range(0, 8192)] [Tooltip("Custom shadow map resolution for this area light. Set to 0 to use pipeline defaults.")] private int m_CustomShadowResolution = 0; [SerializeField] [Range(AreaShadowUtils.k_MinAreaLightShadowCone, AreaShadowUtils.k_MaxAreaLightShadowCone)] [Tooltip("Aperture of the cone used for shadowing the area light.")] private float m_ShadowCone = AreaShadowUtils.k_DefaultAreaLightShadowCone; [SerializeField] [Range(0f, 1f)] [Tooltip("Shadow strength for this area light. Applied to Light.shadowStrength.")] private float m_ShadowStrength = 1f; [SerializeField] [Range(0f, 10f)] [Tooltip("Normal bias used for area light shadowing.")] private float m_ShadowNormalBias = 0.05f; [SerializeField] [Range(0f, 10f)] [Tooltip("Depth bias used for area light shadowing. Acts like Light.shadowBias for rectangle lights.")] private float m_ShadowDepthBias = 0.05f; [SerializeField] [Range(AreaShadowUtils.k_MinShadowNearPlane, AreaShadowUtils.k_MaxShadowNearPlane)] [Tooltip("Shadow near plane for area light. Controls the starting distance of the shadow frustum.")] private float m_ShadowNearPlane = 0.1f; [SerializeField] [Tooltip("Rendering Layer Mask used for area light lighting/shadow filtering.")] private RenderingLayerMask m_RenderingLayerMask = (RenderingLayerMask)1; [SerializeField, HideInInspector] private bool m_PreBakeEnabled; [SerializeField, HideInInspector] private Vector2 m_CachedAreaSize = new Vector2(1.0f, 0.5f); private void Awake() { OnValidate(); } private void OnEnable() { OnValidate(); BGAreaLightManager.RegisterAreaLight(this); #if UNITY_EDITOR UnityEditor.Lightmapping.bakeStarted += OnBakeStarted; UnityEditor.Lightmapping.bakeCompleted += OnBakeCompleted; #endif } private void OnDisable() { BGAreaLightManager.UnRegisterAreaLight(this); #if UNITY_EDITOR UnityEditor.Lightmapping.bakeStarted -= OnBakeStarted; UnityEditor.Lightmapping.bakeCompleted -= OnBakeCompleted; #endif } private void OnDestroy() { BGAreaLightManager.UnRegisterAreaLight(this); } private void OnValidate() { EnsureLight(); EnsureRectangleType(); ForceRealtimeMode(); SyncCastShadowsToLight(); SetShadowCone(m_ShadowCone); SetShadowDepthBias(m_ShadowDepthBias); SetShadowNormalBias(m_ShadowNormalBias); SetShadowNearPlane(m_ShadowNearPlane); ApplyShadowStrength(); SyncRenderingLayerMaskToLight(); } private void Update() { OnValidate(); } private void ForceRealtimeMode() { #if UNITY_EDITOR if (m_Light.lightmapBakeType != LightmapBakeType.Realtime) { m_Light.lightmapBakeType = LightmapBakeType.Realtime; UnityEditor.EditorUtility.SetDirty(m_Light); } #endif } private void EnsureLight() { if (m_Light == null) { m_Light = GetComponent(); } } private void EnsureRectangleType() { if (m_Light.type != LightType.Rectangle) { m_Light.type = LightType.Rectangle; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(m_Light); #endif } #if UNITY_EDITOR if (m_Light.areaSize == Vector2.zero) { m_Light.areaSize = new Vector2(1.0f, 0.5f); UnityEditor.EditorUtility.SetDirty(m_Light); } #endif // Keep the runtime cache in sync with the Light's current area size. m_CachedAreaSize = m_Light.areaSize; } private void ApplyShadowStrength() { float clamped = Mathf.Clamp01(m_ShadowStrength); m_Light.shadowStrength = clamped; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(m_Light); #endif } private void SyncCastShadowsToLight() { if (m_Light == null) { return; } LightShadows targetShadows = m_CastShadows ? LightShadows.Soft : LightShadows.None; if (m_Light.shadows != targetShadows) { m_Light.shadows = targetShadows; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(m_Light); #endif } } private void SyncRenderingLayerMaskToLight() { if (m_Light == null) { return; } if (m_Light.renderingLayerMask != m_RenderingLayerMask) { m_Light.renderingLayerMask = m_RenderingLayerMask; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(m_Light); #endif } } public Light GetLight() { EnsureLight(); return m_Light; } public int GetCustomShadowResolution() { return m_CustomShadowResolution; } public Vector2 GetSize() { return m_CachedAreaSize; } public float GetShadowCone() { return Mathf.Clamp(m_ShadowCone, AreaShadowUtils.k_MinAreaLightShadowCone, AreaShadowUtils.k_MaxAreaLightShadowCone); } public float GetShadowDepthBias() { return Mathf.Max(0f, m_ShadowDepthBias); } public void SetShadowDepthBias(float value) { float clamped = Mathf.Max(0f, value); m_ShadowDepthBias = clamped; } public float GetShadowNormalBias() { return Mathf.Max(0f, m_ShadowNormalBias); } public void SetShadowNormalBias(float value) { float clamped = Mathf.Max(0f, value); m_ShadowNormalBias = clamped; } public float GetShadowNearPlane() { return Mathf.Clamp(m_ShadowNearPlane, AreaShadowUtils.k_MinShadowNearPlane, AreaShadowUtils.k_MaxShadowNearPlane); } public void SetShadowNearPlane(float value) { float clamped = Mathf.Clamp(value, AreaShadowUtils.k_MinShadowNearPlane, AreaShadowUtils.k_MaxShadowNearPlane); m_ShadowNearPlane = clamped; } public void SetShadowCone(float value) { float clamped = Mathf.Clamp(value, AreaShadowUtils.k_MinAreaLightShadowCone, AreaShadowUtils.k_MaxAreaLightShadowCone); m_ShadowCone = clamped; } public bool GetIsRectLight() { Vector2 size = GetSize(); return size.y > 0.001f; } public float GetRangeAttenuationScale() { if (m_Light == null) { return 1.0f; } float range = m_Light.range; if (range <= 0f) { return 1.0f; } return 1.0f / (range * range); } public float GetRangeAttenuationBias() { return 1.0f; } #if UNITY_EDITOR private void OnBakeStarted() { EnsureLight(); if (m_Light == null) { return; } m_PreBakeEnabled = m_Light.enabled; m_Light.enabled = false; } private void OnBakeCompleted() { EnsureLight(); if (m_Light == null) { return; } m_Light.enabled = m_PreBakeEnabled; m_Light.lightmapBakeType = LightmapBakeType.Realtime; UnityEditor.EditorUtility.SetDirty(m_Light); } #endif } }