3470 lines
136 KiB
C#
3470 lines
136 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using JetBrains.Annotations;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using UnityEngine.Serialization;
|
|
|
|
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 float oldIntensity;
|
|
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>
|
|
[HelpURL(Documentation.baseURL + Documentation.version + Documentation.subURL + "Light-Component" + Documentation.endURL)]
|
|
[AddComponentMenu("")] // Hide in menu
|
|
[RequireComponent(typeof(Light))]
|
|
[ExecuteAlways]
|
|
public partial class HDAdditionalLightData : MonoBehaviour, ISerializationCallbackReceiver
|
|
{
|
|
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>
|
|
/// 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
|
|
|
|
[SerializeField, FormerlySerializedAs("displayLightIntensity")]
|
|
float m_Intensity;
|
|
/// <summary>
|
|
/// Get/Set the intensity of the light using the current light unit.
|
|
/// </summary>
|
|
public float intensity
|
|
{
|
|
get => m_Intensity;
|
|
set
|
|
{
|
|
if (m_Intensity == value)
|
|
return;
|
|
|
|
m_Intensity = Mathf.Clamp(value, 0, float.MaxValue);
|
|
UpdateLightIntensity();
|
|
}
|
|
}
|
|
|
|
// Only for Spotlight, should be hide for other light
|
|
[SerializeField, FormerlySerializedAs("enableSpotReflector")]
|
|
bool m_EnableSpotReflector = true;
|
|
/// <summary>
|
|
/// Get/Set the Spot Reflection option on spot lights.
|
|
/// </summary>
|
|
public bool enableSpotReflector
|
|
{
|
|
get => m_EnableSpotReflector;
|
|
set
|
|
{
|
|
if (m_EnableSpotReflector == value)
|
|
return;
|
|
|
|
m_EnableSpotReflector = value;
|
|
UpdateLightIntensity();
|
|
}
|
|
}
|
|
|
|
// Lux unity for all light except directional require a distance
|
|
[SerializeField, FormerlySerializedAs("luxAtDistance")]
|
|
float m_LuxAtDistance = 1.0f;
|
|
/// <summary>
|
|
/// Set/Get the distance for spot lights where the emission intensity is matches the value set in the intensity property.
|
|
/// </summary>
|
|
public float luxAtDistance
|
|
{
|
|
get => m_LuxAtDistance;
|
|
set
|
|
{
|
|
if (m_LuxAtDistance == value)
|
|
return;
|
|
|
|
m_LuxAtDistance = Mathf.Clamp(value, 0, float.MaxValue);
|
|
UpdateLightIntensity();
|
|
}
|
|
}
|
|
|
|
[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);
|
|
UpdateLightIntensity();
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
// Used internally to convert any light unit input into light intensity
|
|
[SerializeField, FormerlySerializedAs("lightUnit")]
|
|
LightUnit m_LightUnit = LightUnit.Lumen;
|
|
/// <summary>
|
|
/// Get/Set the light unit. When changing the light unit, the intensity will be converted to match the previous intensity in the new unit.
|
|
/// </summary>
|
|
public LightUnit lightUnit
|
|
{
|
|
get => m_LightUnit;
|
|
set
|
|
{
|
|
if (m_LightUnit == value)
|
|
return;
|
|
|
|
if (!IsValidLightUnitForType(type, m_SpotLightShape, value))
|
|
{
|
|
var supportedTypes = String.Join(", ", GetSupportedLightUnits(type, m_SpotLightShape));
|
|
Debug.LogError($"Set Light Unit '{value}' to a {GetLightTypeName()} is not allowed, only {supportedTypes} are supported.");
|
|
return;
|
|
}
|
|
|
|
LightUtils.ConvertLightIntensity(m_LightUnit, value, this, legacyLight);
|
|
|
|
m_LightUnit = value;
|
|
UpdateLightIntensity();
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
if (type == HDLightType.Area)
|
|
m_ShapeWidth = Mathf.Clamp(value, k_MinAreaWidth, float.MaxValue);
|
|
else
|
|
m_ShapeWidth = Mathf.Clamp(value, 0, float.MaxValue);
|
|
UpdateAllLightValues();
|
|
}
|
|
}
|
|
|
|
// 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 (type == HDLightType.Area)
|
|
m_ShapeHeight = Mathf.Clamp(value, k_MinAreaWidth, float.MaxValue);
|
|
else
|
|
m_ShapeHeight = Mathf.Clamp(value, 0, float.MaxValue);
|
|
UpdateAllLightValues();
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
[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();
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// 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>
|
|
/// Get/Set IES texture for Point
|
|
/// </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>
|
|
/// Get/Set IES texture for Spot or rectangular light.
|
|
/// </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;
|
|
}
|
|
}
|
|
}
|
|
|
|
[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;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
{
|
|
get => m_InteractsWithSky;
|
|
set
|
|
{
|
|
if (m_InteractsWithSky == value)
|
|
return;
|
|
|
|
m_InteractsWithSky = value;
|
|
}
|
|
}
|
|
[SerializeField, FormerlySerializedAs("angularDiameter")]
|
|
float m_AngularDiameter = 0.5f;
|
|
/// <summary>
|
|
/// Angular diameter of the emissive 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
|
|
}
|
|
}
|
|
|
|
[SerializeField, FormerlySerializedAs("flareSize")]
|
|
float m_FlareSize = 2.0f;
|
|
/// <summary>
|
|
/// Size the flare around the celestial body (in degrees).
|
|
/// </summary>
|
|
public float flareSize
|
|
{
|
|
get => m_FlareSize;
|
|
set
|
|
{
|
|
if (m_FlareSize == value)
|
|
return;
|
|
|
|
m_FlareSize = value; // Serialization code clamps
|
|
}
|
|
}
|
|
|
|
[SerializeField, FormerlySerializedAs("flareTint")]
|
|
Color m_FlareTint = Color.white;
|
|
/// <summary>
|
|
/// Tints the flare of the celestial body.
|
|
/// </summary>
|
|
public Color flareTint
|
|
{
|
|
get => m_FlareTint;
|
|
set
|
|
{
|
|
if (m_FlareTint == value)
|
|
return;
|
|
|
|
m_FlareTint = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField, FormerlySerializedAs("flareFalloff")]
|
|
float m_FlareFalloff = 4.0f;
|
|
/// <summary>
|
|
/// The falloff rate of flare intensity as the angle from the light increases.
|
|
/// </summary>
|
|
public float flareFalloff
|
|
{
|
|
get => m_FlareFalloff;
|
|
set
|
|
{
|
|
if (m_FlareFalloff == value)
|
|
return;
|
|
|
|
m_FlareFalloff = value; // Serialization code clamps
|
|
}
|
|
}
|
|
|
|
[SerializeField, FormerlySerializedAs("surfaceTexture")]
|
|
Texture2D m_SurfaceTexture = null;
|
|
/// <summary>
|
|
/// 2D (disk) texture of the surface of the celestial body. Acts like a multiplier.
|
|
/// </summary>
|
|
public Texture2D surfaceTexture
|
|
{
|
|
get => m_SurfaceTexture;
|
|
set
|
|
{
|
|
if (m_SurfaceTexture == value)
|
|
return;
|
|
|
|
m_SurfaceTexture = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField, FormerlySerializedAs("surfaceTint")]
|
|
Color m_SurfaceTint = Color.white;
|
|
/// <summary>
|
|
/// Tints the surface of the celestial body.
|
|
/// </summary>
|
|
public Color surfaceTint
|
|
{
|
|
get => m_SurfaceTint;
|
|
set
|
|
{
|
|
if (m_SurfaceTint == value)
|
|
return;
|
|
|
|
m_SurfaceTint = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField, 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
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
// Now the renderingLayerMask is used for shadow layers and not light layers
|
|
[SerializeField, FormerlySerializedAs("lightlayersMask")]
|
|
LightLayerEnum m_LightlayersMask = LightLayerEnum.LightLayerDefault;
|
|
/// <summary>
|
|
/// Controls which layer will be affected by this light
|
|
/// </summary>
|
|
/// <value></value>
|
|
public LightLayerEnum lightlayersMask
|
|
{
|
|
get => linkShadowLayers ? (LightLayerEnum)RenderingLayerMaskToLightLayer(legacyLight.renderingLayerMask) : m_LightlayersMask;
|
|
set
|
|
{
|
|
m_LightlayersMask = value;
|
|
|
|
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)LightLayerEnum.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)LightLayerEnum.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, HDShadowUtils.k_MinShadowNearPlane, HDShadowUtils.k_MaxShadowNearPlane);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
|
|
if (m_ShadowUpdateMode != ShadowUpdateMode.EveryFrame && value == ShadowUpdateMode.EveryFrame)
|
|
{
|
|
if (!preserveCachedShadow)
|
|
{
|
|
HDShadowManager.cachedShadowManager.EvictLight(this);
|
|
}
|
|
}
|
|
else if (legacyLight.shadows != LightShadows.None && m_ShadowUpdateMode == ShadowUpdateMode.EveryFrame && value != ShadowUpdateMode.EveryFrame)
|
|
{
|
|
HDShadowManager.cachedShadowManager.RegisterLight(this);
|
|
}
|
|
|
|
m_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; }
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
[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 onDomandShadowRenderOnPlacement
|
|
{
|
|
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 onDomandShadowRenderOnPlacement 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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal API for moving shadow datas from AdditionalShadowData to HDAdditionalLightData
|
|
|
|
[SerializeField]
|
|
float[] m_ShadowCascadeRatios = new float[3] { 0.05f, 0.2f, 0.3f };
|
|
internal float[] shadowCascadeRatios
|
|
{
|
|
get => m_ShadowCascadeRatios;
|
|
set => m_ShadowCascadeRatios = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
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;
|
|
[SerializeField, FormerlySerializedAs("showAdditionalSettings")]
|
|
byte showAdditionalSettings = 0;
|
|
#pragma warning restore 0414
|
|
|
|
HDShadowRequest[] shadowRequests;
|
|
bool m_WillRenderShadowMap;
|
|
bool m_WillRenderScreenSpaceShadow;
|
|
bool m_WillRenderRayTracedShadow;
|
|
int[] m_ShadowRequestIndices;
|
|
|
|
|
|
// Data for cached shadow maps
|
|
[System.NonSerialized]
|
|
internal int lightIdxForCachedShadows = -1;
|
|
|
|
Vector3[] m_CachedViewPositions;
|
|
|
|
|
|
[System.NonSerialized]
|
|
Plane[] m_ShadowFrustumPlanes = new Plane[6];
|
|
|
|
// temporary matrix that stores the previous light data (mainly used to discard history for ray traced screen space shadows)
|
|
[System.NonSerialized] internal Matrix4x4 previousTransform = Matrix4x4.identity;
|
|
// Temporary index that stores the current shadow index for the light
|
|
[System.NonSerialized] internal int shadowIndex = -1;
|
|
|
|
// Runtime datas used to compute light intensity
|
|
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;
|
|
}
|
|
}
|
|
|
|
const string k_EmissiveMeshViewerName = "EmissiveMeshViewer";
|
|
|
|
GameObject m_ChildEmissiveMeshViewer;
|
|
MeshFilter m_EmissiveMeshFilter;
|
|
internal MeshRenderer emissiveMeshRenderer { get; private set; }
|
|
|
|
#if UNITY_EDITOR
|
|
bool m_NeedsPrefabInstanceCheck = false;
|
|
bool needRefreshPrefabInstanceEmissiveMeshes = false;
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OnDestroy()
|
|
{
|
|
if (lightIdxForCachedShadows >= 0) // If it is within the cached system we need to evict it.
|
|
HDShadowManager.cachedShadowManager.EvictLight(this);
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
// If it is within the cached system we need to evict it, unless user explicitly requires not to.
|
|
if (!preserveCachedShadow && lightIdxForCachedShadows >= 0)
|
|
{
|
|
HDShadowManager.cachedShadowManager.EvictLight(this);
|
|
}
|
|
|
|
SetEmissiveMeshRendererEnabled(false);
|
|
s_overlappingHDLights.Remove(this);
|
|
}
|
|
|
|
void SetEmissiveMeshRendererEnabled(bool enabled)
|
|
{
|
|
if (displayAreaLightEmissiveMesh && emissiveMeshRenderer)
|
|
{
|
|
emissiveMeshRenderer.enabled = enabled;
|
|
}
|
|
}
|
|
|
|
int GetShadowRequestCount(HDShadowSettings shadowSettings, HDLightType lightType)
|
|
{
|
|
return lightType == HDLightType.Point
|
|
? 6
|
|
: lightType == HDLightType.Directional
|
|
? shadowSettings.cascadeShadowSplitCount.value
|
|
: 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;
|
|
}
|
|
|
|
internal ShadowMapUpdateType GetShadowUpdateType(HDLightType lightType)
|
|
{
|
|
if (ShadowIsUpdatedEveryFrame()) return ShadowMapUpdateType.Dynamic;
|
|
#if UNITY_2021_1_OR_NEWER
|
|
// Note: For now directional are not supported as it will require extra memory budget. This will change in a near future.
|
|
if (m_AlwaysDrawDynamicShadows && lightType != HDLightType.Directional) return ShadowMapUpdateType.Mixed;
|
|
#endif
|
|
return ShadowMapUpdateType.Cached;
|
|
}
|
|
|
|
internal void EvaluateShadowState(HDCamera hdCamera, in ProcessedLightData processedLight, CullingResults cullResults, FrameSettings frameSettings, int lightIndex)
|
|
{
|
|
Bounds bounds;
|
|
|
|
m_WillRenderShadowMap = legacyLight.shadows != LightShadows.None && frameSettings.IsEnabled(FrameSettingsField.ShadowMaps);
|
|
|
|
m_WillRenderShadowMap &= cullResults.GetShadowCasterBounds(lightIndex, out bounds);
|
|
// When creating a new light, at the first frame, there is no AdditionalShadowData so we can't really render shadows
|
|
m_WillRenderShadowMap &= shadowDimmer > 0;
|
|
// If the shadow is too far away, we don't render it
|
|
m_WillRenderShadowMap &= processedLight.lightType == HDLightType.Directional || processedLight.distanceToCamera < shadowFadeDistance;
|
|
|
|
if (processedLight.lightType == HDLightType.Area && areaLightShape != AreaLightShape.Rectangle)
|
|
{
|
|
m_WillRenderShadowMap = false;
|
|
}
|
|
|
|
// First we reset the ray tracing and screen space shadow data
|
|
m_WillRenderScreenSpaceShadow = false;
|
|
m_WillRenderRayTracedShadow = false;
|
|
|
|
// If this camera does not allow screen space shadows we are done, set the target parameters to false and leave the function
|
|
if (!hdCamera.frameSettings.IsEnabled(FrameSettingsField.ScreenSpaceShadows) || !m_WillRenderShadowMap)
|
|
return;
|
|
|
|
// Flag the ray tracing only shadows
|
|
if (frameSettings.IsEnabled(FrameSettingsField.RayTracing) && m_UseRayTracedShadows)
|
|
{
|
|
bool validShadow = false;
|
|
if (processedLight.gpuLightType == GPULightType.Point
|
|
|| processedLight.gpuLightType == GPULightType.Rectangle
|
|
|| (processedLight.gpuLightType == GPULightType.Spot && processedLight.lightVolumeType == LightVolumeType.Cone))
|
|
{
|
|
validShadow = true;
|
|
}
|
|
|
|
if (validShadow)
|
|
{
|
|
m_WillRenderScreenSpaceShadow = true;
|
|
m_WillRenderRayTracedShadow = true;
|
|
}
|
|
}
|
|
|
|
// Flag the directional shadow
|
|
if (useScreenSpaceShadows && processedLight.gpuLightType == GPULightType.Directional)
|
|
{
|
|
m_WillRenderScreenSpaceShadow = true;
|
|
if (frameSettings.IsEnabled(FrameSettingsField.RayTracing) && m_UseRayTracedShadows)
|
|
{
|
|
m_WillRenderRayTracedShadow = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal int GetResolutionFromSettings(ShadowMapType shadowMapType, HDShadowInitParameters initParameters)
|
|
{
|
|
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 void ReserveShadowMap(Camera camera, HDShadowManager shadowManager, HDShadowSettings shadowSettings, HDShadowInitParameters initParameters, VisibleLight visibleLight, HDLightType lightType)
|
|
{
|
|
if (!m_WillRenderShadowMap)
|
|
return;
|
|
|
|
// Create shadow requests array using the light type
|
|
if (shadowRequests == null || m_ShadowRequestIndices == null || m_CachedViewPositions == null)
|
|
{
|
|
const int maxLightShadowRequestsCount = 6;
|
|
shadowRequests = new HDShadowRequest[maxLightShadowRequestsCount];
|
|
m_ShadowRequestIndices = new int[maxLightShadowRequestsCount];
|
|
m_CachedViewPositions = new Vector3[maxLightShadowRequestsCount];
|
|
|
|
for (int i = 0; i < maxLightShadowRequestsCount; i++)
|
|
{
|
|
shadowRequests[i] = new HDShadowRequest();
|
|
}
|
|
}
|
|
|
|
ShadowMapType shadowType = GetShadowMapType(lightType);
|
|
|
|
// Reserve wanted resolution in the shadow atlas
|
|
int resolution = GetResolutionFromSettings(shadowType, initParameters);
|
|
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 == HDLightType.Directional)
|
|
shadowManager.UpdateDirectionalShadowResolution((int)viewportSize.x, shadowSettings.cascadeShadowSplitCount.value);
|
|
|
|
int count = GetShadowRequestCount(shadowSettings, lightType);
|
|
|
|
var updateType = GetShadowUpdateType(lightType);
|
|
for (int index = 0; index < count; index++)
|
|
{
|
|
m_ShadowRequestIndices[index] = shadowManager.ReserveShadowResolutions(shadowIsInCacheSystem ? new Vector2(resolution, resolution) : viewportSize, shadowMapType, GetInstanceID(), index, updateType);
|
|
}
|
|
}
|
|
|
|
internal bool WillRenderShadowMap()
|
|
{
|
|
return m_WillRenderShadowMap;
|
|
}
|
|
|
|
internal bool WillRenderScreenSpaceShadow()
|
|
{
|
|
return m_WillRenderScreenSpaceShadow;
|
|
}
|
|
|
|
internal bool WillRenderRayTracedShadow()
|
|
{
|
|
return m_WillRenderRayTracedShadow;
|
|
}
|
|
|
|
// 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 rectangleDiagonal = shapeSize.magnitude;
|
|
float halfAngle = coneAngle * 0.5f;
|
|
float cotanHalfAngle = 1.0f / Mathf.Tan(halfAngle * Mathf.Deg2Rad);
|
|
float offset = rectangleDiagonal * cotanHalfAngle;
|
|
|
|
return -offset;
|
|
}
|
|
|
|
private void UpdateDirectionalShadowRequest(HDShadowManager manager, HDShadowSettings shadowSettings, VisibleLight visibleLight, CullingResults cullResults, Vector2 viewportSize, int requestIndex, int lightIndex, Vector3 cameraPos, HDShadowRequest shadowRequest, out Matrix4x4 invViewProjection)
|
|
{
|
|
Vector4 cullingSphere;
|
|
float nearPlaneOffset = QualitySettings.shadowNearPlaneOffset;
|
|
|
|
HDShadowUtils.ExtractDirectionalLightData(
|
|
visibleLight, viewportSize, (uint)requestIndex, shadowSettings.cascadeShadowSplitCount.value,
|
|
shadowSettings.cascadeShadowSplits, nearPlaneOffset, cullResults, lightIndex,
|
|
out shadowRequest.view, out invViewProjection, out shadowRequest.projection,
|
|
out shadowRequest.deviceProjection, out shadowRequest.deviceProjectionYFlip, out shadowRequest.splitData
|
|
);
|
|
|
|
cullingSphere = shadowRequest.splitData.cullingSphere;
|
|
|
|
// Camera relative for directional light culling sphere
|
|
if (ShaderConfig.s_CameraRelativeRendering != 0)
|
|
{
|
|
cullingSphere.x -= cameraPos.x;
|
|
cullingSphere.y -= cameraPos.y;
|
|
cullingSphere.z -= cameraPos.z;
|
|
}
|
|
|
|
manager.UpdateCascade(requestIndex, cullingSphere, shadowSettings.cascadeShadowBorders[requestIndex]);
|
|
}
|
|
|
|
internal void UpdateShadowRequestData(HDCamera hdCamera, HDShadowManager manager, HDShadowSettings shadowSettings, VisibleLight visibleLight,
|
|
CullingResults cullResults, int lightIndex, LightingDebugSettings lightingDebugSettings, HDShadowFilteringQuality filteringQuality,
|
|
Vector2 viewportSize, HDLightType lightType, int shadowIndex, ref HDShadowRequest shadowRequest)
|
|
{
|
|
Matrix4x4 invViewProjection = Matrix4x4.identity;
|
|
Vector3 cameraPos = hdCamera.mainViewConstants.worldSpaceCameraPos;
|
|
|
|
// Write per light type matrices, splitDatas and culling parameters
|
|
switch (lightType)
|
|
{
|
|
case HDLightType.Point:
|
|
HDShadowUtils.ExtractPointLightData(
|
|
visibleLight, viewportSize, shadowNearPlane,
|
|
normalBias, (uint)shadowIndex, filteringQuality, out shadowRequest.view,
|
|
out invViewProjection, out shadowRequest.projection,
|
|
out shadowRequest.deviceProjection, out shadowRequest.deviceProjectionYFlip, out shadowRequest.splitData
|
|
);
|
|
break;
|
|
case HDLightType.Spot:
|
|
float spotAngleForShadows = useCustomSpotLightShadowCone ? Math.Min(customSpotLightShadowCone, visibleLight.light.spotAngle) : visibleLight.light.spotAngle;
|
|
HDShadowUtils.ExtractSpotLightData(
|
|
spotLightShape, spotAngleForShadows, shadowNearPlane, aspectRatio, shapeWidth,
|
|
shapeHeight, visibleLight, viewportSize, normalBias, filteringQuality,
|
|
out shadowRequest.view, out invViewProjection, out shadowRequest.projection,
|
|
out shadowRequest.deviceProjection, out shadowRequest.deviceProjectionYFlip, out shadowRequest.splitData
|
|
);
|
|
break;
|
|
case HDLightType.Directional:
|
|
UpdateDirectionalShadowRequest(manager, shadowSettings, visibleLight, cullResults, viewportSize, shadowIndex, lightIndex, cameraPos, shadowRequest, out invViewProjection);
|
|
break;
|
|
case HDLightType.Area:
|
|
switch (areaLightShape)
|
|
{
|
|
case AreaLightShape.Rectangle:
|
|
Vector2 shapeSize = new Vector2(shapeWidth, m_ShapeHeight);
|
|
float offset = GetAreaLightOffsetForShadows(shapeSize, areaLightShadowCone);
|
|
Vector3 shadowOffset = offset * visibleLight.GetForward();
|
|
HDShadowUtils.ExtractRectangleAreaLightData(visibleLight, visibleLight.GetPosition() + shadowOffset, areaLightShadowCone, shadowNearPlane, shapeSize, viewportSize, normalBias, filteringQuality,
|
|
out shadowRequest.view, out invViewProjection, out shadowRequest.projection, out shadowRequest.deviceProjection, out shadowRequest.deviceProjectionYFlip, out shadowRequest.splitData);
|
|
break;
|
|
case AreaLightShape.Tube:
|
|
//Tube do not cast shadow at the moment.
|
|
//They should not call this method.
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Assign all setting common to every lights
|
|
SetCommonShadowRequestSettings(shadowRequest, visibleLight, cameraPos, invViewProjection, viewportSize, lightIndex, lightType, filteringQuality);
|
|
}
|
|
|
|
internal int UpdateShadowRequest(HDCamera hdCamera, HDShadowManager manager, HDShadowSettings shadowSettings, VisibleLight visibleLight,
|
|
CullingResults cullResults, int lightIndex, LightingDebugSettings lightingDebugSettings, HDShadowFilteringQuality filteringQuality, out int shadowRequestCount)
|
|
{
|
|
int firstShadowRequestIndex = -1;
|
|
Vector3 cameraPos = hdCamera.mainViewConstants.worldSpaceCameraPos;
|
|
shadowRequestCount = 0;
|
|
|
|
HDLightType lightType = type;
|
|
|
|
int count = GetShadowRequestCount(shadowSettings, lightType);
|
|
var updateType = GetShadowUpdateType(lightType);
|
|
bool hasCachedComponent = !ShadowIsUpdatedEveryFrame();
|
|
bool isSampledFromCache = (updateType == ShadowMapUpdateType.Cached);
|
|
|
|
bool needsRenderingDueToTransformChange = false;
|
|
// Note if we are in cached system, but if a placement has not been found by this point we bail out shadows
|
|
bool shadowHasAtlasPlacement = true;
|
|
if (hasCachedComponent)
|
|
{
|
|
// If we force evicted the light, it will have lightIdxForCachedShadows == -1
|
|
shadowHasAtlasPlacement = !HDShadowManager.cachedShadowManager.LightIsPendingPlacement(this, shadowMapType) && (lightIdxForCachedShadows != -1);
|
|
needsRenderingDueToTransformChange = HDShadowManager.cachedShadowManager.NeedRenderingDueToTransformChange(this, lightType);
|
|
}
|
|
|
|
for (int index = 0; index < count; index++)
|
|
{
|
|
var shadowRequest = shadowRequests[index];
|
|
|
|
Matrix4x4 invViewProjection = Matrix4x4.identity;
|
|
int shadowRequestIndex = m_ShadowRequestIndices[index];
|
|
|
|
HDShadowResolutionRequest resolutionRequest = manager.GetResolutionRequest(shadowRequestIndex);
|
|
|
|
if (resolutionRequest == null)
|
|
continue;
|
|
|
|
int cachedShadowID = lightIdxForCachedShadows + index;
|
|
bool needToUpdateCachedContent = false;
|
|
bool needToUpdateDynamicContent = !isSampledFromCache;
|
|
bool hasUpdatedRequestData = false;
|
|
|
|
if (hasCachedComponent && shadowHasAtlasPlacement)
|
|
{
|
|
needToUpdateCachedContent = needsRenderingDueToTransformChange || HDShadowManager.cachedShadowManager.ShadowIsPendingUpdate(cachedShadowID, shadowMapType);
|
|
HDShadowManager.cachedShadowManager.UpdateResolutionRequest(ref resolutionRequest, cachedShadowID, shadowMapType);
|
|
}
|
|
|
|
shadowRequest.isInCachedAtlas = isSampledFromCache;
|
|
shadowRequest.isMixedCached = updateType == ShadowMapUpdateType.Mixed;
|
|
shadowRequest.shouldUseCachedShadowData = false;
|
|
|
|
Vector2 viewportSize = resolutionRequest.resolution;
|
|
|
|
if (shadowRequestIndex == -1)
|
|
continue;
|
|
|
|
shadowRequest.dynamicAtlasViewport = resolutionRequest.dynamicAtlasViewport;
|
|
shadowRequest.cachedAtlasViewport = resolutionRequest.cachedAtlasViewport;
|
|
|
|
if (needToUpdateCachedContent)
|
|
{
|
|
m_CachedViewPositions[index] = cameraPos;
|
|
shadowRequest.cachedShadowData.cacheTranslationDelta = new Vector3(0.0f, 0.0f, 0.0f);
|
|
|
|
// Write per light type matrices, splitDatas and culling parameters
|
|
UpdateShadowRequestData(hdCamera, manager, shadowSettings, visibleLight, cullResults, lightIndex, lightingDebugSettings, filteringQuality, viewportSize, lightType, index, ref shadowRequest);
|
|
|
|
hasUpdatedRequestData = true;
|
|
shadowRequest.shouldUseCachedShadowData = false;
|
|
shadowRequest.shouldRenderCachedComponent = true;
|
|
}
|
|
else if (hasCachedComponent)
|
|
{
|
|
shadowRequest.cachedShadowData.cacheTranslationDelta = cameraPos - m_CachedViewPositions[index];
|
|
shadowRequest.shouldUseCachedShadowData = true;
|
|
shadowRequest.shouldRenderCachedComponent = false;
|
|
// If directional we still need to calculate the split data.
|
|
if (lightType == HDLightType.Directional)
|
|
UpdateDirectionalShadowRequest(manager, shadowSettings, visibleLight, cullResults, viewportSize, index, lightIndex, cameraPos, shadowRequest, out invViewProjection);
|
|
}
|
|
|
|
if (needToUpdateDynamicContent && !hasUpdatedRequestData)
|
|
{
|
|
shadowRequest.shouldUseCachedShadowData = false;
|
|
|
|
shadowRequest.cachedShadowData.cacheTranslationDelta = new Vector3(0.0f, 0.0f, 0.0f);
|
|
// Write per light type matrices, splitDatas and culling parameters
|
|
UpdateShadowRequestData(hdCamera, manager, shadowSettings, visibleLight, cullResults, lightIndex, lightingDebugSettings, filteringQuality, viewportSize, lightType, index, ref shadowRequest);
|
|
}
|
|
|
|
manager.UpdateShadowRequest(shadowRequestIndex, shadowRequest, updateType);
|
|
|
|
if (needToUpdateCachedContent)
|
|
{
|
|
// Handshake with the cached shadow manager to notify about the rendering.
|
|
// Technically the rendering has not happened yet, but it is scheduled.
|
|
HDShadowManager.cachedShadowManager.MarkShadowAsRendered(cachedShadowID, shadowMapType);
|
|
}
|
|
|
|
// Store the first shadow request id to return it
|
|
if (firstShadowRequestIndex == -1)
|
|
firstShadowRequestIndex = shadowRequestIndex;
|
|
|
|
shadowRequestCount++;
|
|
}
|
|
|
|
return shadowHasAtlasPlacement ? firstShadowRequestIndex : -1;
|
|
}
|
|
|
|
void SetCommonShadowRequestSettings(HDShadowRequest shadowRequest, VisibleLight visibleLight, Vector3 cameraPos, Matrix4x4 invViewProjection, Vector2 viewportSize, int lightIndex, HDLightType lightType, HDShadowFilteringQuality filteringQuality)
|
|
{
|
|
// zBuffer param to reconstruct depth position (for transmission)
|
|
float f = legacyLight.range;
|
|
float n = shadowNearPlane;
|
|
shadowRequest.zBufferParam = new Vector4((f - n) / n, 1.0f, (f - n) / (n * f), 1.0f / f);
|
|
shadowRequest.worldTexelSize = 2.0f / shadowRequest.deviceProjectionYFlip.m00 / viewportSize.x * Mathf.Sqrt(2.0f);
|
|
shadowRequest.normalBias = normalBias;
|
|
|
|
// Make light position camera relative:
|
|
// TODO: think about VR (use different camera position for each eye)
|
|
if (ShaderConfig.s_CameraRelativeRendering != 0)
|
|
{
|
|
CoreMatrixUtils.MatrixTimesTranslation(ref shadowRequest.view, cameraPos);
|
|
CoreMatrixUtils.TranslationTimesMatrix(ref invViewProjection, -cameraPos);
|
|
}
|
|
|
|
bool hasOrthoMatrix = false;
|
|
if (lightType == HDLightType.Directional || lightType == HDLightType.Spot && spotLightShape == SpotLightShape.Box)
|
|
{
|
|
hasOrthoMatrix = true;
|
|
shadowRequest.position = new Vector3(shadowRequest.view.m03, shadowRequest.view.m13, shadowRequest.view.m23);
|
|
}
|
|
else
|
|
{
|
|
var vlPos = visibleLight.GetPosition();
|
|
shadowRequest.position = (ShaderConfig.s_CameraRelativeRendering != 0) ? vlPos - cameraPos : vlPos;
|
|
}
|
|
|
|
shadowRequest.shadowToWorld = invViewProjection.transpose;
|
|
shadowRequest.zClip = (lightType != HDLightType.Directional);
|
|
shadowRequest.lightIndex = lightIndex;
|
|
// We don't allow shadow resize for directional cascade shadow
|
|
if (lightType == HDLightType.Directional)
|
|
{
|
|
shadowRequest.shadowMapType = ShadowMapType.CascadedDirectional;
|
|
}
|
|
else if (lightType == HDLightType.Area && areaLightShape == AreaLightShape.Rectangle)
|
|
{
|
|
shadowRequest.shadowMapType = ShadowMapType.AreaLightAtlas;
|
|
}
|
|
else
|
|
{
|
|
shadowRequest.shadowMapType = ShadowMapType.PunctualAtlas;
|
|
}
|
|
|
|
// shadow clip planes (used for tessellation clipping)
|
|
GeometryUtility.CalculateFrustumPlanes(CoreMatrixUtils.MultiplyProjectionMatrix(shadowRequest.projection, shadowRequest.view, hasOrthoMatrix), m_ShadowFrustumPlanes);
|
|
if (shadowRequest.frustumPlanes?.Length != 6)
|
|
shadowRequest.frustumPlanes = new Vector4[6];
|
|
// Left, right, top, bottom, near, far.
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
shadowRequest.frustumPlanes[i] = new Vector4(
|
|
m_ShadowFrustumPlanes[i].normal.x,
|
|
m_ShadowFrustumPlanes[i].normal.y,
|
|
m_ShadowFrustumPlanes[i].normal.z,
|
|
m_ShadowFrustumPlanes[i].distance
|
|
);
|
|
}
|
|
|
|
|
|
float softness = 0.0f;
|
|
if (lightType == HDLightType.Directional)
|
|
{
|
|
var devProj = shadowRequest.deviceProjection;
|
|
float frustumExtentZ = Vector4.Dot(new Vector4(devProj.m32, -devProj.m32, -devProj.m22, devProj.m22), new Vector4(devProj.m22, devProj.m32, devProj.m23, devProj.m33)) /
|
|
(devProj.m22 * (devProj.m22 - devProj.m32));
|
|
|
|
// We use the light view frustum derived from view projection matrix and angular diameter to work out a filter size in
|
|
// shadow map space, essentially figuring out the footprint of the cone subtended by the light on the shadow map
|
|
float halfAngleTan = Mathf.Tan(0.5f * Mathf.Deg2Rad * (softnessScale * m_AngularDiameter) / 2);
|
|
softness = Mathf.Abs(halfAngleTan * frustumExtentZ / (2.0f * shadowRequest.splitData.cullingSphere.w));
|
|
float range = 2.0f * (1.0f / devProj.m22);
|
|
float rangeScale = Mathf.Abs(range) / 100.0f;
|
|
shadowRequest.zBufferParam.x = rangeScale;
|
|
}
|
|
else
|
|
{
|
|
// This derivation has been fitted with quartic regression checking against raytracing reference and with a resolution of 512
|
|
float x = m_ShapeRadius * softnessScale;
|
|
float x2 = x * x;
|
|
softness = 0.02403461f + 3.452916f * x - 1.362672f * x2 + 0.6700115f * x2 * x + 0.2159474f * x2 * x2;
|
|
softness /= 100.0f;
|
|
}
|
|
|
|
var viewportWidth = shadowRequest.isInCachedAtlas ? shadowRequest.cachedAtlasViewport.width : shadowRequest.dynamicAtlasViewport.width;
|
|
softness *= (viewportWidth / 512); // Make it resolution independent whereas the baseline is 512
|
|
|
|
// Bias
|
|
// This base bias is a good value if we expose a [0..1] since values within [0..5] are empirically shown to be sensible for the slope-scale bias with the width of our PCF.
|
|
float baseBias = 5.0f;
|
|
// If we are PCSS, the blur radius can be quite big, hence we need to tweak up the slope bias
|
|
if (filteringQuality == HDShadowFilteringQuality.High)
|
|
{
|
|
if (softness > 0.01f)
|
|
{
|
|
// maxBaseBias is an empirically set value, also the lerp stops at a shadow softness of 0.05, then is clamped.
|
|
float maxBaseBias = 18.0f;
|
|
baseBias = Mathf.Lerp(baseBias, maxBaseBias, Mathf.Min(1.0f, (softness * 100) / 5));
|
|
}
|
|
}
|
|
|
|
shadowRequest.slopeBias = HDShadowUtils.GetSlopeBias(baseBias, slopeBias);
|
|
|
|
// Shadow algorithm parameters
|
|
shadowRequest.shadowSoftness = softness;
|
|
shadowRequest.blockerSampleCount = blockerSampleCount;
|
|
shadowRequest.filterSampleCount = filterSampleCount;
|
|
shadowRequest.minFilterSize = minFilterSize * 0.001f; // This divide by 1000 is here to have a range [0...1] exposed to user
|
|
|
|
shadowRequest.kernelSize = (uint)kernelSize;
|
|
shadowRequest.lightAngle = (lightAngle * Mathf.PI / 180.0f);
|
|
shadowRequest.maxDepthBias = maxDepthBias;
|
|
// We transform it to base two for faster computation.
|
|
// So e^x = 2^y where y = x * log2 (e)
|
|
const float log2e = 1.44269504089f;
|
|
shadowRequest.evsmParams.x = evsmExponent * log2e;
|
|
shadowRequest.evsmParams.y = evsmLightLeakBias;
|
|
shadowRequest.evsmParams.z = m_EvsmVarianceBias;
|
|
shadowRequest.evsmParams.w = evsmBlurPasses;
|
|
}
|
|
|
|
// 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]
|
|
SerializedProperty m_UseColorTemperatureProperty;
|
|
SerializedProperty useColorTemperatureProperty
|
|
{
|
|
get
|
|
{
|
|
if (m_UseColorTemperatureProperty == null)
|
|
{
|
|
m_UseColorTemperatureProperty = lightSerializedObject.FindProperty("m_UseColorTemperature");
|
|
}
|
|
|
|
return m_UseColorTemperatureProperty;
|
|
}
|
|
}
|
|
|
|
[System.NonSerialized]
|
|
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;
|
|
}
|
|
}
|
|
|
|
// TODO: we might be able to get rid to that
|
|
[System.NonSerialized]
|
|
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 ?
|
|
void LateUpdate()
|
|
{
|
|
// 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 (!m_Animated)
|
|
return;
|
|
#endif
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
// If modification are due to change on prefab asset that are non overridden on this prefab instance
|
|
if (m_NeedsPrefabInstanceCheck && PrefabUtility.IsPartOfPrefabInstance(this) && ((PrefabUtility.GetCorrespondingObjectFromOriginalSource(this) as HDAdditionalLightData)?.needRefreshPrefabInstanceEmissiveMeshes ?? false))
|
|
{
|
|
needRefreshPrefabInstanceEmissiveMeshes = true;
|
|
}
|
|
m_NeedsPrefabInstanceCheck = false;
|
|
|
|
// Update the list of overlapping lights for the LightOverlap scene view mode
|
|
if (IsOverlapping())
|
|
s_overlappingHDLights.Add(this);
|
|
else
|
|
s_overlappingHDLights.Remove(this);
|
|
#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 (m_DisplayAreaLightEmissiveMesh && (m_ChildEmissiveMeshViewer == null || m_ChildEmissiveMeshViewer.Equals(null)))
|
|
{
|
|
UpdateAreaLightEmissiveMesh();
|
|
}
|
|
|
|
//if not parented anymore, refresh it
|
|
if (m_ChildEmissiveMeshViewer != null && !m_ChildEmissiveMeshViewer.Equals(null))
|
|
{
|
|
if (m_ChildEmissiveMeshViewer.transform.parent != transform)
|
|
{
|
|
CreateChildEmissiveMeshViewerIfNeeded();
|
|
UpdateAreaLightEmissiveMesh();
|
|
}
|
|
if (m_ChildEmissiveMeshViewer.gameObject.isStatic != gameObject.isStatic)
|
|
m_ChildEmissiveMeshViewer.gameObject.isStatic = gameObject.isStatic;
|
|
if (GameObjectUtility.GetStaticEditorFlags(m_ChildEmissiveMeshViewer.gameObject) != GameObjectUtility.GetStaticEditorFlags(gameObject))
|
|
GameObjectUtility.SetStaticEditorFlags(m_ChildEmissiveMeshViewer.gameObject, GameObjectUtility.GetStaticEditorFlags(gameObject));
|
|
}
|
|
#endif
|
|
|
|
//auto change layer on emissive mesh
|
|
if (areaLightEmissiveMeshLayer == -1
|
|
&& m_ChildEmissiveMeshViewer != null && !m_ChildEmissiveMeshViewer.Equals(null)
|
|
&& m_ChildEmissiveMeshViewer.gameObject.layer != gameObject.layer)
|
|
m_ChildEmissiveMeshViewer.gameObject.layer = gameObject.layer;
|
|
|
|
// Delayed cleanup when removing emissive mesh from timeline
|
|
if (needRefreshEmissiveMeshesFromTimeLineUpdate)
|
|
{
|
|
needRefreshEmissiveMeshesFromTimeLineUpdate = false;
|
|
UpdateAreaLightEmissiveMesh();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// Prefab instance child emissive mesh update
|
|
if (needRefreshPrefabInstanceEmissiveMeshes)
|
|
{
|
|
// We must not call the update on Prefab Asset that are already updated or we will enter infinite loop
|
|
if (!PrefabUtility.IsPartOfPrefabAsset(this))
|
|
{
|
|
UpdateAreaLightEmissiveMesh();
|
|
}
|
|
needRefreshPrefabInstanceEmissiveMeshes = false;
|
|
}
|
|
#endif
|
|
|
|
Vector3 shape = new Vector3(shapeWidth, m_ShapeHeight, shapeRadius);
|
|
|
|
if (legacyLight.enabled != timelineWorkaround.lightEnabled)
|
|
{
|
|
SetEmissiveMeshRendererEnabled(legacyLight.enabled);
|
|
timelineWorkaround.lightEnabled = legacyLight.enabled;
|
|
}
|
|
|
|
// Check if the intensity have been changed by the inspector or an animator
|
|
if (timelineWorkaround.oldLossyScale != transform.lossyScale
|
|
|| intensity != timelineWorkaround.oldIntensity
|
|
|| legacyLight.colorTemperature != timelineWorkaround.oldLightColorTemperature)
|
|
{
|
|
UpdateLightIntensity();
|
|
UpdateAreaLightEmissiveMesh();
|
|
timelineWorkaround.oldLossyScale = transform.lossyScale;
|
|
timelineWorkaround.oldIntensity = intensity;
|
|
timelineWorkaround.oldLightColorTemperature = legacyLight.colorTemperature;
|
|
}
|
|
|
|
// Same check for light angle to update intensity using spot angle
|
|
if (type == HDLightType.Spot && (timelineWorkaround.oldSpotAngle != legacyLight.spotAngle))
|
|
{
|
|
UpdateLightIntensity();
|
|
timelineWorkaround.oldSpotAngle = legacyLight.spotAngle;
|
|
}
|
|
|
|
if (legacyLight.color != timelineWorkaround.oldLightColor
|
|
|| timelineWorkaround.oldLossyScale != transform.lossyScale
|
|
|| displayAreaLightEmissiveMesh != timelineWorkaround.oldDisplayAreaLightEmissiveMesh
|
|
|| legacyLight.colorTemperature != timelineWorkaround.oldLightColorTemperature)
|
|
{
|
|
UpdateAreaLightEmissiveMesh();
|
|
timelineWorkaround.oldLightColor = legacyLight.color;
|
|
timelineWorkaround.oldLossyScale = transform.lossyScale;
|
|
timelineWorkaround.oldDisplayAreaLightEmissiveMesh = displayAreaLightEmissiveMesh;
|
|
timelineWorkaround.oldLightColorTemperature = legacyLight.colorTemperature;
|
|
}
|
|
}
|
|
|
|
void OnDidApplyAnimationProperties()
|
|
{
|
|
UpdateAllLightValues(fromTimeLine: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy all field from this to an additional light data
|
|
/// </summary>
|
|
/// <param name="data">Destination component</param>
|
|
public void CopyTo(HDAdditionalLightData data)
|
|
{
|
|
data.enableSpotReflector = enableSpotReflector;
|
|
data.luxAtDistance = luxAtDistance;
|
|
data.m_InnerSpotPercent = m_InnerSpotPercent;
|
|
data.lightDimmer = lightDimmer;
|
|
data.volumetricDimmer = volumetricDimmer;
|
|
data.lightUnit = lightUnit;
|
|
data.m_FadeDistance = m_FadeDistance;
|
|
data.affectDiffuse = affectDiffuse;
|
|
data.m_AffectSpecular = m_AffectSpecular;
|
|
data.nonLightmappedOnly = nonLightmappedOnly;
|
|
data.m_PointlightHDType = m_PointlightHDType;
|
|
data.spotLightShape = spotLightShape;
|
|
data.shapeWidth = shapeWidth;
|
|
data.m_ShapeHeight = m_ShapeHeight;
|
|
data.aspectRatio = aspectRatio;
|
|
data.shapeRadius = shapeRadius;
|
|
data.m_MaxSmoothness = maxSmoothness;
|
|
data.m_ApplyRangeAttenuation = m_ApplyRangeAttenuation;
|
|
data.useOldInspector = useOldInspector;
|
|
data.featuresFoldout = featuresFoldout;
|
|
data.showAdditionalSettings = showAdditionalSettings;
|
|
data.m_Intensity = m_Intensity;
|
|
data.displayAreaLightEmissiveMesh = displayAreaLightEmissiveMesh;
|
|
data.interactsWithSky = interactsWithSky;
|
|
data.angularDiameter = angularDiameter;
|
|
data.flareSize = flareSize;
|
|
data.flareTint = flareTint;
|
|
data.surfaceTexture = surfaceTexture;
|
|
data.surfaceTint = surfaceTint;
|
|
data.distance = distance;
|
|
|
|
shadowResolution.CopyTo(data.shadowResolution);
|
|
data.shadowDimmer = shadowDimmer;
|
|
data.volumetricShadowDimmer = volumetricShadowDimmer;
|
|
data.shadowFadeDistance = shadowFadeDistance;
|
|
useContactShadow.CopyTo(data.useContactShadow);
|
|
data.slopeBias = slopeBias;
|
|
data.normalBias = normalBias;
|
|
data.shadowCascadeRatios = new float[shadowCascadeRatios.Length];
|
|
shadowCascadeRatios.CopyTo(data.shadowCascadeRatios, 0);
|
|
data.shadowCascadeBorders = new float[shadowCascadeBorders.Length];
|
|
shadowCascadeBorders.CopyTo(data.shadowCascadeBorders, 0);
|
|
data.shadowAlgorithm = shadowAlgorithm;
|
|
data.shadowVariant = shadowVariant;
|
|
data.shadowPrecision = shadowPrecision;
|
|
data.shadowUpdateMode = shadowUpdateMode;
|
|
|
|
data.m_UseCustomSpotLightShadowCone = useCustomSpotLightShadowCone;
|
|
data.m_CustomSpotLightShadowCone = customSpotLightShadowCone;
|
|
|
|
#if UNITY_EDITOR
|
|
data.timelineWorkaround = timelineWorkaround;
|
|
#endif
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Special treatment for Unity built-in area light. Change it to our rectangle light
|
|
var light = lightData.gameObject.GetComponent<Light>();
|
|
|
|
// Set light intensity and unit using its type
|
|
//note: requiring type convert Rectangle and Disc to Area and correctly set areaLight
|
|
switch (lightData.type)
|
|
{
|
|
case HDLightType.Directional:
|
|
lightData.lightUnit = LightUnit.Lux;
|
|
lightData.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 HDLightType.Area: // Rectangle by default when light is created
|
|
switch (lightData.areaLightShape)
|
|
{
|
|
case AreaLightShape.Rectangle:
|
|
lightData.lightUnit = LightUnit.Lumen;
|
|
lightData.intensity = k_DefaultAreaLightIntensity;
|
|
light.shadows = LightShadows.None;
|
|
break;
|
|
case AreaLightShape.Disc:
|
|
//[TODO: to be defined]
|
|
break;
|
|
}
|
|
break;
|
|
case HDLightType.Point:
|
|
case HDLightType.Spot:
|
|
lightData.lightUnit = LightUnit.Lumen;
|
|
lightData.intensity = k_DefaultPunctualLightIntensity;
|
|
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();
|
|
|
|
#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 SetLightIntensityPunctual(float intensity)
|
|
{
|
|
switch (type)
|
|
{
|
|
case HDLightType.Directional:
|
|
legacyLight.intensity = intensity; // Always in lux
|
|
break;
|
|
case HDLightType.Point:
|
|
if (lightUnit == LightUnit.Candela)
|
|
legacyLight.intensity = intensity;
|
|
else
|
|
legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
|
|
break;
|
|
case HDLightType.Spot:
|
|
if (lightUnit == LightUnit.Candela)
|
|
{
|
|
// When using candela, reflector don't have any effect. Our intensity is candela = lumens/steradian and the user
|
|
// provide desired value for an angle of 1 steradian.
|
|
legacyLight.intensity = intensity;
|
|
}
|
|
else // lumen
|
|
{
|
|
if (enableSpotReflector)
|
|
{
|
|
// If reflector is enabled all the lighting from the sphere is focus inside the solid angle of current shape
|
|
if (spotLightShape == SpotLightShape.Cone)
|
|
{
|
|
legacyLight.intensity = LightUtils.ConvertSpotLightLumenToCandela(intensity, legacyLight.spotAngle * Mathf.Deg2Rad, true);
|
|
}
|
|
else if (spotLightShape == SpotLightShape.Pyramid)
|
|
{
|
|
float angleA, angleB;
|
|
LightUtils.CalculateAnglesForPyramid(aspectRatio, legacyLight.spotAngle * Mathf.Deg2Rad, out angleA, out angleB);
|
|
|
|
legacyLight.intensity = LightUtils.ConvertFrustrumLightLumenToCandela(intensity, angleA, angleB);
|
|
}
|
|
else // Box shape, fallback to punctual light.
|
|
{
|
|
legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No reflector, angle act as occlusion of point light.
|
|
legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UpdateLightIntensity()
|
|
{
|
|
if (lightUnit == LightUnit.Lumen)
|
|
{
|
|
if (m_PointlightHDType == PointLightHDType.Punctual)
|
|
SetLightIntensityPunctual(intensity);
|
|
else
|
|
legacyLight.intensity = LightUtils.ConvertAreaLightLumenToLuminance(areaLightShape, intensity, shapeWidth, m_ShapeHeight);
|
|
}
|
|
else if (lightUnit == LightUnit.Ev100)
|
|
{
|
|
legacyLight.intensity = LightUtils.ConvertEvToLuminance(m_Intensity);
|
|
}
|
|
else
|
|
{
|
|
HDLightType lightType = type;
|
|
if ((lightType == HDLightType.Spot || lightType == HDLightType.Point) && lightUnit == LightUnit.Lux)
|
|
{
|
|
// Box are local directional light with lux unity without at distance
|
|
if ((lightType == HDLightType.Spot) && (spotLightShape == SpotLightShape.Box))
|
|
legacyLight.intensity = m_Intensity;
|
|
else
|
|
legacyLight.intensity = LightUtils.ConvertLuxToCandela(m_Intensity, luxAtDistance);
|
|
}
|
|
else
|
|
legacyLight.intensity = m_Intensity;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
legacyLight.SetLightDirty(); // Should be apply only to parameter that's affect GI, but make the code cleaner
|
|
#endif
|
|
}
|
|
|
|
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)
|
|
{
|
|
bool isAreaLight = type == HDLightType.Area;
|
|
bool displayEmissiveMesh = isAreaLight && displayAreaLightEmissiveMesh;
|
|
|
|
// Only show childEmissiveMeshViewer if type is Area and requested
|
|
if (!isAreaLight || !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 (HDRenderPipeline.defaultAsset != null)
|
|
{
|
|
switch (areaLightShape)
|
|
{
|
|
case AreaLightShape.Tube:
|
|
if (m_EmissiveMeshFilter.sharedMesh != HDRenderPipeline.defaultAsset.renderPipelineResources.assets.emissiveCylinderMesh)
|
|
m_EmissiveMeshFilter.sharedMesh = HDRenderPipeline.defaultAsset.renderPipelineResources.assets.emissiveCylinderMesh;
|
|
break;
|
|
case AreaLightShape.Rectangle:
|
|
default:
|
|
if (m_EmissiveMeshFilter.sharedMesh != HDRenderPipeline.defaultAsset.renderPipelineResources.assets.emissiveQuadMesh)
|
|
m_EmissiveMeshFilter.sharedMesh = HDRenderPipeline.defaultAsset.renderPipelineResources.assets.emissiveQuadMesh;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update light area size with clamping
|
|
Vector3 lightSize = new Vector3(m_ShapeWidth, m_ShapeHeight, 0);
|
|
if (areaLightShape == AreaLightShape.Tube)
|
|
lightSize.y = 0;
|
|
lightSize = Vector3.Max(Vector3.one * k_MinAreaWidth, lightSize);
|
|
|
|
switch (areaLightShape)
|
|
{
|
|
case AreaLightShape.Rectangle:
|
|
m_ShapeWidth = lightSize.x;
|
|
m_ShapeHeight = lightSize.y;
|
|
break;
|
|
case AreaLightShape.Tube:
|
|
m_ShapeWidth = lightSize.x;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
legacyLight.areaSize = lightSize;
|
|
#endif
|
|
|
|
// 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)
|
|
{
|
|
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;
|
|
|
|
// We don't have access to the color temperature in the player because it's a private member of the Light component
|
|
#if UNITY_EDITOR
|
|
if (useColorTemperature)
|
|
value *= Mathf.CorrelatedColorTemperatureToRGB(legacyLight.colorTemperature);
|
|
#endif
|
|
|
|
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;
|
|
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, Mathf.Max(range, diag));
|
|
}
|
|
|
|
void UpdateTubeLightBounds()
|
|
{
|
|
legacyLight.useShadowMatrixOverride = false;
|
|
legacyLight.useBoundingSphereOverride = true;
|
|
legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, Mathf.Max(range, m_ShapeWidth * 0.5f));
|
|
}
|
|
|
|
void UpdateBoxLightBounds()
|
|
{
|
|
legacyLight.useShadowMatrixOverride = true;
|
|
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;
|
|
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 (type)
|
|
{
|
|
case HDLightType.Spot:
|
|
switch (spotLightShape)
|
|
{
|
|
case SpotLightShape.Box:
|
|
UpdateBoxLightBounds();
|
|
break;
|
|
case SpotLightShape.Pyramid:
|
|
UpdatePyramidLightBounds();
|
|
break;
|
|
default: // Cone
|
|
legacyLight.useBoundingSphereOverride = false;
|
|
legacyLight.useShadowMatrixOverride = false;
|
|
break;
|
|
}
|
|
break;
|
|
case HDLightType.Area:
|
|
switch (areaLightShape)
|
|
{
|
|
case AreaLightShape.Rectangle:
|
|
UpdateRectangleLightBounds();
|
|
break;
|
|
case AreaLightShape.Tube:
|
|
UpdateTubeLightBounds();
|
|
break;
|
|
}
|
|
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 UNITY_EDITOR
|
|
// We don't want to update the disc area since their shape is largely handled by builtin.
|
|
if (GetLightTypeAndShape() != HDLightTypeAndShape.DiscArea)
|
|
legacyLight.areaSize = new Vector2(shapeWidth, shapeHeight);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronize all the HD Additional Light values with the Light component.
|
|
/// </summary>
|
|
public void UpdateAllLightValues()
|
|
{
|
|
UpdateAllLightValues(false);
|
|
}
|
|
|
|
internal void UpdateAllLightValues(bool fromTimeLine)
|
|
{
|
|
UpdateShapeSize();
|
|
|
|
// Update light intensity
|
|
UpdateLightIntensity();
|
|
|
|
// Patch bounds
|
|
UpdateBounds();
|
|
|
|
UpdateAreaLightEmissiveMesh(fromTimeLine: fromTimeLine);
|
|
}
|
|
|
|
internal void RefreshCachedShadow()
|
|
{
|
|
bool wentThroughCachedShadowSystem = lightIdxForCachedShadows >= 0;
|
|
if (wentThroughCachedShadowSystem)
|
|
HDShadowManager.cachedShadowManager.EvictLight(this);
|
|
|
|
if (!ShadowIsUpdatedEveryFrame() && legacyLight.shadows != LightShadows.None)
|
|
{
|
|
HDShadowManager.cachedShadowManager.RegisterLight(this);
|
|
}
|
|
}
|
|
|
|
#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 the intensity of the light using the current unit.
|
|
/// </summary>
|
|
/// <param name="intensity"></param>
|
|
public void SetIntensity(float intensity) => this.intensity = intensity;
|
|
|
|
/// <summary>
|
|
/// Set the intensity of the light using unit in parameter.
|
|
/// </summary>
|
|
/// <param name="intensity"></param>
|
|
/// <param name="unit">Unit must be a valid Light Unit for the current light type</param>
|
|
public void SetIntensity(float intensity, LightUnit unit)
|
|
{
|
|
this.lightUnit = unit;
|
|
this.intensity = intensity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For Spot Lights only, set the intensity that the spot should emit at a certain distance in meter
|
|
/// </summary>
|
|
/// <param name="luxIntensity"></param>
|
|
/// <param name="distance"></param>
|
|
public void SetSpotLightLuxAt(float luxIntensity, float distance)
|
|
{
|
|
lightUnit = LightUnit.Lux;
|
|
luxAtDistance = distance;
|
|
intensity = luxIntensity;
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
HDLightType lightType = type;
|
|
if (lightType == HDLightType.Area)
|
|
{
|
|
if (cookie.dimension != TextureDimension.Tex2D)
|
|
{
|
|
Debug.LogError("Texture dimension " + cookie.dimension + " is not supported for area lights.");
|
|
return;
|
|
}
|
|
areaLightCookie = cookie;
|
|
}
|
|
else
|
|
{
|
|
if (lightType == HDLightType.Point && cookie.dimension != TextureDimension.Cube)
|
|
{
|
|
Debug.LogError("Texture dimension " + cookie.dimension + " is not supported for point lights.");
|
|
return;
|
|
}
|
|
else if ((lightType == HDLightType.Directional || lightType == HDLightType.Spot) && 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 == HDLightType.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>
|
|
/// Set the light unit.
|
|
/// </summary>
|
|
/// <param name="unit">Unit of the light</param>
|
|
public void SetLightUnit(LightUnit unit) => lightUnit = unit;
|
|
|
|
/// <summary>
|
|
/// Enable shadows on a light.
|
|
/// </summary>
|
|
/// <param name="enabled"></param>
|
|
public void EnableShadows(bool enabled) => legacyLight.shadows = enabled ? LightShadows.Soft : LightShadows.None;
|
|
|
|
/// <summary>
|
|
/// Set the shadow resolution.
|
|
/// </summary>
|
|
/// <param name="resolution">Must be between 16 and 16384</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(LightLayerEnum lightLayerMask, LightLayerEnum 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(LightLayerEnum 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>
|
|
/// Get the list of supported light units depending on the current light type.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public LightUnit[] GetSupportedLightUnits() => GetSupportedLightUnits(type, m_SpotLightShape);
|
|
|
|
/// <summary>
|
|
/// Set the area light size.
|
|
/// </summary>
|
|
/// <param name="size"></param>
|
|
public void SetAreaLightSize(Vector2 size)
|
|
{
|
|
if (type == HDLightType.Area)
|
|
{
|
|
m_ShapeWidth = size.x;
|
|
m_ShapeHeight = size.y;
|
|
UpdateAllLightValues();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the box spot light size.
|
|
/// </summary>
|
|
/// <param name="size"></param>
|
|
public void SetBoxSpotSize(Vector2 size)
|
|
{
|
|
if (type == HDLightType.Spot)
|
|
{
|
|
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
|
|
=> (type == HDLightType.Area && areaLightShape == AreaLightShape.Rectangle)
|
|
? ShadowMapType.AreaLightAtlas
|
|
: type != HDLightType.Directional
|
|
? ShadowMapType.PunctualAtlas
|
|
: ShadowMapType.CascadedDirectional;
|
|
|
|
void OnEnable()
|
|
{
|
|
if (shadowUpdateMode != ShadowUpdateMode.EveryFrame && legacyLight.shadows != LightShadows.None)
|
|
{
|
|
HDShadowManager.cachedShadowManager.RegisterLight(this);
|
|
}
|
|
|
|
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();
|
|
|
|
// This is faster than the above property if lightType is known given that type does a non-trivial amount of work.
|
|
internal ShadowMapType GetShadowMapType(HDLightType lightType)
|
|
{
|
|
return (lightType == HDLightType.Area && areaLightShape == AreaLightShape.Rectangle) ? ShadowMapType.AreaLightAtlas
|
|
: lightType != HDLightType.Directional
|
|
? ShadowMapType.PunctualAtlas
|
|
: ShadowMapType.CascadedDirectional;
|
|
}
|
|
|
|
/// <summary>Tell if the light is overlapping for the light overlap debug mode</summary>
|
|
internal bool IsOverlapping()
|
|
{
|
|
var baking = GetComponent<Light>().bakingOutput;
|
|
bool isOcclusionSeparatelyBaked = baking.occlusionMaskChannel != -1;
|
|
bool isDirectUsingBakedOcclusion = baking.mixedLightingMode == MixedLightingMode.Shadowmask || baking.mixedLightingMode == MixedLightingMode.Subtractive;
|
|
return isDirectUsingBakedOcclusion && !isOcclusionSeparatelyBaked;
|
|
}
|
|
}
|
|
}
|