using System; using System.Collections.Generic; using UnityEngine.LowLevel; using UnityEngine.PlayerLoop; using UnityEngine.Assertions; using UnityEngine.Serialization; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Rendering.HighDefinition { // This structure contains all the old values for every recordable fields from the HD light editor // so we can force timeline to record changes on other fields from the LateUpdate function (editor only) struct TimelineWorkaround { public float oldSpotAngle; public Color oldLightColor; public Vector3 oldLossyScale; public bool oldDisplayAreaLightEmissiveMesh; public float oldLightColorTemperature; public bool lightEnabled; } //@TODO: We should continuously move these values // into the engine when we can see them being generally useful /// /// HDRP Additional light data component. It contains the light API and fields used by HDRP. /// [HDRPHelpURLAttribute("Light-Component")] [AddComponentMenu("")] // Hide in menu [DisallowMultipleComponent] [RequireComponent(typeof(Light))] [ExecuteAlways] public partial class HDAdditionalLightData : MonoBehaviour, ISerializationCallbackReceiver, IAdditionalData { internal const float k_MinLightSize = 0.01f; // Provide a small size of 1cm for line light internal static class ScalableSettings { public static IntScalableSetting ShadowResolutionArea(HDRenderPipelineAsset hdrp) => hdrp.currentPlatformRenderPipelineSettings.hdShadowInitParams.shadowResolutionArea; public static IntScalableSetting ShadowResolutionPunctual(HDRenderPipelineAsset hdrp) => hdrp.currentPlatformRenderPipelineSettings.hdShadowInitParams.shadowResolutionPunctual; public static IntScalableSetting ShadowResolutionDirectional(HDRenderPipelineAsset hdrp) => hdrp.currentPlatformRenderPipelineSettings.hdShadowInitParams.shadowResolutionDirectional; public static BoolScalableSetting UseContactShadow(HDRenderPipelineAsset hdrp) => hdrp.currentPlatformRenderPipelineSettings.lightSettings.useContactShadow; } /// /// Light source used to shade the celestial body. /// public enum CelestialBodyShadingSource { /// /// The celestial body will emit light. /// Emission = 1, /// /// The celestial body will reflect light from a directional light in the scene. /// ReflectSunLight = 0, /// /// The celestial body will be illuminated by an artifical light source. /// Manual = 2, } /// /// The default intensity value for directional lights in Lux /// public const float k_DefaultDirectionalLightIntensity = Mathf.PI; // In lux /// /// The default intensity value for punctual lights in Lumen /// public const float k_DefaultPunctualLightIntensity = 600.0f; // Light default to 600 lumen, i.e ~48 candela /// /// The default intensity value for area lights in Lumen /// public const float k_DefaultAreaLightIntensity = 200.0f; // Light default to 200 lumen to better match point light /// /// Minimum value for the spot light angle /// public const float k_MinSpotAngle = 1.0f; /// /// Maximum value for the spot light angle /// public const float k_MaxSpotAngle = 179.0f; /// /// Minimum aspect ratio for pyramid spot lights /// public const float k_MinAspectRatio = 0.05f; /// /// Maximum aspect ratio for pyramid spot lights /// public const float k_MaxAspectRatio = 20.0f; /// /// Minimum shadow map view bias scale /// public const float k_MinViewBiasScale = 0.0f; /// /// Maximum shadow map view bias scale /// public const float k_MaxViewBiasScale = 15.0f; /// /// Minimum area light size /// public const float k_MinAreaWidth = 0.01f; // Provide a small size of 1cm for line light /// /// Default shadow resolution /// public const int k_DefaultShadowResolution = 512; // EVSM limits internal const float k_MinEvsmExponent = 5.0f; internal const float k_MaxEvsmExponent = 42.0f; internal const float k_MinEvsmLightLeakBias = 0.0f; internal const float k_MaxEvsmLightLeakBias = 1.0f; internal const float k_MinEvsmVarianceBias = 0.0f; internal const float k_MaxEvsmVarianceBias = 0.001f; internal const int k_MinEvsmBlurPasses = 0; internal const int k_MaxEvsmBlurPasses = 8; internal const float k_MinSpotInnerPercent = 0.0f; internal const float k_MaxSpotInnerPercent = 100.0f; internal const float k_MinAreaLightShadowCone = 10.0f; internal const float k_MaxAreaLightShadowCone = 179.0f; /// List of the lights that overlaps when the OverlapLight scene view mode is enabled internal static HashSet s_overlappingHDLights = new HashSet(); #region HDLight Properties API [ExcludeCopy] internal HDLightRenderEntity lightEntity = HDLightRenderEntity.Invalid; [Range(k_MinSpotInnerPercent, k_MaxSpotInnerPercent)] [SerializeField] float m_InnerSpotPercent; // To display this field in the UI this need to be public /// /// Get/Set the inner spot radius in percent. /// public float innerSpotPercent { get => m_InnerSpotPercent; set { if (m_InnerSpotPercent == value) return; m_InnerSpotPercent = Mathf.Clamp(value, k_MinSpotInnerPercent, k_MaxSpotInnerPercent); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).innerSpotPercent = m_InnerSpotPercent; } } /// /// Get the inner spot radius between 0 and 1. /// public float innerSpotPercent01 => innerSpotPercent / 100f; [Range(k_MinSpotInnerPercent, k_MaxSpotInnerPercent)] [SerializeField] float m_SpotIESCutoffPercent = 100.0f; // To display this field in the UI this need to be public /// /// Get/Set the spot ies cutoff. /// public float spotIESCutoffPercent { get => m_SpotIESCutoffPercent; set { if (m_SpotIESCutoffPercent == value) return; m_SpotIESCutoffPercent = Mathf.Clamp(value, k_MinSpotInnerPercent, k_MaxSpotInnerPercent); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).spotIESCutoffPercent = m_SpotIESCutoffPercent; } } /// /// Get the inner spot radius between 0 and 1. /// public float spotIESCutoffPercent01 => spotIESCutoffPercent / 100f; [Range(0.0f, 16.0f)] [SerializeField, FormerlySerializedAs("lightDimmer")] float m_LightDimmer = 1.0f; /// /// Get/Set the light dimmer / multiplier, between 0 and 16. /// public float lightDimmer { get => m_LightDimmer; set { if (m_LightDimmer == value) return; m_LightDimmer = Mathf.Clamp(value, 0.0f, 16.0f); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).lightDimmer = m_LightDimmer; } } [Range(0.0f, 16.0f), SerializeField, FormerlySerializedAs("volumetricDimmer")] float m_VolumetricDimmer = 1.0f; /// /// Get/Set the light dimmer / multiplier on volumetric effects, between 0 and 16. /// public float volumetricDimmer { get => useVolumetric ? m_VolumetricDimmer : 0.0f; set { if (m_VolumetricDimmer == value) return; m_VolumetricDimmer = Mathf.Clamp(value, 0.0f, 16.0f); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).volumetricDimmer = m_VolumetricDimmer; } } // Not used for directional lights. [SerializeField, FormerlySerializedAs("fadeDistance")] float m_FadeDistance = 10000.0f; /// /// Get/Set the light fade distance. /// public float fadeDistance { get => m_FadeDistance; set { if (m_FadeDistance == value) return; m_FadeDistance = Mathf.Clamp(value, 0, float.MaxValue); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).fadeDistance = m_FadeDistance; } } // Not used for directional lights. [SerializeField] float m_VolumetricFadeDistance = 10000.0f; /// /// Get/Set the light fade distance for volumetrics. /// public float volumetricFadeDistance { get => m_VolumetricFadeDistance; set { if (m_VolumetricFadeDistance == value) return; m_VolumetricFadeDistance = Mathf.Clamp(value, 0, float.MaxValue); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).volumetricFadeDistance = m_VolumetricFadeDistance; } } [SerializeField, FormerlySerializedAs("affectDiffuse")] bool m_AffectDiffuse = true; /// /// Controls whether the light affects the diffuse or not /// public bool affectDiffuse { get => m_AffectDiffuse; set { if (m_AffectDiffuse == value) return; m_AffectDiffuse = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).affectDiffuse = m_AffectDiffuse; } } [SerializeField, FormerlySerializedAs("affectSpecular")] bool m_AffectSpecular = true; /// /// Controls whether the light affects the specular or not /// public bool affectSpecular { get => m_AffectSpecular; set { if (m_AffectSpecular == value) return; m_AffectSpecular = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).affectSpecular = m_AffectSpecular; } } // This property work only with shadow mask and allow to say we don't render any lightMapped object in the shadow map [SerializeField, FormerlySerializedAs("nonLightmappedOnly")] bool m_NonLightmappedOnly = false; /// /// Only used when the shadow masks are enabled, control if the we use ShadowMask or DistanceShadowmask for this light. /// public bool nonLightmappedOnly { get => m_NonLightmappedOnly; set { if (m_NonLightmappedOnly == value) return; m_NonLightmappedOnly = value; legacyLight.lightShadowCasterMode = value ? LightShadowCasterMode.NonLightmappedOnly : LightShadowCasterMode.Everything; // We need to update the ray traced shadow flag as we don't want ray traced shadows with shadow mask. if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).useRayTracedShadows = m_UseRayTracedShadows && !m_NonLightmappedOnly; } } // Only for Rectangle/Line/box projector lights. [SerializeField, FormerlySerializedAs("shapeWidth")] float m_ShapeWidth = 0.5f; /// /// Control the width of an area, a box spot light or a directional light cookie. /// public float shapeWidth { get => m_ShapeWidth; set { if (m_ShapeWidth == value) return; var lightType = legacyLight.type; if (lightType.IsArea()) m_ShapeWidth = Mathf.Clamp(value, k_MinAreaWidth, float.MaxValue); else m_ShapeWidth = Mathf.Clamp(value, 0, float.MaxValue); UpdateAllLightValues(); HDLightRenderDatabase.instance.SetShapeWidth(lightEntity, m_ShapeWidth); } } // Only for Rectangle/box projector and rectangle area lights [SerializeField, FormerlySerializedAs("shapeHeight")] float m_ShapeHeight = 0.5f; /// /// Control the height of an area, a box spot light or a directional light cookie. /// public float shapeHeight { get => m_ShapeHeight; set { if (m_ShapeHeight == value) return; if (legacyLight.type.IsArea()) m_ShapeHeight = Mathf.Clamp(value, k_MinAreaWidth, float.MaxValue); else m_ShapeHeight = Mathf.Clamp(value, 0, float.MaxValue); UpdateAllLightValues(); HDLightRenderDatabase.instance.SetShapeHeight(lightEntity, m_ShapeHeight); } } // Only for pyramid projector [SerializeField, FormerlySerializedAs("aspectRatio")] float m_AspectRatio = 1.0f; /// /// Get/Set the aspect ratio of a pyramid light /// public float aspectRatio { get => m_AspectRatio; set { if (m_AspectRatio == value) return; m_AspectRatio = Mathf.Clamp(value, k_MinAspectRatio, k_MaxAspectRatio); UpdateAllLightValues(); HDLightRenderDatabase.instance.SetAspectRatio(lightEntity, m_AspectRatio); } } // Only for Punctual/Sphere/Disc. Default shape radius is not 0 so that specular highlight is visible by default, it matches the previous default of 0.99 for MaxSmoothness. [SerializeField, FormerlySerializedAs("shapeRadius")] float m_ShapeRadius = 0.025f; /// /// Get/Set the radius of a light /// public float shapeRadius { get => m_ShapeRadius; set { if (m_ShapeRadius == value) return; m_ShapeRadius = Mathf.Clamp(value, 0, float.MaxValue); UpdateAllLightValues(); HDLightRenderDatabase.instance.SetShapeRadius(lightEntity, m_ShapeRadius); } } [SerializeField] float m_SoftnessScale = 1.0f; /// /// Get/Set the scale factor applied to shape radius or angular diameter for the softness calculation. /// public float softnessScale { get => m_SoftnessScale; set { if (m_SoftnessScale == value) return; m_SoftnessScale = Mathf.Clamp(value, 0, float.MaxValue); UpdateAllLightValues(); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).softnessScale = m_SoftnessScale; } } } [SerializeField, FormerlySerializedAs("useCustomSpotLightShadowCone")] bool m_UseCustomSpotLightShadowCone = false; // Custom spot angle for spotlight shadows /// /// Toggle the custom spot light shadow cone. /// public bool useCustomSpotLightShadowCone { get => m_UseCustomSpotLightShadowCone; set { if (m_UseCustomSpotLightShadowCone == value) return; m_UseCustomSpotLightShadowCone = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).useCustomSpotLightShadowCone = m_UseCustomSpotLightShadowCone; } } } [SerializeField, FormerlySerializedAs("customSpotLightShadowCone")] float m_CustomSpotLightShadowCone = 30.0f; /// /// Get/Set the custom spot shadow cone value. /// /// public float customSpotLightShadowCone { get => m_CustomSpotLightShadowCone; set { if (m_CustomSpotLightShadowCone == value) return; m_CustomSpotLightShadowCone = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).customSpotLightShadowCone = m_CustomSpotLightShadowCone; } } } // Only for Spot/Point/Directional - use to cheaply fake specular spherical area light // It is not 1 to make sure the highlight does not disappear. [Range(0.0f, 1.0f)] [SerializeField, FormerlySerializedAs("maxSmoothness")] float m_MaxSmoothness = 0.99f; /// /// Get/Set the maximum smoothness of a punctual or directional light. /// public float maxSmoothness { get => m_MaxSmoothness; set { if (m_MaxSmoothness == value) return; m_MaxSmoothness = Mathf.Clamp01(value); } } // If true, we apply the smooth attenuation factor on the range attenuation to get 0 value, else the attenuation is just inverse square and never reach 0 [SerializeField, FormerlySerializedAs("applyRangeAttenuation")] bool m_ApplyRangeAttenuation = true; /// /// If enabled, apply a smooth attenuation factor so at the end of the range, the attenuation is 0. /// Otherwise the inverse-square attenuation is used and the value never reaches 0. /// public bool applyRangeAttenuation { get => m_ApplyRangeAttenuation; set { if (m_ApplyRangeAttenuation == value) return; m_ApplyRangeAttenuation = value; UpdateAllLightValues(); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).applyRangeAttenuation = m_ApplyRangeAttenuation; } } // When true, a mesh will be display to represent the area light (Can only be change in editor, component is added in Editor) [SerializeField, FormerlySerializedAs("displayAreaLightEmissiveMesh")] bool m_DisplayAreaLightEmissiveMesh = false; /// /// If enabled, display an emissive mesh rect synchronized with the intensity and color of the light. /// public bool displayAreaLightEmissiveMesh { get => m_DisplayAreaLightEmissiveMesh; set { if (m_DisplayAreaLightEmissiveMesh == value) return; m_DisplayAreaLightEmissiveMesh = value; UpdateAllLightValues(); } } // Optional cookie for rectangular area lights [SerializeField, FormerlySerializedAs("areaLightCookie")] Texture m_AreaLightCookie = null; /// /// Get/Set cookie texture for area lights. /// public Texture areaLightCookie { get => m_AreaLightCookie; set { if (m_AreaLightCookie == value) return; m_AreaLightCookie = value; UpdateAllLightValues(); } } // Optional IES (Cubemap for PointLight) [SerializeField] internal Texture m_IESPoint; // Optional IES (2D Square texture for Spot or rectangular light) [SerializeField] internal Texture m_IESSpot; /// /// IES texture for Point lights. /// internal Texture IESPoint { get => m_IESPoint; set { if (value.dimension == TextureDimension.Cube) { m_IESPoint = value; UpdateAllLightValues(); } else { Debug.LogError("Texture dimension " + value.dimension + " is not supported for point lights."); m_IESPoint = null; } } } /// /// IES texture for Spot or Rectangular lights. /// internal Texture IESSpot { get => m_IESSpot; set { if (value.dimension == TextureDimension.Tex2D && value.width == value.height) { m_IESSpot = value; UpdateAllLightValues(); } else { Debug.LogError("Texture dimension " + value.dimension + " is not supported for spot lights or rectangular light (only square images)."); m_IESSpot = null; } } } /// /// IES texture for Point, Spot or Rectangular lights. /// For Point lights, this must be a cubemap. /// For Spot or Rectangle lights, this must be a 2D texture /// public Texture IESTexture { get { var lightType = legacyLight.type; if (lightType == LightType.Point) return IESPoint; else if (lightType.IsSpot() || lightType == LightType.Rectangle) return IESSpot; return null; } set { var lightType = legacyLight.type; if (lightType == LightType.Point) IESPoint = value; else if (lightType.IsSpot() || lightType == LightType.Rectangle) IESSpot = value; } } [SerializeField] bool m_IncludeForRayTracing = true; /// /// Controls if the light is enabled when the camera has the RayTracing frame setting enabled. /// public bool includeForRayTracing { get => m_IncludeForRayTracing; set { if (m_IncludeForRayTracing == value) return; m_IncludeForRayTracing = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).includeForRayTracing = m_IncludeForRayTracing; UpdateAllLightValues(); } } [SerializeField] bool m_IncludeForPathTracing = true; /// /// Controls if the light is enabled when the camera has Path Tracing enabled. /// public bool includeForPathTracing { get => m_IncludeForPathTracing; set { if (m_IncludeForPathTracing == value) return; m_IncludeForPathTracing = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).includeForPathTracing = m_IncludeForPathTracing; UpdateAllLightValues(); } } [Range(k_MinAreaLightShadowCone, k_MaxAreaLightShadowCone)] [SerializeField, FormerlySerializedAs("areaLightShadowCone")] float m_AreaLightShadowCone = 120.0f; /// /// Get/Set area light shadow cone value. /// public float areaLightShadowCone { get => m_AreaLightShadowCone; set { if (m_AreaLightShadowCone == value) return; m_AreaLightShadowCone = Mathf.Clamp(value, k_MinAreaLightShadowCone, k_MaxAreaLightShadowCone); UpdateAllLightValues(); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).areaLightShadowCone = m_AreaLightShadowCone; } } } // Flag that tells us if the shadow should be screen space [SerializeField, FormerlySerializedAs("useScreenSpaceShadows")] bool m_UseScreenSpaceShadows = false; /// /// Controls if we resolve the directional light shadows in screen space (ray tracing only). /// public bool useScreenSpaceShadows { get => m_UseScreenSpaceShadows; set { if (m_UseScreenSpaceShadows == value) return; m_UseScreenSpaceShadows = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).useScreenSpaceShadows = m_UseScreenSpaceShadows; } } // Directional lights only. [SerializeField, FormerlySerializedAs("interactsWithSky")] bool m_InteractsWithSky = true; /// /// Controls if the directional light affect the Physically Based sky. /// This have no effect on other skies. /// public bool interactsWithSky { // m_InteractWithSky can be true if user changed from directional to point light, so we need to check current type get => m_InteractsWithSky && legacyLight.type == LightType.Directional; set { if (m_InteractsWithSky == value) return; m_InteractsWithSky = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).interactsWithSky = m_InteractsWithSky; } } [SerializeField, FormerlySerializedAs("angularDiameter")] float m_AngularDiameter = 0.5f; /// /// Angular diameter of the celestial body represented by the light as seen from the camera (in degrees). /// Used to render the sun/moon disk. /// public float angularDiameter { get => m_AngularDiameter; set { if (m_AngularDiameter == value) return; m_AngularDiameter = value; // Serialization code clamps HDLightRenderDatabase.instance.SetAngularDiameter(lightEntity, m_AngularDiameter); } } /// /// Angular diameter mode to use. /// [SerializeField, FormerlySerializedAs("m_DiameterMultiplerMode")] public bool diameterMultiplerMode = false; /// /// Multiplier for the angular diameter of the celestial body used only when rendering the sun disk. /// [SerializeField, Min(0.0f), FormerlySerializedAs("m_DiameterMultiplier")] public float diameterMultiplier = 1.0f; /// /// Override for the angular diameter of the celestial body used only when rendering the sun disk. /// Mode [SerializeField, Min(0.0f), FormerlySerializedAs("m_DiameterOverride")] public float diameterOverride = 0.5f; /// /// Shading source of the celestial body. /// [SerializeField, FormerlySerializedAs("m_EmissiveLightSource")] public CelestialBodyShadingSource celestialBodyShadingSource = CelestialBodyShadingSource.Emission; /// /// The Directional light that should illuminate this celestial body. /// [SerializeField] public Light sunLightOverride; /// /// Color of the light source. /// [SerializeField] internal Color sunColor = Color.white; /// /// Intensity of the light source in Lux. /// [SerializeField, Min(0.0f)] internal float sunIntensity = 130000.0f; /// /// The percentage of moon that receives sunlight. /// [SerializeField, Range(0, 1), FormerlySerializedAs("m_MoonPhase")] public float moonPhase = 0.2f; /// /// The rotation of the moon phase. /// [SerializeField, Range(0, 360.0f), FormerlySerializedAs("m_MoonPhaseRotation")] public float moonPhaseRotation = 0.0f; /// /// The intensity of the sunlight reflected from the planet onto the moon. /// [SerializeField, Min(0.0f), FormerlySerializedAs("m_Earthshine")] public float earthshine = 1.0f; /// /// Size the flare around the celestial body (in degrees). /// [SerializeField, Range(0, 90), FormerlySerializedAs("m_FlareSize")] public float flareSize = 2.0f; /// /// Tints the flare of the celestial body. /// [SerializeField, FormerlySerializedAs("m_FlareTint")] public Color flareTint = Color.white; /// /// The falloff rate of flare intensity as the angle from the light increases. /// [SerializeField, Min(0.0f), FormerlySerializedAs("m_FlareFalloff")] public float flareFalloff = 4.0f; /// /// Intensity of the flare. /// [SerializeField, Range(0, 1)] public float flareMultiplier = 1.0f; /// /// Texture of the surface of the celestial body. Acts like a multiplier. /// [SerializeField, FormerlySerializedAs("m_SurfaceTexture")] public Texture surfaceTexture = null; /// /// Tints the surface of the celestial body. /// [SerializeField, FormerlySerializedAs("m_SurfaceTint")] public Color surfaceTint = Color.white; [SerializeField, Min(0.0f), FormerlySerializedAs("distance")] float m_Distance = 150000000000; // Sun to Earth /// /// Distance from the camera to the emissive celestial body represented by the light. /// public float distance { get => m_Distance; set { if (m_Distance == value) return; m_Distance = value; // Serialization code clamps if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).distance = m_Distance; } } [SerializeField, FormerlySerializedAs("useRayTracedShadows")] bool m_UseRayTracedShadows = false; /// /// Controls if we use ray traced shadows. /// public bool useRayTracedShadows { get => m_UseRayTracedShadows; set { if (m_UseRayTracedShadows == value) return; m_UseRayTracedShadows = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).useRayTracedShadows = m_UseRayTracedShadows; } } [Range(1, 32)] [SerializeField, FormerlySerializedAs("numRayTracingSamples")] int m_NumRayTracingSamples = 4; /// /// Controls the number of sample used for the ray traced shadows. /// public int numRayTracingSamples { get => m_NumRayTracingSamples; set { if (m_NumRayTracingSamples == value) return; m_NumRayTracingSamples = Mathf.Clamp(value, 1, 32); } } [SerializeField, FormerlySerializedAs("filterTracedShadow")] bool m_FilterTracedShadow = true; /// /// Toggle the filtering of ray traced shadows. /// public bool filterTracedShadow { get => m_FilterTracedShadow; set { if (m_FilterTracedShadow == value) return; m_FilterTracedShadow = value; } } [Range(1, 32)] [SerializeField, FormerlySerializedAs("filterSizeTraced")] int m_FilterSizeTraced = 16; /// /// Control the size of the filter used for ray traced shadows /// public int filterSizeTraced { get => m_FilterSizeTraced; set { if (m_FilterSizeTraced == value) return; m_FilterSizeTraced = Mathf.Clamp(value, 1, 32); } } [Range(0.0f, 2.0f)] [SerializeField, FormerlySerializedAs("sunLightConeAngle")] float m_SunLightConeAngle = 0.5f; /// /// Angular size of the sun in degree. /// public float sunLightConeAngle { get => m_SunLightConeAngle; set { if (m_SunLightConeAngle == value) return; m_SunLightConeAngle = Mathf.Clamp(value, 0.0f, 2.0f); } } [SerializeField, FormerlySerializedAs("lightShadowRadius")] float m_LightShadowRadius = 0.5f; /// /// Angular size of the sun in degree. /// public float lightShadowRadius { get => m_LightShadowRadius; set { if (m_LightShadowRadius == value) return; m_LightShadowRadius = Mathf.Max(value, 0.001f); } } [SerializeField] bool m_SemiTransparentShadow = false; /// /// Enable semi-transparent shadows on the light. /// public bool semiTransparentShadow { get => m_SemiTransparentShadow; set { if (m_SemiTransparentShadow == value) return; m_SemiTransparentShadow = value; } } [SerializeField] bool m_ColorShadow = true; /// /// Enable color shadows on the light. /// public bool colorShadow { get => m_ColorShadow; set { if (m_ColorShadow == value) return; m_ColorShadow = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).colorShadow = m_ColorShadow; } } [SerializeField] bool m_DistanceBasedFiltering = false; /// /// Uses the distance to the occluder to improve the shadow denoising. /// internal bool distanceBasedFiltering { get => m_DistanceBasedFiltering; set { if (m_DistanceBasedFiltering == value) return; m_DistanceBasedFiltering = value; } } [Range(k_MinEvsmExponent, k_MaxEvsmExponent)] [SerializeField, FormerlySerializedAs("evsmExponent")] float m_EvsmExponent = 15.0f; /// /// Controls the exponent used for EVSM shadows. /// public float evsmExponent { get => m_EvsmExponent; set { if (m_EvsmExponent == value) return; m_EvsmExponent = Mathf.Clamp(value, k_MinEvsmExponent, k_MaxEvsmExponent); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).evsmExponent = m_EvsmExponent; } } } [Range(k_MinEvsmLightLeakBias, k_MaxEvsmLightLeakBias)] [SerializeField, FormerlySerializedAs("evsmLightLeakBias")] float m_EvsmLightLeakBias = 0.0f; /// /// Controls the light leak bias value for EVSM shadows. /// public float evsmLightLeakBias { get => m_EvsmLightLeakBias; set { if (m_EvsmLightLeakBias == value) return; m_EvsmLightLeakBias = Mathf.Clamp(value, k_MinEvsmLightLeakBias, k_MaxEvsmLightLeakBias); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).evsmLightLeakBias = m_EvsmLightLeakBias; } } } [Range(k_MinEvsmVarianceBias, k_MaxEvsmVarianceBias)] [SerializeField, FormerlySerializedAs("evsmVarianceBias")] float m_EvsmVarianceBias = 1e-5f; /// /// Controls the variance bias used for EVSM shadows. /// public float evsmVarianceBias { get => m_EvsmVarianceBias; set { if (m_EvsmVarianceBias == value) return; m_EvsmVarianceBias = Mathf.Clamp(value, k_MinEvsmVarianceBias, k_MaxEvsmVarianceBias); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).evsmVarianceBias = m_EvsmVarianceBias; } } } [Range(k_MinEvsmBlurPasses, k_MaxEvsmBlurPasses)] [SerializeField, FormerlySerializedAs("evsmBlurPasses")] int m_EvsmBlurPasses = 0; /// /// Controls the number of blur passes used for EVSM shadows. /// public int evsmBlurPasses { get => m_EvsmBlurPasses; set { if (m_EvsmBlurPasses == value) return; m_EvsmBlurPasses = Mathf.Clamp(value, k_MinEvsmBlurPasses, k_MaxEvsmBlurPasses); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).evsmBlurPasses = (byte)m_EvsmBlurPasses; } } } // Now the renderingLayerMask is used for shadow layers and not light layers [SerializeField, FormerlySerializedAs("lightlayersMask")] RenderingLayerMask m_LightlayersMask = (RenderingLayerMask) (uint) UnityEngine.RenderingLayerMask.defaultRenderingLayerMask; /// /// Controls which layer will be affected by this light /// /// public RenderingLayerMask lightlayersMask { get => linkShadowLayers ? (RenderingLayerMask)RenderingLayerMaskToLightLayer(legacyLight.renderingLayerMask) : m_LightlayersMask; set { m_LightlayersMask = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).renderingLayerMask = (uint)m_LightlayersMask; if (linkShadowLayers) legacyLight.renderingLayerMask = LightLayerToRenderingLayerMask((int)m_LightlayersMask, legacyLight.renderingLayerMask); } } [SerializeField, FormerlySerializedAs("linkShadowLayers")] bool m_LinkShadowLayers = true; /// /// Controls if we want to synchronize shadow map light layers and light layers or not. /// public bool linkShadowLayers { get => m_LinkShadowLayers; set => m_LinkShadowLayers = value; } /// /// Returns a mask of light layers as uint and handle the case of Everything as being 0xFF and not -1 /// /// public uint GetLightLayers() { int value = (int)lightlayersMask; return value < 0 ? (uint)RenderingLayerMask.Everything : (uint)value; } /// /// Returns a mask of shadow light layers as uint and handle the case of Everything as being 0xFF and not -1 /// /// public uint GetShadowLayers() { int value = RenderingLayerMaskToLightLayer(legacyLight.renderingLayerMask); return value < 0 ? (uint)RenderingLayerMask.Everything : (uint)value; } // Shadow Settings [SerializeField, FormerlySerializedAs("shadowNearPlane")] float m_ShadowNearPlane = 0.1f; /// /// Controls the near plane distance of the shadows. /// public float shadowNearPlane { get => m_ShadowNearPlane; set { if (m_ShadowNearPlane == value) return; m_ShadowNearPlane = Mathf.Clamp(value, 0, HDShadowUtils.k_MaxShadowNearPlane); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).shadowNearPlane = m_ShadowNearPlane; } } } // PCSS settings [Range(1, 64)] [SerializeField, FormerlySerializedAs("blockerSampleCount")] int m_BlockerSampleCount = 24; /// /// Controls the number of samples used to detect blockers for PCSS shadows. /// public int blockerSampleCount { get => m_BlockerSampleCount; set { if (m_BlockerSampleCount == value) return; m_BlockerSampleCount = Mathf.Clamp(value, 1, 64); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).blockerSampleCount = (byte)m_BlockerSampleCount; } } } [Range(1, 64)] [SerializeField, FormerlySerializedAs("filterSampleCount")] int m_FilterSampleCount = 16; /// /// Controls the number of samples used to filter for PCSS shadows. /// public int filterSampleCount { get => m_FilterSampleCount; set { if (m_FilterSampleCount == value) return; m_FilterSampleCount = Mathf.Clamp(value, 1, 64); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).filterSampleCount = (byte)m_FilterSampleCount; } } } [Range(0, 1.0f)] [SerializeField, FormerlySerializedAs("minFilterSize")] float m_MinFilterSize = 0.1f; /// /// Controls the minimum filter size of PCSS shadows. /// public float minFilterSize { get => m_MinFilterSize; set { if (m_MinFilterSize == value) return; m_MinFilterSize = Mathf.Clamp(value, 0.0f, 1.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).minFilterSize = m_MinFilterSize; } } } [Range(1, 64)] [SerializeField] int m_DirLightPCSSBlockerSampleCount = 24; /// /// Controls the number of samples used to detect blockers for directional lights PCSS shadows. /// // Note: We duplicate this setting so its default value can be different than other light types public int dirLightPCSSBlockerSampleCount { get => m_DirLightPCSSBlockerSampleCount; set { if (m_DirLightPCSSBlockerSampleCount == value) return; m_DirLightPCSSBlockerSampleCount = Mathf.Clamp(value, 1, 64); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSBlockerSampleCount = (byte)m_DirLightPCSSBlockerSampleCount; } } } [Range(1, 64)] [SerializeField] int m_DirLightPCSSFilterSampleCount = 16; /// /// Controls the number of samples used to filter for directional lights PCSS shadows. /// // Note: We duplicate this setting so its default value can be different than other light types public int dirLightPCSSFilterSampleCount { get => m_DirLightPCSSFilterSampleCount; set { if (m_DirLightPCSSFilterSampleCount == value) return; m_DirLightPCSSFilterSampleCount = Mathf.Clamp(value, 1, 64); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSFilterSampleCount = (byte)m_DirLightPCSSFilterSampleCount; } } } [SerializeField] float m_DirLightPCSSMaxPenumbraSize = 0.56f; // Default matching previous API max blocker distance at 64m for a light angular diameter of 0.5 /// /// Maximum penumbra size (in world space), limiting blur filter kernel size /// Measured against a receiving surface perpendicular to light direction (penumbra may get wider for different angles) /// Very large kernels may affect GPU performance and/or produce undesirable artifacts close to caster /// public float dirLightPCSSMaxPenumbraSize { get => m_DirLightPCSSMaxPenumbraSize; set { m_DirLightPCSSMaxPenumbraSize = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSMaxPenumbraSize = m_DirLightPCSSMaxPenumbraSize; } } } [SerializeField] float m_DirLightPCSSMaxSamplingDistance = 0.5f; /// /// Maximum distance from the receiver PCSS shadow sampling occurs, this is to avoid light bleeding due to distant /// blockers hiding the cone apex and leading to missing occlusion, the lower the least light bleeding but too low will cause self-shadowing /// Note that the algorithm will also clamp the sampling distance in function of the blocker distance, to avoid light bleeding with very close blockers /// public float dirLightPCSSMaxSamplingDistance { get => m_DirLightPCSSMaxSamplingDistance; set { m_DirLightPCSSMaxSamplingDistance = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSMaxSamplingDistance = m_DirLightPCSSMaxSamplingDistance; } } } [SerializeField] float m_DirLightPCSSMinFilterSizeTexels = 1.5f; /// /// Minimum PCSS filter size (in shadowmap texels) to avoid aliasing /// public float dirLightPCSSMinFilterSizeTexels { get => m_DirLightPCSSMinFilterSizeTexels; set { m_DirLightPCSSMinFilterSizeTexels = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSMinFilterSizeTexels = m_DirLightPCSSMinFilterSizeTexels; } } } [SerializeField] float m_DirLightPCSSMinFilterMaxAngularDiameter = 10.0f; /// /// Maximum angular diameter to use to reach minimum filter size, this makes a wider cone at the apex /// So that we quickly reach minimum filter size while avoiding self-shadowing /// public float dirLightPCSSMinFilterMaxAngularDiameter { get => m_DirLightPCSSMinFilterMaxAngularDiameter; set { m_DirLightPCSSMinFilterMaxAngularDiameter = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSMinFilterMaxAngularDiameter = m_DirLightPCSSMinFilterMaxAngularDiameter; } } } [SerializeField] float m_DirLightPCSSBlockerSearchAngularDiameter = 12.0f; /// /// Angular diameter to use for blocker search, will include blockers outside of the light cone /// when greater than m_AngularDiameter to reduce light bleeding. Increasing this value too much may /// result in self-shadowing artifacts. A value below m_AngularDiameter will get clamped to m_AngularDiameter /// public float dirLightPCSSBlockerSearchAngularDiameter { get => m_DirLightPCSSBlockerSearchAngularDiameter; set { m_DirLightPCSSBlockerSearchAngularDiameter = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSBlockerSearchAngularDiameter = m_DirLightPCSSBlockerSearchAngularDiameter; } } } [Range(1, 6)] [SerializeField] float m_DirLightPCSSBlockerSamplingClumpExponent = 2.0f; /// /// Affects how blocker search samples are distributed. Samples distance to center is elevated to this power. /// A clump exponent of 1 means uniform distribution on the sampling disk. /// A clump exponent of 2 means distance from center of the uniform distribution are squared (clumped toward center) /// public float dirLightPCSSBlockerSamplingClumpExponent { get => m_DirLightPCSSBlockerSamplingClumpExponent; set { m_DirLightPCSSBlockerSamplingClumpExponent = Math.Max(value, 0.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).dirLightPCSSBlockerSamplingClumpExponent = m_DirLightPCSSBlockerSamplingClumpExponent; } } } // Improved Moment Shadows settings [Range(1, 32)] [SerializeField, FormerlySerializedAs("kernelSize")] int m_KernelSize = 5; /// /// Controls the kernel size for IMSM shadows. /// public int kernelSize { get => m_KernelSize; set { if (m_KernelSize == value) return; m_KernelSize = Mathf.Clamp(value, 1, 32); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).kernelSize = (byte)m_KernelSize; } } } [Range(0.0f, 9.0f)] [SerializeField, FormerlySerializedAs("lightAngle")] float m_LightAngle = 1.0f; /// /// Controls the light angle for IMSM shadows. /// public float lightAngle { get => m_LightAngle; set { if (m_LightAngle == value) return; m_LightAngle = Mathf.Clamp(value, 0.0f, 9.0f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).lightAngle = m_LightAngle; } } } [Range(0.0001f, 0.01f)] [SerializeField, FormerlySerializedAs("maxDepthBias")] float m_MaxDepthBias = 0.001f; /// /// Controls the max depth bias for IMSM shadows. /// public float maxDepthBias { get => m_MaxDepthBias; set { if (m_MaxDepthBias == value) return; m_MaxDepthBias = Mathf.Clamp(value, 0.0001f, 0.01f); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).maxDepthBias = m_MaxDepthBias; } } } /// /// The range of the light. /// /// public float range { get => legacyLight.range; set => legacyLight.range = value; } /// /// Color of the light. /// public Color color { get => legacyLight.color; set { legacyLight.color = value; // Update Area Light Emissive mesh color UpdateAreaLightEmissiveMesh(); } } #endregion #region HDShadow Properties API (from AdditionalShadowData) [ValueCopy] //we want separate object with same values [SerializeField] private IntScalableSettingValue m_ShadowResolution = new IntScalableSettingValue { @override = k_DefaultShadowResolution, useOverride = true, }; /// /// Retrieve the scalable setting for shadow resolution. Use the SetShadowResolution function to set a custom resolution. /// public IntScalableSettingValue shadowResolution => m_ShadowResolution; [Range(0.0f, 1.0f)] [SerializeField] float m_ShadowDimmer = 1.0f; /// /// Get/Set the shadow dimmer. /// public float shadowDimmer { get => m_ShadowDimmer; set { if (m_ShadowDimmer == value) return; m_ShadowDimmer = Mathf.Clamp01(value); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).shadowDimmer = m_ShadowDimmer; } } [Range(0.0f, 1.0f)] [SerializeField] float m_VolumetricShadowDimmer = 1.0f; /// /// Get/Set the volumetric shadow dimmer value, between 0 and 1. /// public float volumetricShadowDimmer { get => useVolumetric ? m_VolumetricShadowDimmer : 0.0f; set { if (m_VolumetricShadowDimmer == value) return; m_VolumetricShadowDimmer = Mathf.Clamp01(value); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).volumetricShadowDimmer = m_VolumetricShadowDimmer; } } [SerializeField] float m_ShadowFadeDistance = 10000.0f; /// /// Get/Set the shadow fade distance. /// public float shadowFadeDistance { get => m_ShadowFadeDistance; set { if (m_ShadowFadeDistance == value) return; m_ShadowFadeDistance = Mathf.Clamp(value, 0, float.MaxValue); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).shadowFadeDistance = m_ShadowFadeDistance; } } [SerializeField] [ValueCopy] //we want separate object with same values BoolScalableSettingValue m_UseContactShadow = new BoolScalableSettingValue { useOverride = true }; /// /// Retrieve the scalable setting to use/ignore contact shadows. Toggle the use contact shadow using @override property of the ScalableSetting. /// public BoolScalableSettingValue useContactShadow => m_UseContactShadow; [SerializeField] bool m_RayTracedContactShadow = false; /// /// Controls if we want to ray trace the contact shadow. /// public bool rayTraceContactShadow { get => m_RayTracedContactShadow; set { if (m_RayTracedContactShadow == value) return; m_RayTracedContactShadow = value; } } [SerializeField] Color m_ShadowTint = Color.black; /// /// Controls the tint of the shadows. /// /// public Color shadowTint { get => m_ShadowTint; set { if (m_ShadowTint == value) return; m_ShadowTint = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).shadowTint = m_ShadowTint; } } [SerializeField] bool m_PenumbraTint = false; /// /// Controls if we want to ray trace the contact shadow. /// public bool penumbraTint { get => m_PenumbraTint; set { if (m_PenumbraTint == value) return; m_PenumbraTint = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).penumbraTint = m_PenumbraTint; } } [SerializeField] float m_NormalBias = 0.75f; /// /// Get/Set the normal bias of the shadow maps. /// /// public float normalBias { get => m_NormalBias; set { if (m_NormalBias == value) return; m_NormalBias = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).normalBias = value; } } } [SerializeField] float m_SlopeBias = 0.5f; /// /// Get/Set the slope bias of the shadow maps. /// /// public float slopeBias { get => m_SlopeBias; set { if (m_SlopeBias == value) return; m_SlopeBias = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).slopeBias = m_SlopeBias; } } } [SerializeField] ShadowUpdateMode m_ShadowUpdateMode = ShadowUpdateMode.EveryFrame; /// /// Get/Set the shadow update mode. /// /// public ShadowUpdateMode shadowUpdateMode { get => m_ShadowUpdateMode; set { if (m_ShadowUpdateMode == value) return; m_ShadowUpdateMode = value; RegisterCachedShadowLightOptional(); if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).shadowUpdateMode = value; } } } [SerializeField] bool m_AlwaysDrawDynamicShadows = false; /// /// Whether cached shadows will always draw dynamic shadow casters. /// /// public bool alwaysDrawDynamicShadows { get => m_AlwaysDrawDynamicShadows; set { m_AlwaysDrawDynamicShadows = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).alwaysDrawDynamicShadows = value; } } } [SerializeField] bool m_UpdateShadowOnLightMovement = false; /// /// Whether a cached shadow map will be automatically updated when the light transform changes (more than a given threshold set via cachedShadowTranslationUpdateThreshold /// and cachedShadowAngleUpdateThreshold). /// /// public bool updateUponLightMovement { get => m_UpdateShadowOnLightMovement; set { if (m_UpdateShadowOnLightMovement != value) { if (m_UpdateShadowOnLightMovement) HDShadowManager.cachedShadowManager.RegisterTransformToCache(this); else HDShadowManager.cachedShadowManager.RegisterTransformToCache(this); m_UpdateShadowOnLightMovement = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).updateUponLightMovement = value; } } } } [SerializeField] float m_CachedShadowTranslationThreshold = 0.01f; /// /// Controls the position threshold over which a cached shadow which is set to update upon light movement /// (updateUponLightMovement from script or Update on Light Movement in UI) triggers an update. /// public float cachedShadowTranslationUpdateThreshold { get => m_CachedShadowTranslationThreshold; set { if (m_CachedShadowTranslationThreshold == value) return; m_CachedShadowTranslationThreshold = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).cachedShadowTranslationUpdateThreshold = value; } } } [SerializeField] float m_CachedShadowAngularThreshold = 0.5f; /// /// If any transform angle of the light is over this threshold (in degrees) since last update, a cached shadow which is set to update upon light movement /// (updateUponLightMovement from script or Update on Light Movement in UI) is updated. /// public float cachedShadowAngleUpdateThreshold { get => m_CachedShadowAngularThreshold; set { if (m_CachedShadowAngularThreshold == value) return; m_CachedShadowAngularThreshold = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).cachedShadowAngleUpdateThreshold = value; } } } // Only for Rectangle area lights. [Range(0.0f, 90.0f)] [SerializeField] float m_BarnDoorAngle = 90.0f; /// /// Get/Set the angle so that it behaves like a barn door. /// public float barnDoorAngle { get => m_BarnDoorAngle; set { if (m_BarnDoorAngle == value) return; m_BarnDoorAngle = Mathf.Clamp(value, 0.0f, 90.0f); UpdateAllLightValues(); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).barnDoorAngle = m_BarnDoorAngle; } } // Only for Rectangle area lights [SerializeField] float m_BarnDoorLength = 0.05f; /// /// Get/Set the length for the barn door sides. /// public float barnDoorLength { get => m_BarnDoorLength; set { if (m_BarnDoorLength == value) return; m_BarnDoorLength = Mathf.Clamp(value, 0.0f, float.MaxValue); UpdateAllLightValues(); if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).barnDoorLength = m_BarnDoorLength; } } [SerializeField] bool m_preserveCachedShadow = false; /// /// Controls whether the cached shadow maps for this light is preserved upon disabling the light. /// If this field is set to true, then the light will maintain its space in the cached shadow atlas until it is destroyed. /// public bool preserveCachedShadow { get => m_preserveCachedShadow; set { if (m_preserveCachedShadow == value) return; m_preserveCachedShadow = value; } } [SerializeField] bool m_OnDemandShadowRenderOnPlacement = true; /// /// If the shadow update mode is set to OnDemand, this parameter controls whether the shadows are rendered the first time without needing an explicit render request. If this properties is false, /// the OnDemand shadows will never be rendered unless a render request is performed explicitly. /// public bool onDemandShadowRenderOnPlacement { get => m_OnDemandShadowRenderOnPlacement; set { if (m_OnDemandShadowRenderOnPlacement == value) return; m_OnDemandShadowRenderOnPlacement = value; } } // This is a bit confusing, but it is an override to ignore the onDemandShadowRenderOnPlacement field when a light is registered for the first time as a consequence of a request for shadow update. internal bool forceRenderOnPlacement = false; /// /// True if the light affects volumetric fog, false otherwise /// public bool affectsVolumetric { get => useVolumetric; set { useVolumetric = value; if (lightEntity.valid) HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity).affectVolumetric = useVolumetric; } } #endregion #region Internal API for moving shadow datas from AdditionalShadowData to HDAdditionalLightData [SerializeField] [ValueCopy] //we want separate object with same values float[] m_ShadowCascadeRatios = new float[3] { 0.05f, 0.2f, 0.3f }; internal float[] shadowCascadeRatios { get => m_ShadowCascadeRatios; set => m_ShadowCascadeRatios = value; } [SerializeField] [ValueCopy] //we want separate object with same values float[] m_ShadowCascadeBorders = new float[4] { 0.2f, 0.2f, 0.2f, 0.2f }; internal float[] shadowCascadeBorders { get => m_ShadowCascadeBorders; set => m_ShadowCascadeBorders = value; } [SerializeField] int m_ShadowAlgorithm = 0; internal int shadowAlgorithm { get => m_ShadowAlgorithm; set => m_ShadowAlgorithm = value; } [SerializeField] int m_ShadowVariant = 0; internal int shadowVariant { get => m_ShadowVariant; set => m_ShadowVariant = value; } [SerializeField] int m_ShadowPrecision = 0; internal int shadowPrecision { get => m_ShadowPrecision; set => m_ShadowPrecision = value; } #endregion #pragma warning disable 0414 // The field '...' is assigned but its value is never used, these fields are used by the inspector // This is specific for the LightEditor GUI and not use at runtime [SerializeField, FormerlySerializedAs("useOldInspector")] bool useOldInspector = false; [SerializeField, FormerlySerializedAs("useVolumetric")] bool useVolumetric = true; [SerializeField, FormerlySerializedAs("featuresFoldout")] bool featuresFoldout = true; #pragma warning restore 0414 internal unsafe UnsafeList shadowRequests { get { UnsafeList retValue = default; if (lightEntity.valid) { HDLightRenderDatabase lightRenderDatabase = HDLightRenderDatabase.instance; HDShadowRequestDatabase shadowRequestDatabase = HDShadowRequestDatabase.instance; NativeList hdShadowRequestStorage = shadowRequestDatabase.hdShadowRequestStorage; int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForShadowRequests; Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < hdShadowRequestStorage.Length); UnsafeList* unsafeListPtr = hdShadowRequestStorage.GetUnsafeList(); retValue = new UnsafeList(unsafeListPtr->Ptr + dataStartIndex, HDShadowRequest.maxLightShadowRequestsCount); } return retValue; } } unsafe UnsafeList shadowRequestIndices { get { UnsafeList retValue = default; if (lightEntity.valid) { HDLightRenderDatabase lightRenderDatabase = HDLightRenderDatabase.instance; HDShadowRequestDatabase shadowRequestDatabase = HDShadowRequestDatabase.instance; NativeList hdShadowIndicesStorage = shadowRequestDatabase.hdShadowRequestIndicesStorage; int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForRequestIndices; Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < hdShadowIndicesStorage.Length); UnsafeList* unsafeListPtr = hdShadowIndicesStorage.GetUnsafeList(); retValue = new UnsafeList(unsafeListPtr->Ptr + dataStartIndex, HDShadowRequest.maxLightShadowRequestsCount); } return retValue; } } // Data for cached shadow maps [System.NonSerialized, ExcludeCopy] int m_LightIdxForCachedShadows = -1; internal int lightIdxForCachedShadows { get => m_LightIdxForCachedShadows; set { m_LightIdxForCachedShadows = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).lightIdxForCachedShadows = value; } } } internal bool hasShadowCache { get { return lightIdxForCachedShadows != -1; } } unsafe Vector3* m_CachedViewPositions { get { Vector3* ptr = null; if (lightEntity.valid) { HDLightRenderDatabase lightRenderDatabase = HDLightRenderDatabase.instance; HDShadowRequestDatabase shadowRequestDatabase = HDShadowRequestDatabase.instance; NativeList cachedViewPositionsStorage = shadowRequestDatabase.cachedViewPositionsStorage; int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForCachedViewPositions; Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < cachedViewPositionsStorage.Length); UnsafeList* unsafeListPtr = cachedViewPositionsStorage.GetUnsafeList(); ptr = unsafeListPtr->Ptr + dataStartIndex; } return ptr; } } // Temporary matrix that stores the previous light data (mainly used to discard history for ray traced screen space shadows) [System.NonSerialized, ExcludeCopy] internal Matrix4x4 previousTransform = Matrix4x4.identity; // Temporary index that stores the current shadow index for the light [System.NonSerialized, ExcludeCopy] internal int shadowIndex = -1; // Temporary information if the shadow was cached [System.NonSerialized, ExcludeCopy] internal bool wasReallyVisibleLastFrame = true; [System.NonSerialized, ExcludeCopy] internal bool fallbackToCachedShadows = false; // Runtime datas used to compute light intensity [ExcludeCopy] Light m_Light; internal Light legacyLight { get { // Calling TryGetComponent only when needed is faster than letting the null check happen inside TryGetComponent if (m_Light == null) TryGetComponent(out m_Light); return m_Light; } } [ExcludeCopy] private LightType? cachedLightType; const string k_EmissiveMeshViewerName = "EmissiveMeshViewer"; [ExcludeCopy] GameObject m_ChildEmissiveMeshViewer; [ExcludeCopy] internal MeshFilter m_EmissiveMeshFilter; [field: ExcludeCopy] internal MeshRenderer emissiveMeshRenderer { get; private set; } #if UNITY_EDITOR [ExcludeCopy] bool m_NeedsPrefabInstanceCheck = false; [ExcludeCopy] bool needRefreshPrefabInstanceEmissiveMeshes = false; #endif [ExcludeCopy] bool needRefreshEmissiveMeshesFromTimeLineUpdate = false; void CreateChildEmissiveMeshViewerIfNeeded() { #if UNITY_EDITOR if (PrefabUtility.IsPartOfPrefabAsset(this)) return; #endif bool here = m_ChildEmissiveMeshViewer != null && !m_ChildEmissiveMeshViewer.Equals(null); #if UNITY_EDITOR //if not parented anymore, destroy it if (here && m_ChildEmissiveMeshViewer.transform.parent != transform) { if (Application.isPlaying) Destroy(m_ChildEmissiveMeshViewer); else DestroyImmediate(m_ChildEmissiveMeshViewer); m_ChildEmissiveMeshViewer = null; m_EmissiveMeshFilter = null; here = false; } #endif //if not here, try to find it first if (!here) { foreach (Transform child in transform) { var test = child.GetComponents(typeof(Component)); if (child.name == k_EmissiveMeshViewerName && child.hideFlags == (HideFlags.NotEditable | HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor) && child.GetComponents(typeof(MeshFilter)).Length == 1 && child.GetComponents(typeof(MeshRenderer)).Length == 1 && child.GetComponents(typeof(Component)).Length == 3) // Transform + MeshFilter + MeshRenderer { m_ChildEmissiveMeshViewer = child.gameObject; m_ChildEmissiveMeshViewer.transform.localPosition = Vector3.zero; m_ChildEmissiveMeshViewer.transform.localRotation = Quaternion.identity; m_ChildEmissiveMeshViewer.transform.localScale = Vector3.one; m_ChildEmissiveMeshViewer.layer = areaLightEmissiveMeshLayer == -1 ? gameObject.layer : areaLightEmissiveMeshLayer; m_EmissiveMeshFilter = m_ChildEmissiveMeshViewer.GetComponent(); emissiveMeshRenderer = m_ChildEmissiveMeshViewer.GetComponent(); emissiveMeshRenderer.shadowCastingMode = m_AreaLightEmissiveMeshShadowCastingMode; emissiveMeshRenderer.motionVectorGenerationMode = m_AreaLightEmissiveMeshMotionVectorGenerationMode; here = true; break; } } } //if still not here, create it if (!here) { m_ChildEmissiveMeshViewer = new GameObject(k_EmissiveMeshViewerName, typeof(MeshFilter), typeof(MeshRenderer)); m_ChildEmissiveMeshViewer.hideFlags = HideFlags.NotEditable | HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor; m_ChildEmissiveMeshViewer.transform.SetParent(transform); m_ChildEmissiveMeshViewer.transform.localPosition = Vector3.zero; m_ChildEmissiveMeshViewer.transform.localRotation = Quaternion.identity; m_ChildEmissiveMeshViewer.transform.localScale = Vector3.one; m_ChildEmissiveMeshViewer.layer = areaLightEmissiveMeshLayer == -1 ? gameObject.layer : areaLightEmissiveMeshLayer; m_EmissiveMeshFilter = m_ChildEmissiveMeshViewer.GetComponent(); emissiveMeshRenderer = m_ChildEmissiveMeshViewer.GetComponent(); emissiveMeshRenderer.shadowCastingMode = m_AreaLightEmissiveMeshShadowCastingMode; emissiveMeshRenderer.motionVectorGenerationMode = m_AreaLightEmissiveMeshMotionVectorGenerationMode; } } void DestroyChildEmissiveMeshViewer() { m_EmissiveMeshFilter = null; emissiveMeshRenderer.enabled = false; emissiveMeshRenderer = null; CoreUtils.Destroy(m_ChildEmissiveMeshViewer); m_ChildEmissiveMeshViewer = null; } [SerializeField] ShadowCastingMode m_AreaLightEmissiveMeshShadowCastingMode = ShadowCastingMode.Off; [SerializeField] MotionVectorGenerationMode m_AreaLightEmissiveMeshMotionVectorGenerationMode; [SerializeField] int m_AreaLightEmissiveMeshLayer = -1; //Special value that means we need to grab the one in the Light for initialization (for migration purpose) /// Change the Shadow Casting Mode of the generated emissive mesh for Area Light public ShadowCastingMode areaLightEmissiveMeshShadowCastingMode { get => m_AreaLightEmissiveMeshShadowCastingMode; set { if (m_AreaLightEmissiveMeshShadowCastingMode == value) return; m_AreaLightEmissiveMeshShadowCastingMode = value; if (emissiveMeshRenderer != null && !emissiveMeshRenderer.Equals(null)) { emissiveMeshRenderer.shadowCastingMode = m_AreaLightEmissiveMeshShadowCastingMode; } } } /// Change the Motion Vector Generation Mode of the generated emissive mesh for Area Light public MotionVectorGenerationMode areaLightEmissiveMeshMotionVectorGenerationMode { get => m_AreaLightEmissiveMeshMotionVectorGenerationMode; set { if (m_AreaLightEmissiveMeshMotionVectorGenerationMode == value) return; m_AreaLightEmissiveMeshMotionVectorGenerationMode = value; if (emissiveMeshRenderer != null && !emissiveMeshRenderer.Equals(null)) { emissiveMeshRenderer.motionVectorGenerationMode = m_AreaLightEmissiveMeshMotionVectorGenerationMode; } } } /// Change the Layer of the generated emissive mesh for Area Light public int areaLightEmissiveMeshLayer { get => m_AreaLightEmissiveMeshLayer; set { if (m_AreaLightEmissiveMeshLayer == value) return; m_AreaLightEmissiveMeshLayer = value; if (emissiveMeshRenderer != null && !emissiveMeshRenderer.Equals(null) && m_AreaLightEmissiveMeshLayer != -1) { emissiveMeshRenderer.gameObject.layer = m_AreaLightEmissiveMeshLayer; } } } /// A callback allowing the creation of a new Matrix4x4 based on the lightLocalToWorld matrix public delegate Matrix4x4 CustomViewCallback(Matrix4x4 lightLocalToWorldMatrix); CustomViewCallback m_CustomViewCallbackEvent; /// Change the View matrix for Spot Light public CustomViewCallback CustomViewCallbackEvent { get { return m_CustomViewCallbackEvent; } set { m_CustomViewCallbackEvent = value; if (lightEntity.valid) { HDLightRenderDatabase.instance.SetCustomCallback(lightEntity, value); } } } void OnDestroy() { if (lightIdxForCachedShadows >= 0) // If it is within the cached system we need to evict it. HDShadowManager.cachedShadowManager.EvictLight(this, legacyLight.type); DestroyHDLightRenderEntity(); } internal void DestroyHDLightRenderEntity() { if (!lightEntity.valid) return; HDLightRenderDatabase.instance.DestroyEntity(lightEntity); lightEntity = HDLightRenderEntity.Invalid; } void OnDisable() { // If it is within the cached system we need to evict it, unless user explicitly requires not to. // If the shadow was pending placement in the atlas, we also evict it, even if the user wants to preserve it. if ((!preserveCachedShadow || HDShadowManager.cachedShadowManager.LightIsPendingPlacement(lightIdxForCachedShadows, shadowMapType)) && hasShadowCache) { HDShadowManager.cachedShadowManager.EvictLight(this, legacyLight.type); } SetEmissiveMeshRendererEnabled(false); s_overlappingHDLights.Remove(this); DestroyHDLightRenderEntity(); } void SetEmissiveMeshRendererEnabled(bool enabled) { if (displayAreaLightEmissiveMesh && emissiveMeshRenderer) { emissiveMeshRenderer.enabled = enabled; } } internal static int GetShadowRequestCount(int shadowSettingsCascadeShadowSplitCount, LightType lightType) { return lightType == LightType.Point ? 6 : lightType == LightType.Directional ? shadowSettingsCascadeShadowSplitCount : 1; } /// /// Request shadow map rendering when Update Mode is set to On Demand. /// public void RequestShadowMapRendering() { if (shadowUpdateMode == ShadowUpdateMode.OnDemand) { HDShadowManager.cachedShadowManager.ScheduleShadowUpdate(this); } } /// /// Some lights render more than one shadow maps (e.g. cascade shadow maps or point lights). This method is used to request the rendering of specific shadow map /// when Update Mode is set to On Demand. For example, to request the update of a second cascade, shadowIndex should be 1. /// Note: if shadowIndex is a 0-based index and it must be lower than the number of shadow maps a light renders (i.e. cascade count for directional lights, 6 for point lights). /// /// The index of the subshadow to update. public void RequestSubShadowMapRendering(int shadowIndex) { if (shadowUpdateMode == ShadowUpdateMode.OnDemand) { HDShadowManager.cachedShadowManager.ScheduleShadowUpdate(this, shadowIndex); } } internal bool ShadowIsUpdatedEveryFrame() { return shadowUpdateMode == ShadowUpdateMode.EveryFrame; } // TODO: This is used to avoid compilation errors due to unreachable code static bool s_EnableFallbackToCachedShadows = false; internal void RegisterCachedShadowLightOptional() { // TODO Enable fall back to cached shadows for relevant systems fallbackToCachedShadows = (shadowUpdateMode == ShadowUpdateMode.EveryFrame) && (legacyLight.type != LightType.Directional) && s_EnableFallbackToCachedShadows; bool wantsShadowCache = shadowUpdateMode != ShadowUpdateMode.EveryFrame || fallbackToCachedShadows; wantsShadowCache = wantsShadowCache && (legacyLight.shadows != LightShadows.None); if (!wantsShadowCache && hasShadowCache && !preserveCachedShadow) { HDShadowManager.cachedShadowManager.EvictLight(this, this.legacyLight.type); } bool onDemand = shadowUpdateMode == ShadowUpdateMode.OnDemand && !onDemandShadowRenderOnPlacement; if (wantsShadowCache && !hasShadowCache && !onDemand && lightEntity.valid) { HDShadowManager.cachedShadowManager.RegisterLight(this); } } internal ShadowMapUpdateType GetShadowUpdateType(LightType lightType) { return GetShadowUpdateType(lightType, shadowUpdateMode, alwaysDrawDynamicShadows, HDCachedShadowManager.instance.DirectionalHasCachedAtlas()); } internal static ShadowMapUpdateType GetShadowUpdateType(LightType lightType, ShadowUpdateMode shadowUpdateMode, bool alwaysDrawDynamicShadows, bool directionalHasCachedAtlas) { if (shadowUpdateMode == ShadowUpdateMode.EveryFrame) return ShadowMapUpdateType.Dynamic; #if UNITY_2021_1_OR_NEWER if (alwaysDrawDynamicShadows) { if (lightType == LightType.Directional) { if (directionalHasCachedAtlas) return ShadowMapUpdateType.Mixed; } else { return ShadowMapUpdateType.Mixed; } } #endif return ShadowMapUpdateType.Cached; } internal int GetResolutionFromSettings(ShadowMapType shadowMapType, HDShadowInitParameters initParameters, bool cachedResolution = false) { if (cachedResolution && fallbackToCachedShadows) { return HDShadowManager.k_OffscreenShadowMapResolution; } switch (shadowMapType) { case ShadowMapType.CascadedDirectional: return Math.Min(m_ShadowResolution.Value(initParameters.shadowResolutionDirectional), initParameters.maxDirectionalShadowMapResolution); case ShadowMapType.PunctualAtlas: return Math.Min(m_ShadowResolution.Value(initParameters.shadowResolutionPunctual), initParameters.maxPunctualShadowMapResolution); case ShadowMapType.AreaLightAtlas: return Math.Min(m_ShadowResolution.Value(initParameters.shadowResolutionArea), initParameters.maxAreaShadowMapResolution); default: return 0; } } internal int GetResolutionFromSettings(LightType lightType, HDShadowInitParameters initParameters) { return GetResolutionFromSettings(GetShadowMapType(lightType), initParameters); } internal void ReserveShadowMap(Camera camera, HDShadowManager shadowManager, HDShadowSettings shadowSettings, in HDShadowInitParameters initParameters, in VisibleLight visibleLight, LightType lightType, bool forcedVisible) { HDLightRenderDatabase renderDatabase = HDLightRenderDatabase.instance; // Create shadow requests array using the light type if (!renderDatabase.GetShadowRequestSetHandle(lightEntity).valid) { renderDatabase.AllocateHDShadowRequests(lightEntity); } ShadowMapType shadowType = GetShadowMapType(lightType); // Reserve wanted resolution in the shadow atlas int resolution = GetResolutionFromSettings(shadowType, initParameters); //Exit out early if we dont want to render the shadow anyways if (resolution == 0) return; Vector2 viewportSize = new Vector2(resolution, resolution); bool viewPortRescaling = false; // Compute dynamic shadow resolution viewPortRescaling |= (shadowType == ShadowMapType.PunctualAtlas && initParameters.punctualLightShadowAtlas.useDynamicViewportRescale); viewPortRescaling |= (shadowType == ShadowMapType.AreaLightAtlas && initParameters.areaLightShadowAtlas.useDynamicViewportRescale); bool shadowIsInCacheSystem = !ShadowIsUpdatedEveryFrame(); if (viewPortRescaling && !shadowIsInCacheSystem) { // Formulas: https://www.desmos.com/calculator/tdodbuysut f(x) is the distance between 0 and 1, g(x) is the screen ratio (oscillating to simulate different light sizes) // The idea is to have a lot of resolution when the camera is close to the light OR the screen area is high. // linear normalized distance between the light and camera with max shadow distance float distance01 = Mathf.Clamp01(Vector3.Distance(camera.transform.position, visibleLight.GetPosition()) / shadowSettings.maxShadowDistance.value); // ease out and invert the curve, give more importance to closer distances distance01 = 1.0f - Mathf.Pow(distance01, 2); // normalized ratio between light range and distance float range01 = Mathf.Clamp01(visibleLight.range / Vector3.Distance(camera.transform.position, visibleLight.GetPosition())); float scaleFactor01 = Mathf.Max(distance01, range01); // We allow a maximum of 64 rescale between the highest and lowest shadow resolution // It prevent having too many resolution changes when the player is moving. const float maxRescaleSteps = 64; scaleFactor01 = Mathf.RoundToInt(scaleFactor01 * maxRescaleSteps) / maxRescaleSteps; // resize viewport size by the normalized size of the light on screen viewportSize = Vector2.Lerp(HDShadowManager.k_MinShadowMapResolution * Vector2.one, viewportSize, scaleFactor01); } viewportSize = Vector2.Max(viewportSize, new Vector2(HDShadowManager.k_MinShadowMapResolution, HDShadowManager.k_MinShadowMapResolution)); // Update the directional shadow atlas size if (lightType == LightType.Directional) shadowManager.UpdateDirectionalShadowResolution((int)viewportSize.x, shadowSettings.cascadeShadowSplitCount.value); if (shadowIsInCacheSystem) viewportSize = new Vector2(resolution, resolution); int count = GetShadowRequestCount(shadowSettings.cascadeShadowSplitCount.value, lightType); var updateType = GetShadowUpdateType(lightType); bool hasCachedComponent = !ShadowIsUpdatedEveryFrame(); if (forcedVisible && !shadowIsInCacheSystem) { // Limit resolution for offscreen lights with dynamic shadowmap viewportSize = Vector2.Min(viewportSize, new Vector2(HDShadowManager.k_OffscreenShadowMapResolution, HDShadowManager.k_OffscreenShadowMapResolution)); if (lightEntity.valid) { // Change this in the database HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).shadowUpdateMode = ShadowUpdateMode.OnDemand; } HDShadowManager.cachedShadowManager.ScheduleShadowUpdate(this); updateType = ShadowMapUpdateType.Cached; wasReallyVisibleLastFrame = false; } else { if (lightEntity.valid) { // Change this in the database HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).shadowUpdateMode = m_ShadowUpdateMode; } wasReallyVisibleLastFrame = true; } var requestIndices = shadowRequestIndices; for (int index = 0; index < count; index++) { requestIndices[index] = shadowManager.ReserveShadowResolutions(viewportSize, shadowMapType, GetInstanceID(), index, updateType); } } internal bool HasShadowAtlasPlacement() { // If we force evicted the light, it will have lightIdxForCachedShadows == -1 return !HDShadowManager.cachedShadowManager.LightIsPendingPlacement(lightIdxForCachedShadows, shadowMapType) && (lightIdxForCachedShadows != -1); } internal void OverrideShadowResolutionRequestsWithShadowCache(HDShadowManager shadowManager, HDShadowSettings shadowSettings, LightType lightType) { int shadowRequestCount = GetShadowRequestCount(shadowSettings.cascadeShadowSplitCount.value, lightType); for (int i = 0; i < shadowRequestCount; i++) { int shadowRequestIndex = shadowRequestIndices[i]; if (shadowRequestIndex < 0 || shadowRequestIndex >= shadowManager.shadowResolutionRequestStorage.Length) continue; ref HDShadowResolutionRequest resolutionRequest = ref shadowManager.shadowResolutionRequestStorage.ElementAt(shadowRequestIndex); int cachedShadowID = lightIdxForCachedShadows + i; HDShadowManager.cachedShadowManager.OverrideShadowResolutionRequestWithCachedData(ref resolutionRequest, cachedShadowID, shadowMapType); } } // This offset shift the position of the spotlight used to approximate the area light shadows. The offset is the minimum such that the full // area light shape is included in the cone spanned by the spot light. internal static float GetAreaLightOffsetForShadows(Vector2 shapeSize, float coneAngle) { float halfMinSize = Mathf.Min(shapeSize.x, shapeSize.y) * 0.5f; float halfAngle = coneAngle * 0.5f; float cotanHalfAngle = 1.0f / Mathf.Tan(halfAngle * Mathf.Deg2Rad); float offset = halfMinSize * cotanHalfAngle; return -offset; } // We need these old states to make timeline and the animator record the intensity value and the emissive mesh changes [System.NonSerialized] TimelineWorkaround timelineWorkaround = new TimelineWorkaround(); #if UNITY_EDITOR // Force to retrieve color light's m_UseColorTemperature because it's private [System.NonSerialized, ExcludeCopy] SerializedProperty m_UseColorTemperatureProperty; SerializedProperty useColorTemperatureProperty { get { if (m_UseColorTemperatureProperty == null) { m_UseColorTemperatureProperty = lightSerializedObject.FindProperty("m_UseColorTemperature"); } return m_UseColorTemperatureProperty; } } [System.NonSerialized, ExcludeCopy] SerializedObject m_LightSerializedObject; SerializedObject lightSerializedObject { get { if (m_LightSerializedObject == null) { m_LightSerializedObject = new SerializedObject(legacyLight); } return m_LightSerializedObject; } } #endif internal bool useColorTemperature { get => legacyLight.useColorTemperature; set { if (legacyLight.useColorTemperature == value) return; legacyLight.useColorTemperature = value; } } internal Color EvaluateLightColor() { Color finalColor = legacyLight.color.linear * legacyLight.intensity; if (legacyLight.useColorTemperature) finalColor *= Mathf.CorrelatedColorTemperatureToRGB(legacyLight.colorTemperature); return finalColor; } // TODO: we might be able to get rid to that [System.NonSerialized, ExcludeCopy] bool m_Animated; private void Start() { // If there is an animator attached ot the light, we assume that some of the light properties // might be driven by this animator (using timeline or animations) so we force the LateUpdate // to sync the animated HDAdditionalLightData properties with the light component. m_Animated = GetComponent() != null; } // TODO: There are a lot of old != current checks and assignation in this function, maybe think about using another system ? internal static void TickLateUpdate() { // Prevent any unwanted sync when not in HDRP (case 1217575) if (HDRenderPipeline.currentPipeline == null) return; DynamicArray allAdditionalLightDatas = HDLightRenderDatabase.instance.hdAdditionalLightData; int additionalLightCount = HDLightRenderDatabase.instance.lightCount; for (int i = 0; i < additionalLightCount; i++) { HDAdditionalLightData lightData = allAdditionalLightDatas[i]; // HDRP manually subcribes to the player loop callback and calls the tick function. This can trigger an // edge case during async scene loads where the component is initialized, but the parent GameObject is // not. In this case, we simply skip the tick logic. This is in a try block because there's no other // way. Simply accessing the .gameObject member calls a getter that will throw a null ref exception in // this invalid state. try { var go = lightData.gameObject; } catch (Exception) { continue; } if (lightData.cachedLightType != lightData.legacyLight.type) { // ^ The light type has changed since the last tick. if (lightData.m_ShadowUpdateMode != ShadowUpdateMode.EveryFrame && lightData.cachedLightType.HasValue) { HDShadowManager.cachedShadowManager.EvictLight(lightData, lightData.cachedLightType.Value); } var directionalLights = HDLightRenderDatabase.instance.directionalLights; if (lightData.cachedLightType == LightType.Directional) { directionalLights.Add(lightData); } else if (lightData.legacyLight.type != LightType.Directional) { // Remove the light from directionalLights, We use a loop to avoid a GC allocation (UUM-69806) for (int k = 0; k < directionalLights.Count; ++k) { if (ReferenceEquals(directionalLights[k], lightData)) { directionalLights.RemoveAt(k); break; } } } #if UNITY_EDITOR switch (lightData.legacyLight.type) { case LightType.Disc: lightData.legacyLight.lightmapBakeType = LightmapBakeType.Baked; break; case LightType.Tube: lightData.legacyLight.lightmapBakeType = LightmapBakeType.Realtime; break; } #endif lightData.cachedLightType = lightData.legacyLight.type; lightData.RegisterCachedShadowLightOptional(); } bool forceShadowCulling = false; // TODO enable for relevant systems if (s_EnableFallbackToCachedShadows) { // Only force lights that will fall back to cached shadows forceShadowCulling = lightData.fallbackToCachedShadows; // If we have something in the cache, there is no need to force culling // if the light is visible again it will be culled, until then we use // the cached shadow map forceShadowCulling &= lightData.wasReallyVisibleLastFrame; } lightData.legacyLight.forceVisible = forceShadowCulling; // TODO: The rest of this loop only handles animation. Iterate over a separate list in builds, // containing only lights with Animator components. // We force the animation in the editor and in play mode when there is an animator component attached to the light #if !UNITY_EDITOR if (!lightData.m_Animated) continue; #endif #if UNITY_EDITOR // If modification are due to change on prefab asset that are non overridden on this prefab instance if (lightData.m_NeedsPrefabInstanceCheck && PrefabUtility.IsPartOfPrefabInstance(lightData) && ((PrefabUtility.GetCorrespondingObjectFromOriginalSource(lightData) as HDAdditionalLightData)?.needRefreshPrefabInstanceEmissiveMeshes ?? false)) { lightData.needRefreshPrefabInstanceEmissiveMeshes = true; } lightData.m_NeedsPrefabInstanceCheck = false; // Update the list of overlapping lights for the LightOverlap scene view mode if (lightData.IsOverlapping()) s_overlappingHDLights.Add(lightData); else s_overlappingHDLights.Remove(lightData); #endif #if UNITY_EDITOR // If we requested an emissive mesh but for some reason (e.g. Reload scene unchecked in the Enter Playmode options) Awake has not been called, // we need to create it manually. if (lightData.m_DisplayAreaLightEmissiveMesh && (lightData.m_ChildEmissiveMeshViewer == null || lightData.m_ChildEmissiveMeshViewer.Equals(null))) { lightData.UpdateAreaLightEmissiveMesh(); } //if not parented anymore, refresh it if (lightData.m_ChildEmissiveMeshViewer != null && !lightData.m_ChildEmissiveMeshViewer.Equals(null)) { if (lightData.m_ChildEmissiveMeshViewer.transform.parent != lightData.transform) { lightData.CreateChildEmissiveMeshViewerIfNeeded(); lightData.UpdateAreaLightEmissiveMesh(); } if (lightData.m_ChildEmissiveMeshViewer.isStatic != lightData.gameObject.isStatic) lightData.m_ChildEmissiveMeshViewer.isStatic = lightData.gameObject.isStatic; if (GameObjectUtility.GetStaticEditorFlags(lightData.m_ChildEmissiveMeshViewer) != GameObjectUtility.GetStaticEditorFlags(lightData.gameObject)) GameObjectUtility.SetStaticEditorFlags(lightData.m_ChildEmissiveMeshViewer, GameObjectUtility.GetStaticEditorFlags(lightData.gameObject)); } #endif //auto change layer on emissive mesh if (lightData.areaLightEmissiveMeshLayer == -1 && lightData.m_ChildEmissiveMeshViewer != null && !lightData.m_ChildEmissiveMeshViewer.Equals(null) && lightData.m_ChildEmissiveMeshViewer.layer != lightData.gameObject.layer) lightData.m_ChildEmissiveMeshViewer.layer = lightData.gameObject.layer; // Delayed cleanup when removing emissive mesh from timeline if (lightData.needRefreshEmissiveMeshesFromTimeLineUpdate) { lightData.needRefreshEmissiveMeshesFromTimeLineUpdate = false; lightData.UpdateAreaLightEmissiveMesh(); } #if UNITY_EDITOR // Prefab instance child emissive mesh update if (lightData.needRefreshPrefabInstanceEmissiveMeshes) { // We must not call the update on Prefab Asset that are already updated or we will enter infinite loop if (!PrefabUtility.IsPartOfPrefabAsset(lightData)) { lightData.UpdateAreaLightEmissiveMesh(); } lightData.needRefreshPrefabInstanceEmissiveMeshes = false; } #endif if (lightData.legacyLight.enabled != lightData.timelineWorkaround.lightEnabled) { lightData.SetEmissiveMeshRendererEnabled(lightData.legacyLight.enabled); lightData.timelineWorkaround.lightEnabled = lightData.legacyLight.enabled; } // Check if the intensity have been changed by the inspector or an animator if (lightData.timelineWorkaround.oldLossyScale != lightData.transform.lossyScale || lightData.legacyLight.colorTemperature != lightData.timelineWorkaround.oldLightColorTemperature) { lightData.UpdateAreaLightEmissiveMesh(); lightData.timelineWorkaround.oldLossyScale = lightData.transform.lossyScale; lightData.timelineWorkaround.oldLightColorTemperature = lightData.legacyLight.colorTemperature; } #if !UNITY_EDITOR // Same check for light angle to update intensity using spot angle if ((lightData.legacyLight.type == LightType.Spot || lightData.legacyLight.type == LightType.Pyramid) && (lightData.timelineWorkaround.oldSpotAngle != lightData.legacyLight.spotAngle)) { // If light unit is currently displayed in lumen and 'reflector' is on and the spot angle has changed, // recalculate intensity (candela) so lumen value remains constant if (lightData.legacyLight.lightUnit == LightUnit.Lumen && lightData.legacyLight.enableSpotReflector) { float oldSolidAngle; float newSolidAngle; if (lightData.legacyLight.type == LightType.Spot) { oldSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(lightData.timelineWorkaround.oldSpotAngle); newSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(lightData.legacyLight.spotAngle); } else // Pyramid { oldSolidAngle = LightUnitUtils.GetSolidAngleFromPyramidLight( lightData.timelineWorkaround.oldSpotAngle, lightData.aspectRatio); newSolidAngle = LightUnitUtils.GetSolidAngleFromPyramidLight( lightData.legacyLight.spotAngle, lightData.aspectRatio); } float oldLumen = LightUnitUtils.CandelaToLumen(lightData.legacyLight.intensity, oldSolidAngle); lightData.legacyLight.intensity = LightUnitUtils.LumenToCandela(oldLumen, newSolidAngle); } lightData.timelineWorkaround.oldSpotAngle = lightData.legacyLight.spotAngle; } #endif if (lightData.legacyLight.color != lightData.timelineWorkaround.oldLightColor || lightData.timelineWorkaround.oldLossyScale != lightData.transform.lossyScale || lightData.displayAreaLightEmissiveMesh != lightData.timelineWorkaround.oldDisplayAreaLightEmissiveMesh || lightData.legacyLight.colorTemperature != lightData.timelineWorkaround.oldLightColorTemperature) { lightData.UpdateAreaLightEmissiveMesh(); lightData.timelineWorkaround.oldLightColor = lightData.legacyLight.color; lightData.timelineWorkaround.oldLossyScale = lightData.transform.lossyScale; lightData.timelineWorkaround.oldDisplayAreaLightEmissiveMesh = lightData.displayAreaLightEmissiveMesh; lightData.timelineWorkaround.oldLightColorTemperature = lightData.legacyLight.colorTemperature; } } } void OnDidApplyAnimationProperties() { UpdateAllLightValues(fromTimeLine: true); UpdateRenderEntity(); } /// /// Copy all field from this to an additional light data /// /// Destination component public void CopyTo(HDAdditionalLightData data) { data.m_InnerSpotPercent = m_InnerSpotPercent; data.m_SpotIESCutoffPercent = m_SpotIESCutoffPercent; data.m_LightDimmer = m_LightDimmer; data.m_VolumetricDimmer = m_VolumetricDimmer; data.m_FadeDistance = m_FadeDistance; data.m_VolumetricFadeDistance = m_VolumetricFadeDistance; data.m_AffectDiffuse = m_AffectDiffuse; data.m_AffectSpecular = m_AffectSpecular; data.m_NonLightmappedOnly = m_NonLightmappedOnly; data.m_ShapeWidth = m_ShapeWidth; data.m_ShapeHeight = m_ShapeHeight; data.m_AspectRatio = m_AspectRatio; data.m_ShapeRadius = m_ShapeRadius; data.m_SoftnessScale = m_SoftnessScale; data.m_UseCustomSpotLightShadowCone = m_UseCustomSpotLightShadowCone; data.m_CustomSpotLightShadowCone = m_CustomSpotLightShadowCone; data.m_MaxSmoothness = m_MaxSmoothness; data.m_ApplyRangeAttenuation = m_ApplyRangeAttenuation; data.m_DisplayAreaLightEmissiveMesh = m_DisplayAreaLightEmissiveMesh; data.m_AreaLightCookie = m_AreaLightCookie; data.m_IESPoint = m_IESPoint; data.m_IESSpot = m_IESSpot; data.m_IncludeForRayTracing = m_IncludeForRayTracing; data.m_IncludeForPathTracing = m_IncludeForPathTracing; data.m_AreaLightShadowCone = m_AreaLightShadowCone; data.m_UseScreenSpaceShadows = m_UseScreenSpaceShadows; data.m_InteractsWithSky = m_InteractsWithSky; data.m_AngularDiameter = m_AngularDiameter; data.diameterMultiplerMode = diameterMultiplerMode; data.diameterMultiplier = diameterMultiplier; data.diameterOverride = diameterOverride; data.celestialBodyShadingSource = celestialBodyShadingSource; data.sunLightOverride = sunLightOverride; data.sunColor = sunColor; data.sunIntensity = sunIntensity; data.moonPhase = moonPhase; data.moonPhaseRotation = moonPhaseRotation; data.earthshine = earthshine; data.flareSize = flareSize; data.flareTint = flareTint; data.flareFalloff = flareFalloff; data.flareMultiplier = flareMultiplier; data.surfaceTexture = surfaceTexture; data.surfaceTint = surfaceTint; data.m_Distance = m_Distance; data.m_UseRayTracedShadows = m_UseRayTracedShadows; data.m_NumRayTracingSamples = m_NumRayTracingSamples; data.m_FilterTracedShadow = m_FilterTracedShadow; data.m_FilterSizeTraced = m_FilterSizeTraced; data.m_SunLightConeAngle = m_SunLightConeAngle; data.m_LightShadowRadius = m_LightShadowRadius; data.m_SemiTransparentShadow = m_SemiTransparentShadow; data.m_ColorShadow = m_ColorShadow; data.m_DistanceBasedFiltering = m_DistanceBasedFiltering; data.m_EvsmExponent = m_EvsmExponent; data.m_EvsmLightLeakBias = m_EvsmLightLeakBias; data.m_EvsmVarianceBias = m_EvsmVarianceBias; data.m_EvsmBlurPasses = m_EvsmBlurPasses; data.m_LightlayersMask = m_LightlayersMask; data.m_LinkShadowLayers = m_LinkShadowLayers; data.m_ShadowNearPlane = m_ShadowNearPlane; data.m_BlockerSampleCount = m_BlockerSampleCount; data.m_FilterSampleCount = m_FilterSampleCount; data.m_MinFilterSize = m_MinFilterSize; data.m_KernelSize = m_KernelSize; data.m_LightAngle = m_LightAngle; data.m_MaxDepthBias = m_MaxDepthBias; m_ShadowResolution.CopyTo(data.m_ShadowResolution); data.m_ShadowDimmer = m_ShadowDimmer; data.m_VolumetricShadowDimmer = m_VolumetricShadowDimmer; data.m_ShadowFadeDistance = m_ShadowFadeDistance; m_UseContactShadow.CopyTo(data.m_UseContactShadow); data.m_RayTracedContactShadow = m_RayTracedContactShadow; data.m_ShadowTint = m_ShadowTint; data.m_PenumbraTint = m_PenumbraTint; data.m_NormalBias = m_NormalBias; data.m_SlopeBias = m_SlopeBias; data.m_ShadowUpdateMode = m_ShadowUpdateMode; data.m_AlwaysDrawDynamicShadows = m_AlwaysDrawDynamicShadows; data.m_UpdateShadowOnLightMovement = m_UpdateShadowOnLightMovement; data.m_CachedShadowTranslationThreshold = m_CachedShadowTranslationThreshold; data.m_CachedShadowAngularThreshold = m_CachedShadowAngularThreshold; data.m_BarnDoorLength = m_BarnDoorLength; data.m_BarnDoorAngle = m_BarnDoorAngle; data.m_preserveCachedShadow = m_preserveCachedShadow; data.m_OnDemandShadowRenderOnPlacement = m_OnDemandShadowRenderOnPlacement; data.forceRenderOnPlacement = forceRenderOnPlacement; data.m_ShadowCascadeRatios = new float[m_ShadowCascadeRatios.Length]; m_ShadowCascadeRatios.CopyTo(data.m_ShadowCascadeRatios, 0); data.m_ShadowCascadeBorders = new float[m_ShadowCascadeBorders.Length]; m_ShadowCascadeBorders.CopyTo(data.m_ShadowCascadeBorders, 0); data.m_ShadowAlgorithm = m_ShadowAlgorithm; data.m_ShadowVariant = m_ShadowVariant; data.m_ShadowPrecision = m_ShadowPrecision; data.useOldInspector = useOldInspector; data.useVolumetric = useVolumetric; data.featuresFoldout = featuresFoldout; data.m_AreaLightEmissiveMeshShadowCastingMode = m_AreaLightEmissiveMeshShadowCastingMode; data.m_AreaLightEmissiveMeshMotionVectorGenerationMode = m_AreaLightEmissiveMeshMotionVectorGenerationMode; data.m_AreaLightEmissiveMeshLayer = m_AreaLightEmissiveMeshLayer; data.dirLightPCSSMaxPenumbraSize = dirLightPCSSMaxPenumbraSize; data.dirLightPCSSMaxSamplingDistance = dirLightPCSSMaxSamplingDistance; data.dirLightPCSSMinFilterSizeTexels = dirLightPCSSMinFilterSizeTexels; data.dirLightPCSSMinFilterMaxAngularDiameter = dirLightPCSSMinFilterMaxAngularDiameter; data.dirLightPCSSBlockerSearchAngularDiameter = dirLightPCSSBlockerSearchAngularDiameter; data.dirLightPCSSBlockerSamplingClumpExponent = dirLightPCSSBlockerSamplingClumpExponent; data.dirLightPCSSBlockerSampleCount = dirLightPCSSBlockerSampleCount; data.dirLightPCSSFilterSampleCount = dirLightPCSSFilterSampleCount; #if UNITY_EDITOR data.timelineWorkaround = timelineWorkaround; #endif data.UpdateAllLightValues(); data.UpdateRenderEntity(); } // As we have our own default value, we need to initialize the light intensity correctly /// /// Initialize an HDAdditionalLightData that have just beeing created. /// /// public static void InitDefaultHDAdditionalLightData(HDAdditionalLightData lightData) { var light = lightData.legacyLight; // Set light intensity and unit using its type switch (light.type) { case LightType.Directional: light.lightUnit = LightUnit.Lux; light.intensity = k_DefaultDirectionalLightIntensity / Mathf.PI * 100000.0f; // Change back to just k_DefaultDirectionalLightIntensity on 11.0.0 (can't change constant as it's a breaking change) break; case LightType.Box: light.lightUnit = LightUnit.Lux; light.intensity = LightUnitUtils.LumenToCandela(k_DefaultPunctualLightIntensity, LightUnitUtils.SphereSolidAngle); // Find a proper default for box lights break; case LightType.Rectangle: case LightType.Disc: case LightType.Tube: light.lightUnit = LightUnit.Lumen; light.intensity = LightUnitUtils.ConvertIntensity(light, k_DefaultAreaLightIntensity, LightUnit.Lumen, LightUnit.Nits); lightData.shadowNearPlane = 0; light.shadows = LightShadows.None; break; case LightType.Point: case LightType.Spot: case LightType.Pyramid: light.lightUnit = LightUnit.Lumen; light.intensity = LightUnitUtils.ConvertIntensity(light, k_DefaultPunctualLightIntensity, LightUnit.Lumen, LightUnit.Candela); break; } // We don't use the global settings of shadow mask by default light.lightShadowCasterMode = LightShadowCasterMode.Everything; lightData.normalBias = 0.75f; lightData.slopeBias = 0.5f; // Enable filter/temperature mode by default for all light types lightData.useColorTemperature = true; } void OnValidate() { UpdateBounds(); RefreshCachedShadow(); // Light size must be non-zero, else we get NaNs. shapeWidth = Mathf.Max(shapeWidth, k_MinLightSize); shapeHeight = Mathf.Max(shapeHeight, k_MinLightSize); shapeRadius = Mathf.Max(shapeRadius, 0.0f); #if UNITY_EDITOR // If modification are due to change on prefab asset, we want to have prefab instances to self-update, but we cannot check in OnValidate if this is part of // prefab instance. So we delay the check on next update (and before teh LateUpdate logic) m_NeedsPrefabInstanceCheck = true; #endif } #region Update functions to patch values in the Light component when we change properties inside HDAdditionalLightData void Awake() { Migrate(); // We need to reconstruct the emissive mesh at Light creation if needed due to not beeing able to change hierarchy in prefab asset. // This is especially true at Tuntime as there is no code path that will trigger the rebuild of emissive mesh until one of the property modifying it is changed. UpdateAreaLightEmissiveMesh(); } internal void UpdateAreaLightEmissiveMesh(bool fromTimeLine = false) { var lightType = legacyLight.type; bool displayEmissiveMesh = lightType.IsArea() && displayAreaLightEmissiveMesh; // Only show childEmissiveMeshViewer if type is Area and requested if (!lightType.IsArea() || !displayEmissiveMesh) { if (m_ChildEmissiveMeshViewer) { if (fromTimeLine) { // Cannot perform destroy in OnDidApplyAnimationProperties // So shut down rendering instead and set up a flag for cleaning later emissiveMeshRenderer.enabled = false; needRefreshEmissiveMeshesFromTimeLineUpdate = true; } else DestroyChildEmissiveMeshViewer(); } // We don't have anything to do left if the dislay emissive mesh option is disabled return; } #if UNITY_EDITOR else if (PrefabUtility.IsPartOfPrefabAsset(this)) { // Child emissive mesh should not be handled in asset but we must trigger every instance to update themselves. Will be done in OnValidate needRefreshPrefabInstanceEmissiveMeshes = true; // We don't have anything to do left as the child will never appear while editing the prefab asset return; } #endif else { CreateChildEmissiveMeshViewerIfNeeded(); #if UNITY_EDITOR // In Prefab Instance, as we can be called from OnValidate due to Prefab Asset modification, we need to refresh modification on child emissive mesh if (needRefreshPrefabInstanceEmissiveMeshes && PrefabUtility.IsPartOfPrefabInstance(this)) { emissiveMeshRenderer.shadowCastingMode = m_AreaLightEmissiveMeshShadowCastingMode; emissiveMeshRenderer.motionVectorGenerationMode = m_AreaLightEmissiveMeshMotionVectorGenerationMode; } #endif } // Update Mesh if (GraphicsSettings.TryGetRenderPipelineSettings(out var assets)) { switch (lightType) { case LightType.Tube: if (m_EmissiveMeshFilter.sharedMesh != assets.emissiveCylinderMesh) m_EmissiveMeshFilter.sharedMesh = assets.emissiveCylinderMesh; break; default: if (m_EmissiveMeshFilter.sharedMesh != assets.emissiveQuadMesh) m_EmissiveMeshFilter.sharedMesh = assets.emissiveQuadMesh; break; } } // Update light area size with clamping Vector3 lightSize = new Vector3(m_ShapeWidth, m_ShapeHeight, 0); if (lightType == LightType.Tube) lightSize.y = 0; lightSize = Vector3.Max(Vector3.one * k_MinAreaWidth, lightSize); switch (lightType) { case LightType.Rectangle: m_ShapeWidth = lightSize.x; m_ShapeHeight = lightSize.y; break; case LightType.Tube: m_ShapeWidth = lightSize.x; break; case LightType.Disc: m_ShapeWidth = lightSize.x; m_ShapeHeight = lightSize.x; break; default: break; } if (lightEntity.valid) { ref HDLightRenderData lightRenderData = ref HDLightRenderDatabase.instance.EditLightDataAsRef(lightEntity); lightRenderData.shapeWidth = m_ShapeWidth; lightRenderData.shapeHeight = m_ShapeHeight; } legacyLight.areaSize = lightSize; // Update child emissive mesh scale Vector3 lossyScale = emissiveMeshRenderer.transform.localRotation * transform.lossyScale; emissiveMeshRenderer.transform.localScale = new Vector3(lightSize.x / lossyScale.x, lightSize.y / lossyScale.y, k_MinAreaWidth / lossyScale.z); // NOTE: When the user duplicates a light in the editor, the material is not duplicated and when changing the properties of one of them (source or duplication) // It either overrides both or is overriden. Given that when we duplicate an object the name changes, this approach works. When the name of the game object is then changed again // the material is not re-created until one of the light properties is changed again. if (emissiveMeshRenderer.sharedMaterial == null || emissiveMeshRenderer.sharedMaterial.name != gameObject.name) { // Shader.Find works because the Unlit shader is referenced in the HDRP Runtime Resources // We can't access the resources though because HDRP isn't initialized during the Awake of this gameobject emissiveMeshRenderer.sharedMaterial = new Material(Shader.Find("HDRP/Unlit")); emissiveMeshRenderer.sharedMaterial.SetFloat("_IncludeIndirectLighting", 0.0f); emissiveMeshRenderer.sharedMaterial.name = gameObject.name; } // Update Mesh emissive properties emissiveMeshRenderer.sharedMaterial.SetColor("_UnlitColor", Color.black); // m_Light.intensity is in luminance which is the value we need for emissive color Color value = legacyLight.color.linear * legacyLight.intensity; if (useColorTemperature) value *= Mathf.CorrelatedColorTemperatureToRGB(legacyLight.colorTemperature); value *= lightDimmer; emissiveMeshRenderer.sharedMaterial.SetColor("_EmissiveColor", value); bool enableEmissiveColorMap = false; // Set the cookie (if there is one) and raise or remove the shader feature if (displayEmissiveMesh && areaLightCookie != null && areaLightCookie != Texture2D.whiteTexture) { emissiveMeshRenderer.sharedMaterial.SetTexture("_EmissiveColorMap", areaLightCookie); enableEmissiveColorMap = true; } else if (displayEmissiveMesh && IESSpot != null && IESSpot != Texture2D.whiteTexture) { emissiveMeshRenderer.sharedMaterial.SetTexture("_EmissiveColorMap", IESSpot); enableEmissiveColorMap = true; } else { emissiveMeshRenderer.sharedMaterial.SetTexture("_EmissiveColorMap", Texture2D.whiteTexture); } CoreUtils.SetKeyword(emissiveMeshRenderer.sharedMaterial, "_EMISSIVE_COLOR_MAP", enableEmissiveColorMap); if (m_AreaLightEmissiveMeshLayer != -1) emissiveMeshRenderer.gameObject.layer = m_AreaLightEmissiveMeshLayer; } void UpdateRectangleLightBounds() { legacyLight.useShadowMatrixOverride = false; // TODO: Don't use bounding sphere overrides. Support this properly in Unity native instead. legacyLight.useBoundingSphereOverride = true; float halfWidth = m_ShapeWidth * 0.5f; float halfHeight = m_ShapeHeight * 0.5f; float diag = Mathf.Sqrt(halfWidth * halfWidth + halfHeight * halfHeight); legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, range + diag); } void UpdateDiscLightBounds() { legacyLight.useShadowMatrixOverride = false; // TODO: Don't use bounding sphere overrides. Support this properly in Unity native instead. legacyLight.useBoundingSphereOverride = true; legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, range + m_ShapeWidth); } void UpdateTubeLightBounds() { legacyLight.useShadowMatrixOverride = false; // TODO: Don't use bounding sphere overrides. Support this properly in Unity native instead. legacyLight.useBoundingSphereOverride = true; legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, range + m_ShapeWidth * 0.5f); } void UpdateBoxLightBounds() { legacyLight.useShadowMatrixOverride = true; // TODO: Don't use bounding sphere overrides. Support this properly in Unity native instead. legacyLight.useBoundingSphereOverride = true; // Need to inverse scale because culling != rendering convention apparently Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f)); legacyLight.shadowMatrixOverride = HDShadowUtils.ExtractBoxLightProjectionMatrix(legacyLight.range, shapeWidth, m_ShapeHeight, shadowNearPlane) * scaleMatrix; // Very conservative bounding sphere taking the diagonal of the shape as the radius float diag = new Vector3(shapeWidth * 0.5f, m_ShapeHeight * 0.5f, legacyLight.range * 0.5f).magnitude; legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, legacyLight.range * 0.5f, diag); } void UpdatePyramidLightBounds() { legacyLight.useShadowMatrixOverride = true; // TODO: Don't use bounding sphere overrides. Support this properly in Unity native instead. legacyLight.useBoundingSphereOverride = true; // Need to inverse scale because culling != rendering convention apparently Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f)); legacyLight.shadowMatrixOverride = HDShadowUtils.ExtractSpotLightProjectionMatrix(legacyLight.range, legacyLight.spotAngle, shadowNearPlane, aspectRatio, 0.0f) * scaleMatrix; legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, legacyLight.range); } void UpdateBounds() { switch (legacyLight.type) { case LightType.Spot: legacyLight.useBoundingSphereOverride = false; legacyLight.useShadowMatrixOverride = false; break; case LightType.Box: UpdateBoxLightBounds(); break; case LightType.Pyramid: UpdatePyramidLightBounds(); break; case LightType.Rectangle: UpdateRectangleLightBounds(); break; case LightType.Disc: UpdateDiscLightBounds(); break; case LightType.Tube: UpdateTubeLightBounds(); break; default: legacyLight.useBoundingSphereOverride = false; legacyLight.useShadowMatrixOverride = false; break; } } void UpdateShapeSize() { // Force to clamp the shape if we changed the type of the light shapeWidth = m_ShapeWidth; shapeHeight = m_ShapeHeight; if (legacyLight.type == LightType.Pyramid) { // Pyramid lights use areaSize.x for aspect ratio. legacyLight.areaSize = new Vector2(aspectRatio, 0); } else if (legacyLight.type != LightType.Disc) { // We don't want to update the disc area since their shape is largely handled by builtin. legacyLight.areaSize = new Vector2(shapeWidth, shapeHeight); } } /// /// Synchronize all the HD Additional Light values with the Light component. /// public void UpdateAllLightValues() { UpdateAllLightValues(false); } internal void UpdateAllLightValues(bool fromTimeLine) { UpdateShapeSize(); // Patch bounds UpdateBounds(); UpdateAreaLightEmissiveMesh(fromTimeLine: fromTimeLine); } internal void RefreshCachedShadow() { bool wentThroughCachedShadowSystem = lightIdxForCachedShadows >= 0; if (wentThroughCachedShadowSystem) HDShadowManager.cachedShadowManager.EvictLight(this, legacyLight.type); RegisterCachedShadowLightOptional(); } #endregion #region User API functions /// /// Set the color of the light. /// /// Color /// Optional color temperature public void SetColor(Color color, float colorTemperature = -1) { if (colorTemperature != -1) { legacyLight.colorTemperature = colorTemperature; useColorTemperature = true; } this.color = color; } /// /// Toggle the usage of color temperature. /// /// public void EnableColorTemperature(bool enable) { useColorTemperature = enable; } /// /// Set light cookie. Note that the texture must have a power of two size. /// /// Cookie texture, must be 2D for Directional, Spot and Area light and Cubemap for Point lights /// area light public void SetCookie(Texture cookie, Vector2 directionalLightCookieSize) { LightType lightType = legacyLight.type; if (lightType.IsArea()) { if (cookie.dimension != TextureDimension.Tex2D) { Debug.LogError("Texture dimension " + cookie.dimension + " is not supported for area lights."); return; } areaLightCookie = cookie; } else { if (lightType == LightType.Point && cookie.dimension != TextureDimension.Cube) { Debug.LogError("Texture dimension " + cookie.dimension + " is not supported for point lights."); return; } else if ((lightType == LightType.Directional || lightType.IsSpot()) && cookie.dimension != TextureDimension.Tex2D) // Only 2D cookie are supported for Directional and Spot lights { Debug.LogError("Texture dimension " + cookie.dimension + " is not supported for Directional/Spot lights."); return; } if (lightType == LightType.Directional) { shapeWidth = directionalLightCookieSize.x; shapeHeight = directionalLightCookieSize.y; } legacyLight.cookie = cookie; } } /// /// Set light cookie. /// /// Cookie texture, must be 2D for Directional, Spot and Area light and Cubemap for Point lights public void SetCookie(Texture cookie) => SetCookie(cookie, Vector2.zero); /// /// Set the spot light angle and inner spot percent. We don't use Light.innerSpotAngle. /// /// inner spot angle in degree /// inner spot angle in percent public void SetSpotAngle(float angle, float innerSpotPercent = 0) { this.legacyLight.spotAngle = angle; this.innerSpotPercent = innerSpotPercent; } /// /// Set the dimmer for light and volumetric light. /// /// Dimmer for the light /// Dimmer for the volumetrics public void SetLightDimmer(float dimmer = 1, float volumetricDimmer = 1) { this.lightDimmer = dimmer; this.volumetricDimmer = volumetricDimmer; } /// /// Enable shadows on a light. /// /// public void EnableShadows(bool enabled) => legacyLight.shadows = enabled ? LightShadows.Soft : LightShadows.None; internal bool ShadowsEnabled() { return legacyLight.shadows != LightShadows.None; } /// /// Set the shadow resolution. /// /// Must be between 16 and 16384 but we will allow 0 to turn off the shadow public void SetShadowResolution(int resolution) { if (shadowResolution.@override != resolution) { shadowResolution.@override = resolution; RefreshCachedShadow(); } } /// /// Set the shadow resolution quality level. /// /// The quality level to use public void SetShadowResolutionLevel(int level) { if (shadowResolution.level != level) { shadowResolution.level = level; RefreshCachedShadow(); } } /// /// Set whether the shadow resolution use the override value. /// /// True to use the override value, false otherwise. public void SetShadowResolutionOverride(bool useOverride) { if (shadowResolution.useOverride != useOverride) { shadowResolution.useOverride = useOverride; RefreshCachedShadow(); } } /// /// Set the near plane of the shadow. /// /// public void SetShadowNearPlane(float nearPlaneDistance) => shadowNearPlane = nearPlaneDistance; /// /// Set parameters for PCSS shadows. /// /// Number of samples used to detect blockers /// Number of samples used to filter the shadow map /// Minimum filter intensity /// Scale applied to shape radius or angular diameter in the softness calculations. public void SetPCSSParams(int blockerSampleCount = 16, int filterSampleCount = 24, float minFilterSize = 0.01f, float radiusScaleForSoftness = 1) { this.blockerSampleCount = blockerSampleCount; this.filterSampleCount = filterSampleCount; this.minFilterSize = minFilterSize; this.softnessScale = radiusScaleForSoftness; } /// /// Set the light layer and shadow map light layer masks. The feature must be enabled in the HDRP asset in norder to work. /// /// Layer mask for receiving light /// Layer mask for shadow rendering public void SetLightLayer(RenderingLayerMask lightLayerMask, RenderingLayerMask shadowLayerMask) { // disable the shadow / light layer link linkShadowLayers = false; legacyLight.renderingLayerMask = LightLayerToRenderingLayerMask((int)shadowLayerMask, (int)legacyLight.renderingLayerMask); lightlayersMask = lightLayerMask; } /// /// Set the shadow dimmer. /// /// Dimmer between 0 and 1 /// Dimmer between 0 and 1 for volumetrics public void SetShadowDimmer(float shadowDimmer = 1, float volumetricShadowDimmer = 1) { this.shadowDimmer = shadowDimmer; this.volumetricShadowDimmer = volumetricShadowDimmer; } /// /// Shadow fade distance in meter. /// /// public void SetShadowFadeDistance(float distance) => shadowFadeDistance = distance; /// /// Set the Shadow tint for the directional light. /// /// public void SetDirectionalShadowTint(Color tint) => shadowTint = tint; /// /// Set the shadow update mode. /// /// public void SetShadowUpdateMode(ShadowUpdateMode updateMode) => shadowUpdateMode = updateMode; // A bunch of function that changes stuff on the legacy light so users don't have to get the // light component which would lead to synchronization problem with ou HD datas. /// /// Set the range of the light. /// /// public void SetRange(float range) => legacyLight.range = range; /// /// Set the shadow map light layer masks. The feature must be enabled in the HDRP asset in norder to work. /// /// public void SetShadowLightLayer(RenderingLayerMask shadowLayerMask) => legacyLight.renderingLayerMask = LightLayerToRenderingLayerMask((int)shadowLayerMask, (int)legacyLight.renderingLayerMask); /// /// Set the light culling mask. /// /// public void SetCullingMask(int cullingMask) => legacyLight.cullingMask = cullingMask; /// /// Set the light layer shadow cull distances. /// /// /// public float[] SetLayerShadowCullDistances(float[] layerShadowCullDistances) => legacyLight.layerShadowCullDistances = layerShadowCullDistances; /// /// Set the area light size. /// /// public void SetAreaLightSize(Vector2 size) { if (legacyLight.type.IsArea()) { m_ShapeWidth = size.x; m_ShapeHeight = size.y; HDLightRenderDatabase.instance.SetShapeWidth(lightEntity, m_ShapeWidth); HDLightRenderDatabase.instance.SetShapeHeight(lightEntity, m_ShapeHeight); UpdateAllLightValues(); } } /// /// Set the box spot light size. /// /// public void SetBoxSpotSize(Vector2 size) { if (legacyLight.type == LightType.Box) { shapeWidth = size.x; shapeHeight = size.y; } } #if UNITY_EDITOR /// [Editor Only] Set the lightmap bake type. public LightmapBakeType lightmapBakeType { get => legacyLight.lightmapBakeType; set => legacyLight.lightmapBakeType = value; } #endif #endregion /// /// Converts a light layer into a rendering layer mask. /// /// Light layer is stored in the first 8 bit of the rendering layer mask. /// /// NOTE: light layers are obsolete, use directly renderingLayerMask. /// /// The light layer, only the first 8 bits will be used. /// Current renderingLayerMask, only the last 24 bits will be used. /// internal static int LightLayerToRenderingLayerMask(int lightLayer, int renderingLayerMask) { var renderingLayerMask_u32 = (uint)renderingLayerMask; var lightLayer_u8 = (byte)lightLayer; return (int)((renderingLayerMask_u32 & 0xFFFFFF00) | lightLayer_u8); } /// /// Converts a renderingLayerMask into a lightLayer. /// /// NOTE: light layers are obsolete, use directly renderingLayerMask. /// /// /// internal static int RenderingLayerMaskToLightLayer(int renderingLayerMask) => (byte)renderingLayerMask; ShadowMapType shadowMapType { get { var lightType = legacyLight.type; return lightType == LightType.Rectangle ? ShadowMapType.AreaLightAtlas : lightType != LightType.Directional ? ShadowMapType.PunctualAtlas : ShadowMapType.CascadedDirectional; } } // TODO: Remove. Use the above property instead. internal ShadowMapType GetShadowMapType(LightType lightType) { return (lightType == LightType.Rectangle) ? ShadowMapType.AreaLightAtlas : lightType != LightType.Directional ? ShadowMapType.PunctualAtlas : ShadowMapType.CascadedDirectional; } internal void UpdateRenderEntity() { //NOTE: do not add members into HDLighRenderData unless this data is strictly required by the GPU lightData. // HDRP requires an intermediate / structure like represnetation of lights so we can parallelize processing. // Because Lights in HDRP are complimented by HDAdditionalLightData, which is a GameObject component, we cant access // this component in burst. Thus every new single member added into a HDLightRenderData struct must be updated to reflect the // state of the game side. Adding members into HDLightRenderData will incur into CPU cost for ProcessLightsForGPU. HDLightRenderDatabase lightEntities = HDLightRenderDatabase.instance; if (!lightEntities.IsValid(lightEntity)) return; ref HDLightRenderData lightRenderData = ref lightEntities.EditLightDataAsRef(lightEntity); lightRenderData.renderingLayerMask = (uint)m_LightlayersMask; lightRenderData.fadeDistance = m_FadeDistance; lightRenderData.distance = m_Distance; lightRenderData.angularDiameter = m_AngularDiameter; lightRenderData.volumetricFadeDistance = m_VolumetricFadeDistance; lightRenderData.includeForRayTracing = m_IncludeForRayTracing; lightRenderData.includeForPathTracing = m_IncludeForPathTracing; lightRenderData.useScreenSpaceShadows = m_UseScreenSpaceShadows; // If we are pure shadowmask, we disable raytraced shadows. #if UNITY_EDITOR if (legacyLight.lightmapBakeType == LightmapBakeType.Mixed) #else if (legacyLight.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed) #endif lightRenderData.useRayTracedShadows = !m_NonLightmappedOnly && m_UseRayTracedShadows; else lightRenderData.useRayTracedShadows = m_UseRayTracedShadows; lightRenderData.colorShadow = m_ColorShadow; lightRenderData.lightDimmer = m_LightDimmer; lightRenderData.volumetricDimmer = m_VolumetricDimmer; lightRenderData.shadowDimmer = m_ShadowDimmer; lightRenderData.shadowFadeDistance = m_ShadowFadeDistance; lightRenderData.volumetricShadowDimmer = m_VolumetricShadowDimmer; lightRenderData.shapeWidth = m_ShapeWidth; lightRenderData.shapeHeight = m_ShapeHeight; lightRenderData.aspectRatio = m_AspectRatio; lightRenderData.innerSpotPercent = m_InnerSpotPercent; lightRenderData.spotIESCutoffPercent = m_SpotIESCutoffPercent; lightRenderData.shapeRadius = m_ShapeRadius; lightRenderData.barnDoorLength = m_BarnDoorLength; lightRenderData.barnDoorAngle = m_BarnDoorAngle; lightRenderData.affectVolumetric = useVolumetric; lightRenderData.affectDiffuse = m_AffectDiffuse; lightRenderData.affectSpecular = m_AffectSpecular; lightRenderData.applyRangeAttenuation = m_ApplyRangeAttenuation; lightRenderData.penumbraTint = m_PenumbraTint; lightRenderData.interactsWithSky = m_InteractsWithSky; lightRenderData.shadowTint = m_ShadowTint; lightEntities.EditAdditionalLightUpdateDataAsRef(lightEntity).Set(this); } internal void CreateHDLightRenderEntity(bool autoDestroy = false) { if (!lightEntity.valid) { HDLightRenderDatabase lightEntities = HDLightRenderDatabase.instance; lightEntity = lightEntities.CreateEntity(autoDestroy); lightEntities.AttachGameObjectData(lightEntity, legacyLight.GetInstanceID(), this, legacyLight.gameObject); } UpdateRenderEntity(); } void OnEnable() { CreateHDLightRenderEntity(); RegisterCachedShadowLightOptional(); SetEmissiveMeshRendererEnabled(true); } /// /// Deserialization callback /// void ISerializationCallbackReceiver.OnAfterDeserialize() { } /// /// Serialization callback /// void ISerializationCallbackReceiver.OnBeforeSerialize() { // When reseting, Light component can be not available (will be called later in Reset) if (m_Light == null || m_Light.Equals(null)) return; UpdateBounds(); } void Reset() => UpdateBounds(); /// Tell if the light is overlapping for the light overlap debug mode internal bool IsOverlapping() { var baking = legacyLight.bakingOutput; bool isOcclusionSeparatelyBaked = baking.occlusionMaskChannel != -1; bool isDirectUsingBakedOcclusion = baking.mixedLightingMode == MixedLightingMode.Shadowmask || baking.mixedLightingMode == MixedLightingMode.Subtractive; return isDirectUsingBakedOcclusion && !isOcclusionSeparatelyBaked; } } // The LateUpdate of HDAdditionalLightData relies on Unity's LateUpdate callback, which comes with significant overhead. // By adding a single static callback to Unity's PlayerLoop, we reduced the per-frame per-light CPU overhead considerably. /// /// LightLateUpdate. /// public static class LightLateUpdate { #if UNITY_EDITOR [UnityEditor.InitializeOnLoadMethod] #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif internal static void Init() { var currentLoopSystem = LowLevel.PlayerLoop.GetCurrentPlayerLoop(); bool found = AppendToPlayerLoopList(typeof(LightLateUpdate), Tick, ref currentLoopSystem, typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate)); LowLevel.PlayerLoop.SetPlayerLoop(currentLoopSystem); } internal static void Tick() { HDAdditionalLightData.TickLateUpdate(); } internal static bool AppendToPlayerLoopList(Type updateType, PlayerLoopSystem.UpdateFunction updateFunction, ref PlayerLoopSystem playerLoop, Type playerLoopSystemType) { if (updateType == null || updateFunction == null || playerLoopSystemType == null) return false; if (playerLoop.type == playerLoopSystemType) { var oldListLength = playerLoop.subSystemList != null ? playerLoop.subSystemList.Length : 0; var newSubsystemList = new PlayerLoopSystem[oldListLength + 1]; for (var i = 0; i < oldListLength; ++i) newSubsystemList[i] = playerLoop.subSystemList[i]; newSubsystemList[oldListLength] = new PlayerLoopSystem { type = updateType, updateDelegate = updateFunction }; playerLoop.subSystemList = newSubsystemList; return true; } if (playerLoop.subSystemList != null) { for (var i = 0; i < playerLoop.subSystemList.Length; ++i) { if (AppendToPlayerLoopList(updateType, updateFunction, ref playerLoop.subSystemList[i], playerLoopSystemType)) return true; } } return false; } } }