You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

3847 lines
154 KiB

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
/// <summary>
/// HDRP Additional light data component. It contains the light API and fields used by HDRP.
/// </summary>
[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;
}
/// <summary>
/// Light source used to shade the celestial body.
/// </summary>
public enum CelestialBodyShadingSource
{
/// <summary>
/// The celestial body will emit light.
/// </summary>
Emission = 1,
/// <summary>
/// The celestial body will reflect light from a directional light in the scene.
/// </summary>
ReflectSunLight = 0,
/// <summary>
/// The celestial body will be illuminated by an artifical light source.
/// </summary>
Manual = 2,
}
/// <summary>
/// The default intensity value for directional lights in Lux
/// </summary>
public const float k_DefaultDirectionalLightIntensity = Mathf.PI; // In lux
/// <summary>
/// The default intensity value for punctual lights in Lumen
/// </summary>
public const float k_DefaultPunctualLightIntensity = 600.0f; // Light default to 600 lumen, i.e ~48 candela
/// <summary>
/// The default intensity value for area lights in Lumen
/// </summary>
public const float k_DefaultAreaLightIntensity = 200.0f; // Light default to 200 lumen to better match point light
/// <summary>
/// Minimum value for the spot light angle
/// </summary>
public const float k_MinSpotAngle = 1.0f;
/// <summary>
/// Maximum value for the spot light angle
/// </summary>
public const float k_MaxSpotAngle = 179.0f;
/// <summary>
/// Minimum aspect ratio for pyramid spot lights
/// </summary>
public const float k_MinAspectRatio = 0.05f;
/// <summary>
/// Maximum aspect ratio for pyramid spot lights
/// </summary>
public const float k_MaxAspectRatio = 20.0f;
/// <summary>
/// Minimum shadow map view bias scale
/// </summary>
public const float k_MinViewBiasScale = 0.0f;
/// <summary>
/// Maximum shadow map view bias scale
/// </summary>
public const float k_MaxViewBiasScale = 15.0f;
/// <summary>
/// Minimum area light size
/// </summary>
public const float k_MinAreaWidth = 0.01f; // Provide a small size of 1cm for line light
/// <summary>
/// Default shadow resolution
/// </summary>
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;
/// <summary>List of the lights that overlaps when the OverlapLight scene view mode is enabled</summary>
internal static HashSet<HDAdditionalLightData> s_overlappingHDLights = new HashSet<HDAdditionalLightData>();
#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
/// <summary>
/// Get/Set the inner spot radius in percent.
/// </summary>
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;
}
}
/// <summary>
/// Get the inner spot radius between 0 and 1.
/// </summary>
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
/// <summary>
/// Get/Set the spot ies cutoff.
/// </summary>
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;
}
}
/// <summary>
/// Get the inner spot radius between 0 and 1.
/// </summary>
public float spotIESCutoffPercent01 => spotIESCutoffPercent / 100f;
[Range(0.0f, 16.0f)]
[SerializeField, FormerlySerializedAs("lightDimmer")]
float m_LightDimmer = 1.0f;
/// <summary>
/// Get/Set the light dimmer / multiplier, between 0 and 16.
/// </summary>
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;
/// <summary>
/// Get/Set the light dimmer / multiplier on volumetric effects, between 0 and 16.
/// </summary>
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;
/// <summary>
/// Get/Set the light fade distance.
/// </summary>
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;
/// <summary>
/// Get/Set the light fade distance for volumetrics.
/// </summary>
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;
/// <summary>
/// Controls whether the light affects the diffuse or not
/// </summary>
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;
/// <summary>
/// Controls whether the light affects the specular or not
/// </summary>
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;
/// <summary>
/// Only used when the shadow masks are enabled, control if the we use ShadowMask or DistanceShadowmask for this light.
/// </summary>
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;
/// <summary>
/// Control the width of an area, a box spot light or a directional light cookie.
/// </summary>
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;
/// <summary>
/// Control the height of an area, a box spot light or a directional light cookie.
/// </summary>
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;
/// <summary>
/// Get/Set the aspect ratio of a pyramid light
/// </summary>
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;
/// <summary>
/// Get/Set the radius of a light
/// </summary>
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;
/// <summary>
/// Get/Set the scale factor applied to shape radius or angular diameter for the softness calculation.
/// </summary>
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
/// <summary>
/// Toggle the custom spot light shadow cone.
/// </summary>
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;
/// <summary>
/// Get/Set the custom spot shadow cone value.
/// </summary>
/// <value></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;
/// <summary>
/// Get/Set the maximum smoothness of a punctual or directional light.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// If enabled, display an emissive mesh rect synchronized with the intensity and color of the light.
/// </summary>
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;
/// <summary>
/// Get/Set cookie texture for area lights.
/// </summary>
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;
/// <summary>
/// IES texture for Point lights.
/// </summary>
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;
}
}
}
/// <summary>
/// IES texture for Spot or Rectangular lights.
/// </summary>
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;
}
}
}
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// Controls if the light is enabled when the camera has the RayTracing frame setting enabled.
/// </summary>
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;
/// <summary>
/// Controls if the light is enabled when the camera has Path Tracing enabled.
/// </summary>
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;
/// <summary>
/// Get/Set area light shadow cone value.
/// </summary>
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;
/// <summary>
/// Controls if we resolve the directional light shadows in screen space (ray tracing only).
/// </summary>
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;
/// <summary>
/// Controls if the directional light affect the Physically Based sky.
/// This have no effect on other skies.
/// </summary>
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;
/// <summary>
/// Angular diameter of the celestial body represented by the light as seen from the camera (in degrees).
/// Used to render the sun/moon disk.
/// </summary>
public float angularDiameter
{
get => m_AngularDiameter;
set
{
if (m_AngularDiameter == value)
return;
m_AngularDiameter = value; // Serialization code clamps
HDLightRenderDatabase.instance.SetAngularDiameter(lightEntity, m_AngularDiameter);
}
}
/// <summary>
/// Angular diameter mode to use.
/// </summary>
[SerializeField, FormerlySerializedAs("m_DiameterMultiplerMode")]
public bool diameterMultiplerMode = false;
/// <summary>
/// Multiplier for the angular diameter of the celestial body used only when rendering the sun disk.
/// </summary>
[SerializeField, Min(0.0f), FormerlySerializedAs("m_DiameterMultiplier")]
public float diameterMultiplier = 1.0f;
/// <summary>
/// Override for the angular diameter of the celestial body used only when rendering the sun disk.
/// </summary>Mode
[SerializeField, Min(0.0f), FormerlySerializedAs("m_DiameterOverride")]
public float diameterOverride = 0.5f;
/// <summary>
/// Shading source of the celestial body.
/// </summary>
[SerializeField, FormerlySerializedAs("m_EmissiveLightSource")]
public CelestialBodyShadingSource celestialBodyShadingSource = CelestialBodyShadingSource.Emission;
/// <summary>
/// The Directional light that should illuminate this celestial body.
/// </summary>
[SerializeField]
public Light sunLightOverride;
/// <summary>
/// Color of the light source.
/// </summary>
[SerializeField]
internal Color sunColor = Color.white;
/// <summary>
/// Intensity of the light source in Lux.
/// </summary>
[SerializeField, Min(0.0f)]
internal float sunIntensity = 130000.0f;
/// <summary>
/// The percentage of moon that receives sunlight.
/// </summary>
[SerializeField, Range(0, 1), FormerlySerializedAs("m_MoonPhase")]
public float moonPhase = 0.2f;
/// <summary>
/// The rotation of the moon phase.
/// </summary>
[SerializeField, Range(0, 360.0f), FormerlySerializedAs("m_MoonPhaseRotation")]
public float moonPhaseRotation = 0.0f;
/// <summary>
/// The intensity of the sunlight reflected from the planet onto the moon.
/// </summary>
[SerializeField, Min(0.0f), FormerlySerializedAs("m_Earthshine")]
public float earthshine = 1.0f;
/// <summary>
/// Size the flare around the celestial body (in degrees).
/// </summary>
[SerializeField, Range(0, 90), FormerlySerializedAs("m_FlareSize")]
public float flareSize = 2.0f;
/// <summary>
/// Tints the flare of the celestial body.
/// </summary>
[SerializeField, FormerlySerializedAs("m_FlareTint")]
public Color flareTint = Color.white;
/// <summary>
/// The falloff rate of flare intensity as the angle from the light increases.
/// </summary>
[SerializeField, Min(0.0f), FormerlySerializedAs("m_FlareFalloff")]
public float flareFalloff = 4.0f;
/// <summary>
/// Intensity of the flare.
/// </summary>
[SerializeField, Range(0, 1)]
public float flareMultiplier = 1.0f;
/// <summary>
/// Texture of the surface of the celestial body. Acts like a multiplier.
/// </summary>
[SerializeField, FormerlySerializedAs("m_SurfaceTexture")]
public Texture surfaceTexture = null;
/// <summary>
/// Tints the surface of the celestial body.
/// </summary>
[SerializeField, FormerlySerializedAs("m_SurfaceTint")]
public Color surfaceTint = Color.white;
[SerializeField, Min(0.0f), FormerlySerializedAs("distance")]
float m_Distance = 150000000000; // Sun to Earth
/// <summary>
/// Distance from the camera to the emissive celestial body represented by the light.
/// </summary>
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;
/// <summary>
/// Controls if we use ray traced shadows.
/// </summary>
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;
/// <summary>
/// Controls the number of sample used for the ray traced shadows.
/// </summary>
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;
/// <summary>
/// Toggle the filtering of ray traced shadows.
/// </summary>
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;
/// <summary>
/// Control the size of the filter used for ray traced shadows
/// </summary>
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;
/// <summary>
/// Angular size of the sun in degree.
/// </summary>
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;
/// <summary>
/// Angular size of the sun in degree.
/// </summary>
public float lightShadowRadius
{
get => m_LightShadowRadius;
set
{
if (m_LightShadowRadius == value)
return;
m_LightShadowRadius = Mathf.Max(value, 0.001f);
}
}
[SerializeField]
bool m_SemiTransparentShadow = false;
/// <summary>
/// Enable semi-transparent shadows on the light.
/// </summary>
public bool semiTransparentShadow
{
get => m_SemiTransparentShadow;
set
{
if (m_SemiTransparentShadow == value)
return;
m_SemiTransparentShadow = value;
}
}
[SerializeField]
bool m_ColorShadow = true;
/// <summary>
/// Enable color shadows on the light.
/// </summary>
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;
/// <summary>
/// Uses the distance to the occluder to improve the shadow denoising.
/// </summary>
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;
/// <summary>
/// Controls the exponent used for EVSM shadows.
/// </summary>
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;
/// <summary>
/// Controls the light leak bias value for EVSM shadows.
/// </summary>
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;
/// <summary>
/// Controls the variance bias used for EVSM shadows.
/// </summary>
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;
/// <summary>
/// Controls the number of blur passes used for EVSM shadows.
/// </summary>
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;
/// <summary>
/// Controls which layer will be affected by this light
/// </summary>
/// <value></value>
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;
/// <summary>
/// Controls if we want to synchronize shadow map light layers and light layers or not.
/// </summary>
public bool linkShadowLayers
{
get => m_LinkShadowLayers;
set => m_LinkShadowLayers = value;
}
/// <summary>
/// Returns a mask of light layers as uint and handle the case of Everything as being 0xFF and not -1
/// </summary>
/// <returns></returns>
public uint GetLightLayers()
{
int value = (int)lightlayersMask;
return value < 0 ? (uint)RenderingLayerMask.Everything : (uint)value;
}
/// <summary>
/// Returns a mask of shadow light layers as uint and handle the case of Everything as being 0xFF and not -1
/// </summary>
/// <returns></returns>
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;
/// <summary>
/// Controls the near plane distance of the shadows.
/// </summary>
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;
/// <summary>
/// Controls the number of samples used to detect blockers for PCSS shadows.
/// </summary>
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;
/// <summary>
/// Controls the number of samples used to filter for PCSS shadows.
/// </summary>
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;
/// <summary>
/// Controls the minimum filter size of PCSS shadows.
/// </summary>
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;
/// <summary>
/// Controls the number of samples used to detect blockers for directional lights PCSS shadows.
/// </summary>
// 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;
/// <summary>
/// Controls the number of samples used to filter for directional lights PCSS shadows.
/// </summary>
// 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
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// Minimum PCSS filter size (in shadowmap texels) to avoid aliasing
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// 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)
/// </summary>
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;
/// <summary>
/// Controls the kernel size for IMSM shadows.
/// </summary>
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;
/// <summary>
/// Controls the light angle for IMSM shadows.
/// </summary>
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;
/// <summary>
/// Controls the max depth bias for IMSM shadows.
/// </summary>
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;
}
}
}
/// <summary>
/// The range of the light.
/// </summary>
/// <value></value>
public float range
{
get => legacyLight.range;
set => legacyLight.range = value;
}
/// <summary>
/// Color of the light.
/// </summary>
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,
};
/// <summary>
/// Retrieve the scalable setting for shadow resolution. Use the SetShadowResolution function to set a custom resolution.
/// </summary>
public IntScalableSettingValue shadowResolution => m_ShadowResolution;
[Range(0.0f, 1.0f)]
[SerializeField]
float m_ShadowDimmer = 1.0f;
/// <summary>
/// Get/Set the shadow dimmer.
/// </summary>
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;
/// <summary>
/// Get/Set the volumetric shadow dimmer value, between 0 and 1.
/// </summary>
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;
/// <summary>
/// Get/Set the shadow fade distance.
/// </summary>
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 };
/// <summary>
/// Retrieve the scalable setting to use/ignore contact shadows. Toggle the use contact shadow using @override property of the ScalableSetting.
/// </summary>
public BoolScalableSettingValue useContactShadow => m_UseContactShadow;
[SerializeField]
bool m_RayTracedContactShadow = false;
/// <summary>
/// Controls if we want to ray trace the contact shadow.
/// </summary>
public bool rayTraceContactShadow
{
get => m_RayTracedContactShadow;
set
{
if (m_RayTracedContactShadow == value)
return;
m_RayTracedContactShadow = value;
}
}
[SerializeField]
Color m_ShadowTint = Color.black;
/// <summary>
/// Controls the tint of the shadows.
/// </summary>
/// <value></value>
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;
/// <summary>
/// Controls if we want to ray trace the contact shadow.
/// </summary>
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;
/// <summary>
/// Get/Set the normal bias of the shadow maps.
/// </summary>
/// <value></value>
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;
/// <summary>
/// Get/Set the slope bias of the shadow maps.
/// </summary>
/// <value></value>
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;
/// <summary>
/// Get/Set the shadow update mode.
/// </summary>
/// <value></value>
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;
/// <summary>
/// Whether cached shadows will always draw dynamic shadow casters.
/// </summary>
/// <value></value>
public bool alwaysDrawDynamicShadows
{
get => m_AlwaysDrawDynamicShadows;
set
{
m_AlwaysDrawDynamicShadows = value;
if (lightEntity.valid)
{
HDLightRenderDatabase.instance.EditAdditionalLightUpdateDataAsRef(lightEntity).alwaysDrawDynamicShadows = value;
}
}
}
[SerializeField]
bool m_UpdateShadowOnLightMovement = false;
/// <summary>
/// Whether a cached shadow map will be automatically updated when the light transform changes (more than a given threshold set via cachedShadowTranslationUpdateThreshold
/// and cachedShadowAngleUpdateThreshold).
/// </summary>
/// <value></value>
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// Get/Set the angle so that it behaves like a barn door.
/// </summary>
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;
/// <summary>
/// Get/Set the length for the barn door sides.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
public bool preserveCachedShadow
{
get => m_preserveCachedShadow;
set
{
if (m_preserveCachedShadow == value)
return;
m_preserveCachedShadow = value;
}
}
[SerializeField]
bool m_OnDemandShadowRenderOnPlacement = true;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// True if the light affects volumetric fog, false otherwise
/// </summary>
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<HDShadowRequest> shadowRequests
{
get
{
UnsafeList<HDShadowRequest> retValue = default;
if (lightEntity.valid)
{
HDLightRenderDatabase lightRenderDatabase = HDLightRenderDatabase.instance;
HDShadowRequestDatabase shadowRequestDatabase = HDShadowRequestDatabase.instance;
NativeList<HDShadowRequest> hdShadowRequestStorage = shadowRequestDatabase.hdShadowRequestStorage;
int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForShadowRequests;
Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < hdShadowRequestStorage.Length);
UnsafeList<HDShadowRequest>* unsafeListPtr = hdShadowRequestStorage.GetUnsafeList();
retValue = new UnsafeList<HDShadowRequest>(unsafeListPtr->Ptr + dataStartIndex, HDShadowRequest.maxLightShadowRequestsCount);
}
return retValue;
}
}
unsafe UnsafeList<int> shadowRequestIndices
{
get
{
UnsafeList<int> retValue = default;
if (lightEntity.valid)
{
HDLightRenderDatabase lightRenderDatabase = HDLightRenderDatabase.instance;
HDShadowRequestDatabase shadowRequestDatabase = HDShadowRequestDatabase.instance;
NativeList<int> hdShadowIndicesStorage = shadowRequestDatabase.hdShadowRequestIndicesStorage;
int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForRequestIndices;
Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < hdShadowIndicesStorage.Length);
UnsafeList<int>* unsafeListPtr = hdShadowIndicesStorage.GetUnsafeList();
retValue = new UnsafeList<int>(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<Vector3> cachedViewPositionsStorage = shadowRequestDatabase.cachedViewPositionsStorage;
int dataStartIndex = lightRenderDatabase.GetShadowRequestSetHandle(lightEntity).storageIndexForCachedViewPositions;
Assert.IsTrue(dataStartIndex >= 0 && dataStartIndex < cachedViewPositionsStorage.Length);
UnsafeList<Vector3>* 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<Light>(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<MeshFilter>();
emissiveMeshRenderer = m_ChildEmissiveMeshViewer.GetComponent<MeshRenderer>();
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<MeshFilter>();
emissiveMeshRenderer = m_ChildEmissiveMeshViewer.GetComponent<MeshRenderer>();
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)
/// <summary> Change the Shadow Casting Mode of the generated emissive mesh for Area Light </summary>
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;
}
}
}
/// <summary> Change the Motion Vector Generation Mode of the generated emissive mesh for Area Light </summary>
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;
}
}
}
/// <summary> Change the Layer of the generated emissive mesh for Area Light </summary>
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;
}
}
}
/// <summary> A callback allowing the creation of a new Matrix4x4 based on the lightLocalToWorld matrix </summary>
public delegate Matrix4x4 CustomViewCallback(Matrix4x4 lightLocalToWorldMatrix);
CustomViewCallback m_CustomViewCallbackEvent;
/// <summary> Change the View matrix for Spot Light </summary>
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;
}
/// <summary>
/// Request shadow map rendering when Update Mode is set to On Demand.
/// </summary>
public void RequestShadowMapRendering()
{
if (shadowUpdateMode == ShadowUpdateMode.OnDemand)
{
HDShadowManager.cachedShadowManager.ScheduleShadowUpdate(this);
}
}
/// <summary>
/// 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).
/// </summary>
/// <param name="shadowIndex">The index of the subshadow to update.</param>
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<Animator>() != 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<HDAdditionalLightData> 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();
}
/// <summary>
/// Copy all field from this to an additional light data
/// </summary>
/// <param name="data">Destination component</param>
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
/// <summary>
/// Initialize an HDAdditionalLightData that have just beeing created.
/// </summary>
/// <param name="lightData"></param>
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<HDRenderPipelineRuntimeAssets>(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);
}
}
/// <summary>
/// Synchronize all the HD Additional Light values with the Light component.
/// </summary>
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
/// <summary>
/// Set the color of the light.
/// </summary>
/// <param name="color">Color</param>
/// <param name="colorTemperature">Optional color temperature</param>
public void SetColor(Color color, float colorTemperature = -1)
{
if (colorTemperature != -1)
{
legacyLight.colorTemperature = colorTemperature;
useColorTemperature = true;
}
this.color = color;
}
/// <summary>
/// Toggle the usage of color temperature.
/// </summary>
/// <param name="enable"></param>
public void EnableColorTemperature(bool enable)
{
useColorTemperature = enable;
}
/// <summary>
/// Set light cookie. Note that the texture must have a power of two size.
/// </summary>
/// <param name="cookie">Cookie texture, must be 2D for Directional, Spot and Area light and Cubemap for Point lights</param>
/// <param name="directionalLightCookieSize">area light </param>
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;
}
}
/// <summary>
/// Set light cookie.
/// </summary>
/// <param name="cookie">Cookie texture, must be 2D for Directional, Spot and Area light and Cubemap for Point lights</param>
public void SetCookie(Texture cookie) => SetCookie(cookie, Vector2.zero);
/// <summary>
/// Set the spot light angle and inner spot percent. We don't use Light.innerSpotAngle.
/// </summary>
/// <param name="angle">inner spot angle in degree</param>
/// <param name="innerSpotPercent">inner spot angle in percent</param>
public void SetSpotAngle(float angle, float innerSpotPercent = 0)
{
this.legacyLight.spotAngle = angle;
this.innerSpotPercent = innerSpotPercent;
}
/// <summary>
/// Set the dimmer for light and volumetric light.
/// </summary>
/// <param name="dimmer">Dimmer for the light</param>
/// <param name="volumetricDimmer">Dimmer for the volumetrics</param>
public void SetLightDimmer(float dimmer = 1, float volumetricDimmer = 1)
{
this.lightDimmer = dimmer;
this.volumetricDimmer = volumetricDimmer;
}
/// <summary>
/// Enable shadows on a light.
/// </summary>
/// <param name="enabled"></param>
public void EnableShadows(bool enabled) => legacyLight.shadows = enabled ? LightShadows.Soft : LightShadows.None;
internal bool ShadowsEnabled()
{
return legacyLight.shadows != LightShadows.None;
}
/// <summary>
/// Set the shadow resolution.
/// </summary>
/// <param name="resolution">Must be between 16 and 16384 but we will allow 0 to turn off the shadow</param>
public void SetShadowResolution(int resolution)
{
if (shadowResolution.@override != resolution)
{
shadowResolution.@override = resolution;
RefreshCachedShadow();
}
}
/// <summary>
/// Set the shadow resolution quality level.
/// </summary>
/// <param name="level">The quality level to use</param>
public void SetShadowResolutionLevel(int level)
{
if (shadowResolution.level != level)
{
shadowResolution.level = level;
RefreshCachedShadow();
}
}
/// <summary>
/// Set whether the shadow resolution use the override value.
/// </summary>
/// <param name="useOverride">True to use the override value, false otherwise.</param>
public void SetShadowResolutionOverride(bool useOverride)
{
if (shadowResolution.useOverride != useOverride)
{
shadowResolution.useOverride = useOverride;
RefreshCachedShadow();
}
}
/// <summary>
/// Set the near plane of the shadow.
/// </summary>
/// <param name="nearPlaneDistance"></param>
public void SetShadowNearPlane(float nearPlaneDistance) => shadowNearPlane = nearPlaneDistance;
/// <summary>
/// Set parameters for PCSS shadows.
/// </summary>
/// <param name="blockerSampleCount">Number of samples used to detect blockers</param>
/// <param name="filterSampleCount">Number of samples used to filter the shadow map</param>
/// <param name="minFilterSize">Minimum filter intensity</param>
/// <param name="radiusScaleForSoftness">Scale applied to shape radius or angular diameter in the softness calculations.</param>
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;
}
/// <summary>
/// Set the light layer and shadow map light layer masks. The feature must be enabled in the HDRP asset in norder to work.
/// </summary>
/// <param name="lightLayerMask">Layer mask for receiving light</param>
/// <param name="shadowLayerMask">Layer mask for shadow rendering</param>
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;
}
/// <summary>
/// Set the shadow dimmer.
/// </summary>
/// <param name="shadowDimmer">Dimmer between 0 and 1</param>
/// <param name="volumetricShadowDimmer">Dimmer between 0 and 1 for volumetrics</param>
public void SetShadowDimmer(float shadowDimmer = 1, float volumetricShadowDimmer = 1)
{
this.shadowDimmer = shadowDimmer;
this.volumetricShadowDimmer = volumetricShadowDimmer;
}
/// <summary>
/// Shadow fade distance in meter.
/// </summary>
/// <param name="distance"></param>
public void SetShadowFadeDistance(float distance) => shadowFadeDistance = distance;
/// <summary>
/// Set the Shadow tint for the directional light.
/// </summary>
/// <param name="tint"></param>
public void SetDirectionalShadowTint(Color tint) => shadowTint = tint;
/// <summary>
/// Set the shadow update mode.
/// </summary>
/// <param name="updateMode"></param>
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.
/// <summary>
/// Set the range of the light.
/// </summary>
/// <param name="range"></param>
public void SetRange(float range) => legacyLight.range = range;
/// <summary>
/// Set the shadow map light layer masks. The feature must be enabled in the HDRP asset in norder to work.
/// </summary>
/// <param name="shadowLayerMask"></param>
public void SetShadowLightLayer(RenderingLayerMask shadowLayerMask) => legacyLight.renderingLayerMask = LightLayerToRenderingLayerMask((int)shadowLayerMask, (int)legacyLight.renderingLayerMask);
/// <summary>
/// Set the light culling mask.
/// </summary>
/// <param name="cullingMask"></param>
public void SetCullingMask(int cullingMask) => legacyLight.cullingMask = cullingMask;
/// <summary>
/// Set the light layer shadow cull distances.
/// </summary>
/// <param name="layerShadowCullDistances"></param>
/// <returns></returns>
public float[] SetLayerShadowCullDistances(float[] layerShadowCullDistances) => legacyLight.layerShadowCullDistances = layerShadowCullDistances;
/// <summary>
/// Set the area light size.
/// </summary>
/// <param name="size"></param>
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();
}
}
/// <summary>
/// Set the box spot light size.
/// </summary>
/// <param name="size"></param>
public void SetBoxSpotSize(Vector2 size)
{
if (legacyLight.type == LightType.Box)
{
shapeWidth = size.x;
shapeHeight = size.y;
}
}
#if UNITY_EDITOR
/// <summary> [Editor Only] Set the lightmap bake type. </summary>
public LightmapBakeType lightmapBakeType
{
get => legacyLight.lightmapBakeType;
set => legacyLight.lightmapBakeType = value;
}
#endif
#endregion
/// <summary>
/// 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.
/// </summary>
/// <param name="lightLayer">The light layer, only the first 8 bits will be used.</param>
/// <param name="renderingLayerMask">Current renderingLayerMask, only the last 24 bits will be used.</param>
/// <returns></returns>
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);
}
/// <summary>
/// Converts a renderingLayerMask into a lightLayer.
///
/// NOTE: light layers are obsolete, use directly renderingLayerMask.
/// </summary>
/// <param name="renderingLayerMask"></param>
/// <returns></returns>
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.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);
}
/// <summary>
/// Deserialization callback
/// </summary>
void ISerializationCallbackReceiver.OnAfterDeserialize() { }
/// <summary>
/// Serialization callback
/// </summary>
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();
/// <summary>Tell if the light is overlapping for the light overlap debug mode</summary>
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.
/// <summary>
/// LightLateUpdate.
/// </summary>
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;
}
}
}