2 using UnityEngine.Rendering;
5 using UnityEditor.Experimental.Rendering;
6 using UnityEditor.Experimental.Rendering.HDPipeline;
8 using UnityEngine.Serialization;
10 namespace UnityEngine.Experimental.Rendering.HDPipeline
12 // This enum extent the original LightType enum with new light type from HD
13 public enum LightTypeExtent
15 Punctual, // Fallback on LightShape type
22 public enum SpotLightShape { Cone, Pyramid, Box };
26 Lumen, // lm = total power/flux emitted by the light
27 Candela, // lm/sr = flux per steradian
28 Lux, // lm/m² = flux per unit area
29 Luminance, // lm/m²/sr = flux per unit area and per steradian
30 Ev100, // ISO 100 Exposure Value (https://en.wikipedia.org/wiki/Exposure_value)
34 public enum LightLayerEnum
36 Nothing = 0, // Custom name for "Nothing" option
37 LightLayerDefault = 1 << 0,
45 Everything = 0xFF, // Custom name for "Everything" option
48 // This structure contains all the old values for every recordable fields from the HD light editor
49 // so we can force timeline to record changes on other fields from the LateUpdate function (editor only)
50 struct TimelineWorkaround
52 public float oldDisplayLightIntensity;
53 public float oldLuxAtDistance;
54 public float oldSpotAngle;
55 public bool oldEnableSpotReflector;
56 public Color oldLightColor;
57 public Vector3 oldLocalScale;
58 public bool oldDisplayAreaLightEmissiveMesh;
59 public LightTypeExtent oldLightTypeExtent;
60 public float oldLightColorTemperature;
61 public Vector3 oldShape;
62 public float lightDimmer;
65 //@TODO: We should continuously move these values
66 // into the engine when we can see them being generally useful
67 [RequireComponent(typeof(Light))]
69 public class HDAdditionalLightData : MonoBehaviour, ISerializationCallbackReceiver
71 // TODO: Use proper migration toolkit
72 // 3. Added ShadowNearPlane to HDRP additional light data, we don't use Light.shadowNearPlane anymore
73 // 4. Migrate HDAdditionalLightData.lightLayer to Light.renderingLayerMask
74 // 5. Added the ShadowLayer
75 private const int currentVersion = 5;
77 [HideInInspector, SerializeField]
78 [FormerlySerializedAs("m_Version")]
79 [System.Obsolete("version is deprecated, use m_Version instead")]
80 private float version = currentVersion;
82 private int m_Version = currentVersion;
84 // To be able to have correct default values for our lights and to also control the conversion of intensity from the light editor (so it is compatible with GI)
85 // we add intensity (for each type of light we want to manage).
86 [System.Obsolete("directionalIntensity is deprecated, use intensity and lightUnit instead")]
87 public float directionalIntensity = k_DefaultDirectionalLightIntensity;
88 [System.Obsolete("punctualIntensity is deprecated, use intensity and lightUnit instead")]
89 public float punctualIntensity = k_DefaultPunctualLightIntensity;
90 [System.Obsolete("areaIntensity is deprecated, use intensity and lightUnit instead")]
91 public float areaIntensity = k_DefaultAreaLightIntensity;
93 public const float k_DefaultDirectionalLightIntensity = Mathf.PI; // In lux
94 public const float k_DefaultPunctualLightIntensity = 600.0f; // Light default to 600 lumen, i.e ~48 candela
95 public const float k_DefaultAreaLightIntensity = 200.0f; // Light default to 200 lumen to better match point light
97 public float intensity
99 get { return displayLightIntensity; }
100 set { SetLightIntensity(value); }
103 // Only for Spotlight, should be hide for other light
104 public bool enableSpotReflector = false;
105 // Lux unity for all light except directional require a distance
106 public float luxAtDistance = 1.0f;
108 [Range(0.0f, 100.0f)]
109 public float m_InnerSpotPercent; // To display this field in the UI this need to be public
111 public float GetInnerSpotPercent01()
113 return Mathf.Clamp(m_InnerSpotPercent, 0.0f, 100.0f) / 100.0f;
117 public float lightDimmer = 1.0f;
119 [Range(0.0f, 1.0f), SerializeField, FormerlySerializedAs("volumetricDimmer")]
120 private float m_VolumetricDimmer = 1.0f;
122 public float volumetricDimmer
124 get { return useVolumetric ? m_VolumetricDimmer : 0f; }
125 set { m_VolumetricDimmer = value; }
128 // Used internally to convert any light unit input into light intensity
129 public LightUnit lightUnit = LightUnit.Lumen;
131 // Not used for directional lights.
132 public float fadeDistance = 10000.0f;
134 public bool affectDiffuse = true;
135 public bool affectSpecular = true;
137 // This property work only with shadow mask and allow to say we don't render any lightMapped object in the shadow map
138 public bool nonLightmappedOnly = false;
140 public LightTypeExtent lightTypeExtent = LightTypeExtent.Punctual;
142 // Only for Spotlight, should be hide for other light
143 public SpotLightShape spotLightShape { get { return m_SpotLightShape; } set { SetSpotLightShape(value); } }
144 [SerializeField, FormerlySerializedAs("spotLightShape")]
145 SpotLightShape m_SpotLightShape = SpotLightShape.Cone;
147 // Only for Rectangle/Line/box projector lights
148 public float shapeWidth = 0.5f;
150 // Only for Rectangle/box projector lights
151 public float shapeHeight = 0.5f;
153 // Only for pyramid projector
154 public float aspectRatio = 1.0f;
156 // Only for Punctual/Sphere/Disc
157 public float shapeRadius = 0.0f;
159 // Only for Spot/Point - use to cheaply fake specular spherical area light
160 // It is not 1 to make sure the highlight does not disappear.
162 public float maxSmoothness = 0.99f;
164 // 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
165 public bool applyRangeAttenuation = true;
167 // This is specific for the LightEditor GUI and not use at runtime
168 public bool useOldInspector = false;
169 public bool useVolumetric = true;
170 public bool featuresFoldout = true;
171 public byte showAdditionalSettings = 0;
172 public float displayLightIntensity;
174 // When true, a mesh will be display to represent the area light (Can only be change in editor, component is added in Editor)
175 public bool displayAreaLightEmissiveMesh = false;
177 // Optional cookie for rectangular area lights
178 public Texture areaLightCookie = null;
180 [Range(0.0f, 179.0f)]
181 public float areaLightShadowCone = 120.0f;
183 // Flag that tells us if the shadow should be screen space
184 public bool useScreenSpaceShadows = false;
186 #if ENABLE_RAYTRACING
187 public bool useRayTracedShadows = false;
189 public int numRayTracingSamples = 4;
190 public bool filterTracedShadow = true;
192 public int filterSizeTraced = 16;
194 public float sunLightConeAngle = 0.5f;
198 public float evsmExponent = 15.0f;
200 public float evsmLightLeakBias = 0.0f;
201 [Range(0.0f, 0.001f)]
202 public float evsmVarianceBias = 1e-5f;
204 public int evsmBlurPasses = 0;
206 // Duplication of HDLightEditor.k_MinAreaWidth, maybe do something about that
207 const float k_MinAreaWidth = 0.01f; // Provide a small size of 1cm for line light
209 [Obsolete("Use Light.renderingLayerMask instead")]
210 public LightLayerEnum lightLayers = LightLayerEnum.LightLayerDefault;
212 // Now the renderingLayerMask is used for shadow layers and not light layers
213 public LightLayerEnum lightlayersMask = LightLayerEnum.LightLayerDefault;
214 public bool linkShadowLayers = true;
216 // This function return a mask of light layers as uint and handle the case of Everything as being 0xFF and not -1
217 public uint GetLightLayers()
219 int value = (int)lightlayersMask;
220 return value < 0 ? (uint)LightLayerEnum.Everything : (uint)value;
224 public float shadowNearPlane = 0.1f;
228 public float shadowSoftness = .5f;
230 public int blockerSampleCount = 24;
232 public int filterSampleCount = 16;
234 public float minFilterSize = 0.00001f;
236 // Improved Moment Shadows settings
238 public int kernelSize = 5;
240 public float lightAngle = 1.0f;
241 [Range(0.0001f, 0.01f)]
242 public float maxDepthBias = 0.001f;
244 HDShadowRequest[] shadowRequests;
245 bool m_WillRenderShadowMap;
246 bool m_WillRenderScreenSpaceShadow;
247 #if ENABLE_RAYTRACING
248 bool m_WillRenderRayTracedShadow;
250 int[] m_ShadowRequestIndices;
252 [System.NonSerialized]
253 Plane[] m_ShadowFrustumPlanes = new Plane[6];
255 #if ENABLE_RAYTRACING
256 // Temporary index that stores the current shadow index for the light
257 [System.NonSerialized] public int shadowIndex;
260 [System.NonSerialized] HDShadowSettings _ShadowSettings = null;
261 HDShadowSettings m_ShadowSettings
265 if (_ShadowSettings == null)
266 _ShadowSettings = VolumeManager.instance.stack.GetComponent<HDShadowSettings>();
267 return _ShadowSettings;
271 AdditionalShadowData _ShadowData;
272 AdditionalShadowData m_ShadowData
276 if (_ShadowData == null)
277 _ShadowData = GetComponent<AdditionalShadowData>();
282 int GetShadowRequestCount()
284 return (legacyLight.type == LightType.Point && lightTypeExtent == LightTypeExtent.Punctual) ? 6 : (legacyLight.type == LightType.Directional) ? m_ShadowSettings.cascadeShadowSplitCount.value : 1;
287 public void EvaluateShadowState(HDCamera hdCamera, CullingResults cullResults, FrameSettings frameSettings, int lightIndex)
290 float cameraDistance = Vector3.Distance(hdCamera.camera.transform.position, transform.position);
292 m_WillRenderShadowMap = legacyLight.shadows != LightShadows.None && frameSettings.IsEnabled(FrameSettingsField.Shadow);
294 m_WillRenderShadowMap &= cullResults.GetShadowCasterBounds(lightIndex, out bounds);
295 // When creating a new light, at the first frame, there is no AdditionalShadowData so we can't really render shadows
296 m_WillRenderShadowMap &= m_ShadowData != null && m_ShadowData.shadowDimmer > 0;
297 // If the shadow is too far away, we don't render it
298 if (m_ShadowData != null)
299 m_WillRenderShadowMap &= legacyLight.type == LightType.Directional || cameraDistance < (m_ShadowData.shadowFadeDistance);
301 // First we reset the ray tracing and screen space sahdow data
302 m_WillRenderScreenSpaceShadow = false;
303 #if ENABLE_RAYTRACING
304 m_WillRenderRayTracedShadow = false;
307 // If this camera does not allow screen space shadows we are done, set the target parameters to false and leave the function
308 if (!hdCamera.frameSettings.IsEnabled(FrameSettingsField.ScreenSpaceShadows) || !m_WillRenderShadowMap)
311 #if ENABLE_RAYTRACING
312 // We render screen space shadows if we are a ray traced rectangle area light or a screen space directional light shadow
313 if ((useRayTracedShadows && lightTypeExtent == LightTypeExtent.Rectangle)
314 || (useScreenSpaceShadows && legacyLight.type == LightType.Directional))
316 m_WillRenderScreenSpaceShadow = true;
319 // We will evaluate a ray traced shadow if we a ray traced area shadow
320 if ((useRayTracedShadows && lightTypeExtent == LightTypeExtent.Rectangle)
321 || (useRayTracedShadows && legacyLight.type == LightType.Directional))
323 m_WillRenderRayTracedShadow = true;
328 public void ReserveShadowMap(Camera camera, HDShadowManager shadowManager, HDShadowInitParameters initParameters, Rect screenRect)
330 if (!m_WillRenderShadowMap)
333 // Create shadow requests array using the light type
334 if (shadowRequests == null || m_ShadowRequestIndices == null)
336 const int maxLightShadowRequestsCount = 6;
337 shadowRequests = new HDShadowRequest[maxLightShadowRequestsCount];
338 m_ShadowRequestIndices = new int[maxLightShadowRequestsCount];
340 for (int i = 0; i < maxLightShadowRequestsCount; i++)
341 shadowRequests[i] = new HDShadowRequest();
344 Vector2 viewportSize = new Vector2(m_ShadowData.shadowResolution, m_ShadowData.shadowResolution);
346 // Reserver wanted resolution in the shadow atlas
347 ShadowMapType shadowMapType = (lightTypeExtent == LightTypeExtent.Rectangle) ? ShadowMapType.AreaLightAtlas :
348 (legacyLight.type != LightType.Directional) ? ShadowMapType.PunctualAtlas : ShadowMapType.CascadedDirectional;
350 bool viewPortRescaling = false;
351 // Compute dynamic shadow resolution
353 viewPortRescaling |= (shadowMapType == ShadowMapType.PunctualAtlas && initParameters.punctualLightShadowAtlas.useDynamicViewportRescale);
354 viewPortRescaling |= (shadowMapType == ShadowMapType.AreaLightAtlas && initParameters.areaLightShadowAtlas.useDynamicViewportRescale);
356 if (viewPortRescaling)
358 // resize viewport size by the normalized size of the light on screen
359 float screenArea = screenRect.width * screenRect.height;
360 viewportSize *= Mathf.Lerp(64f / viewportSize.x, 1f, screenArea);
361 viewportSize = Vector2.Max(new Vector2(64f, 64f) / viewportSize, viewportSize);
363 // Prevent flickering caused by the floating size of the viewport
364 viewportSize.x = Mathf.Round(viewportSize.x);
365 viewportSize.y = Mathf.Round(viewportSize.y);
368 viewportSize = Vector2.Max(viewportSize, new Vector2(HDShadowManager.k_MinShadowMapResolution, HDShadowManager.k_MinShadowMapResolution));
370 // Update the directional shadow atlas size
371 if (legacyLight.type == LightType.Directional)
372 shadowManager.UpdateDirectionalShadowResolution((int)viewportSize.x, m_ShadowSettings.cascadeShadowSplitCount.value);
374 int count = GetShadowRequestCount();
375 for (int index = 0; index < count; index++)
376 m_ShadowRequestIndices[index] = shadowManager.ReserveShadowResolutions(viewportSize, shadowMapType);
379 public bool WillRenderShadowMap()
381 return m_WillRenderShadowMap;
384 public bool WillRenderScreenSpaceShadow()
386 return m_WillRenderScreenSpaceShadow;
389 #if ENABLE_RAYTRACING
390 public bool WillRenderRayTracedShadow()
392 return m_WillRenderRayTracedShadow;
396 // This offset shift the position of the spotlight used to approximate the area light shadows. The offset is the minimum such that the full
397 // area light shape is included in the cone spanned by the spot light.
398 public static float GetAreaLightOffsetForShadows(Vector2 shapeSize, float coneAngle)
400 float rectangleDiagonal = shapeSize.magnitude;
401 float halfAngle = coneAngle * 0.5f;
402 float cotanHalfAngle = 1.0f / Mathf.Tan(halfAngle * Mathf.Deg2Rad);
403 float offset = rectangleDiagonal * cotanHalfAngle;
408 // Must return the first executed shadow request
409 public int UpdateShadowRequest(HDCamera hdCamera, HDShadowManager manager, VisibleLight visibleLight, CullingResults cullResults, int lightIndex, out int shadowRequestCount)
411 int firstShadowRequestIndex = -1;
412 Vector3 cameraPos = hdCamera.mainViewConstants.worldSpaceCameraPos;
413 shadowRequestCount = 0;
415 int count = GetShadowRequestCount();
416 for (int index = 0; index < count; index++)
418 var shadowRequest = shadowRequests[index];
419 Matrix4x4 invViewProjection = Matrix4x4.identity;
420 int shadowRequestIndex = m_ShadowRequestIndices[index];
421 Vector2 viewportSize = manager.GetReservedResolution(shadowRequestIndex);
423 if (shadowRequestIndex == -1)
426 if (lightTypeExtent == LightTypeExtent.Rectangle)
428 Vector2 shapeSize = new Vector2(shapeWidth, shapeHeight);
429 float offset = GetAreaLightOffsetForShadows(shapeSize, areaLightShadowCone);
430 Vector3 shadowOffset = offset * visibleLight.GetForward();
431 HDShadowUtils.ExtractAreaLightData(hdCamera, visibleLight, lightTypeExtent, visibleLight.GetPosition() + shadowOffset, areaLightShadowCone, shadowNearPlane, shapeSize, viewportSize, m_ShadowData.normalBiasMax, out shadowRequest.view, out invViewProjection, out shadowRequest.deviceProjectionYFlip, out shadowRequest.deviceProjection, out shadowRequest.splitData);
435 // Write per light type matrices, splitDatas and culling parameters
436 switch (legacyLight.type)
438 case LightType.Point:
439 HDShadowUtils.ExtractPointLightData(
440 hdCamera, legacyLight.type, visibleLight, viewportSize, shadowNearPlane,
441 m_ShadowData.normalBiasMax, (uint)index, out shadowRequest.view,
442 out invViewProjection, out shadowRequest.deviceProjectionYFlip,
443 out shadowRequest.deviceProjection, out shadowRequest.splitData
447 HDShadowUtils.ExtractSpotLightData(
448 hdCamera, legacyLight.type, spotLightShape, shadowNearPlane, aspectRatio, shapeWidth,
449 shapeHeight, visibleLight, viewportSize, m_ShadowData.normalBiasMax,
450 out shadowRequest.view, out invViewProjection, out shadowRequest.deviceProjectionYFlip,
451 out shadowRequest.deviceProjection, out shadowRequest.splitData
454 case LightType.Directional:
455 Vector4 cullingSphere;
456 float nearPlaneOffset = QualitySettings.shadowNearPlaneOffset;
458 HDShadowUtils.ExtractDirectionalLightData(
459 visibleLight, viewportSize, (uint)index, m_ShadowSettings.cascadeShadowSplitCount.value,
460 m_ShadowSettings.cascadeShadowSplits, nearPlaneOffset, cullResults, lightIndex,
461 out shadowRequest.view, out invViewProjection, out shadowRequest.deviceProjectionYFlip,
462 out shadowRequest.deviceProjection, out shadowRequest.splitData
465 cullingSphere = shadowRequest.splitData.cullingSphere;
467 // Camera relative for directional light culling sphere
468 if (ShaderConfig.s_CameraRelativeRendering != 0)
470 cullingSphere.x -= cameraPos.x;
471 cullingSphere.y -= cameraPos.y;
472 cullingSphere.z -= cameraPos.z;
474 manager.UpdateCascade(index, cullingSphere, m_ShadowSettings.cascadeShadowBorders[index]);
479 // Assign all setting common to every lights
480 SetCommonShadowRequestSettings(shadowRequest, cameraPos, invViewProjection, shadowRequest.deviceProjectionYFlip * shadowRequest.view, viewportSize, lightIndex);
482 manager.UpdateShadowRequest(shadowRequestIndex, shadowRequest);
484 // Store the first shadow request id to return it
485 if (firstShadowRequestIndex == -1)
486 firstShadowRequestIndex = shadowRequestIndex;
488 shadowRequestCount++;
491 return firstShadowRequestIndex;
494 void SetCommonShadowRequestSettings(HDShadowRequest shadowRequest, Vector3 cameraPos, Matrix4x4 invViewProjection, Matrix4x4 viewProjection, Vector2 viewportSize, int lightIndex)
496 // zBuffer param to reconstruct depth position (for transmission)
497 float f = legacyLight.range;
498 float n = shadowNearPlane;
499 shadowRequest.zBufferParam = new Vector4((f-n)/n, 1.0f, (f-n)/n*f, 1.0f/f);
500 shadowRequest.viewBias = new Vector4(m_ShadowData.viewBiasMin, m_ShadowData.viewBiasMax, m_ShadowData.viewBiasScale, 2.0f / shadowRequest.deviceProjectionYFlip.m00 / viewportSize.x * 1.4142135623730950488016887242097f);
501 shadowRequest.normalBias = new Vector3(m_ShadowData.normalBiasMin, m_ShadowData.normalBiasMax, m_ShadowData.normalBiasScale);
502 shadowRequest.flags = 0;
503 shadowRequest.flags |= m_ShadowData.sampleBiasScale ? (int)HDShadowFlag.SampleBiasScale : 0;
504 shadowRequest.flags |= m_ShadowData.edgeLeakFixup ? (int)HDShadowFlag.EdgeLeakFixup : 0;
505 shadowRequest.flags |= m_ShadowData.edgeToleranceNormal ? (int)HDShadowFlag.EdgeToleranceNormal : 0;
506 shadowRequest.edgeTolerance = m_ShadowData.edgeTolerance;
508 // Make light position camera relative:
509 // TODO: think about VR (use different camera position for each eye)
510 if (ShaderConfig.s_CameraRelativeRendering != 0)
512 var translation = Matrix4x4.Translate(cameraPos);
513 shadowRequest.view *= translation;
514 translation.SetColumn(3, -cameraPos);
515 translation[15] = 1.0f;
516 invViewProjection = translation * invViewProjection;
519 if (legacyLight.type == LightType.Directional || (legacyLight.type == LightType.Spot && spotLightShape == SpotLightShape.Box))
520 shadowRequest.position = new Vector3(shadowRequest.view.m03, shadowRequest.view.m13, shadowRequest.view.m23);
522 shadowRequest.position = (ShaderConfig.s_CameraRelativeRendering != 0) ? transform.position - cameraPos : transform.position;
524 shadowRequest.shadowToWorld = invViewProjection.transpose;
525 shadowRequest.zClip = (legacyLight.type != LightType.Directional);
526 shadowRequest.lightIndex = lightIndex;
527 // We don't allow shadow resize for directional cascade shadow
528 if (legacyLight.type == LightType.Directional)
530 shadowRequest.shadowMapType = ShadowMapType.CascadedDirectional;
532 else if (lightTypeExtent == LightTypeExtent.Rectangle)
534 shadowRequest.shadowMapType = ShadowMapType.AreaLightAtlas;
538 shadowRequest.shadowMapType = ShadowMapType.PunctualAtlas;
541 shadowRequest.lightType = (int) legacyLight.type;
543 // shadow clip planes (used for tessellation clipping)
544 GeometryUtility.CalculateFrustumPlanes(viewProjection, m_ShadowFrustumPlanes);
545 if (shadowRequest.frustumPlanes?.Length != 6)
546 shadowRequest.frustumPlanes = new Vector4[6];
547 // Left, right, top, bottom, near, far.
548 for (int i = 0; i < 6; i++)
550 shadowRequest.frustumPlanes[i] = new Vector4(
551 m_ShadowFrustumPlanes[i].normal.x,
552 m_ShadowFrustumPlanes[i].normal.y,
553 m_ShadowFrustumPlanes[i].normal.z,
554 m_ShadowFrustumPlanes[i].distance
558 // Shadow algorithm parameters
559 shadowRequest.shadowSoftness = shadowSoftness / 100f;
560 shadowRequest.blockerSampleCount = blockerSampleCount;
561 shadowRequest.filterSampleCount = filterSampleCount;
562 shadowRequest.minFilterSize = minFilterSize;
564 shadowRequest.kernelSize = (uint)kernelSize;
565 shadowRequest.lightAngle = (lightAngle * Mathf.PI / 180.0f);
566 shadowRequest.maxDepthBias = maxDepthBias;
567 // We transform it to base two for faster computation.
568 // So e^x = 2^y where y = x * log2 (e)
569 const float log2e = 1.44269504089f;
570 shadowRequest.evsmParams.x = evsmExponent * log2e;
571 shadowRequest.evsmParams.y = evsmLightLeakBias;
572 shadowRequest.evsmParams.z = evsmVarianceBias;
573 shadowRequest.evsmParams.w = evsmBlurPasses;
576 // We need these old states to make timeline and the animator record the intensity value and the emissive mesh changes
577 [System.NonSerialized]
578 TimelineWorkaround timelineWorkaround = new TimelineWorkaround();
580 // For light that used the old intensity system we update them
581 [System.NonSerialized]
582 bool needsIntensityUpdate_1_0 = false;
584 // Runtime datas used to compute light intensity
586 internal Light legacyLight
591 m_light = GetComponent<Light>();
596 void SetLightIntensity(float intensity)
598 displayLightIntensity = intensity;
600 if (lightUnit == LightUnit.Lumen)
602 if (lightTypeExtent == LightTypeExtent.Punctual)
603 SetLightIntensityPunctual(intensity);
605 legacyLight.intensity = LightUtils.ConvertAreaLightLumenToLuminance(lightTypeExtent, intensity, shapeWidth, shapeHeight);
607 else if (lightUnit == LightUnit.Ev100)
609 legacyLight.intensity = LightUtils.ConvertEvToLuminance(intensity);
611 else if ((legacyLight.type == LightType.Spot || legacyLight.type == LightType.Point) && lightUnit == LightUnit.Lux)
613 // Box are local directional light with lux unity without at distance
614 if ((legacyLight.type == LightType.Spot) && (spotLightShape == SpotLightShape.Box))
615 legacyLight.intensity = intensity;
617 legacyLight.intensity = LightUtils.ConvertLuxToCandela(intensity, luxAtDistance);
620 legacyLight.intensity = intensity;
623 legacyLight.SetLightDirty(); // Should be apply only to parameter that's affect GI, but make the code cleaner
627 void SetLightIntensityPunctual(float intensity)
629 switch (legacyLight.type)
631 case LightType.Directional:
632 legacyLight.intensity = intensity; // Always in lux
634 case LightType.Point:
635 if (lightUnit == LightUnit.Candela)
636 legacyLight.intensity = intensity;
638 legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
641 if (lightUnit == LightUnit.Candela)
643 // When using candela, reflector don't have any effect. Our intensity is candela = lumens/steradian and the user
644 // provide desired value for an angle of 1 steradian.
645 legacyLight.intensity = intensity;
649 if (enableSpotReflector)
651 // If reflector is enabled all the lighting from the sphere is focus inside the solid angle of current shape
652 if (spotLightShape == SpotLightShape.Cone)
654 legacyLight.intensity = LightUtils.ConvertSpotLightLumenToCandela(intensity, legacyLight.spotAngle * Mathf.Deg2Rad, true);
656 else if (spotLightShape == SpotLightShape.Pyramid)
658 float angleA, angleB;
659 LightUtils.CalculateAnglesForPyramid(aspectRatio, legacyLight.spotAngle * Mathf.Deg2Rad, out angleA, out angleB);
661 legacyLight.intensity = LightUtils.ConvertFrustrumLightLumenToCandela(intensity, angleA, angleB);
663 else // Box shape, fallback to punctual light.
665 legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
670 // No reflector, angle act as occlusion of point light.
671 legacyLight.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
678 public static bool IsAreaLight(LightTypeExtent lightType)
680 return lightType != LightTypeExtent.Punctual;
685 // Force to retrieve color light's m_UseColorTemperature because it's private
686 [System.NonSerialized]
687 SerializedProperty useColorTemperatureProperty;
688 [System.NonSerialized]
689 SerializedObject lightSerializedObject;
690 public bool useColorTemperature
694 if (useColorTemperatureProperty == null)
696 lightSerializedObject = new SerializedObject(legacyLight);
697 useColorTemperatureProperty = lightSerializedObject.FindProperty("m_UseColorTemperature");
700 lightSerializedObject.Update();
702 return useColorTemperatureProperty.boolValue;
706 public static bool IsAreaLight(SerializedProperty lightType)
708 return IsAreaLight((LightTypeExtent)lightType.enumValueIndex);
713 [System.NonSerialized]
718 // If there is an animator attached ot the light, we assume that some of the light properties
719 // might be driven by this animator (using timeline or animations) so we force the LateUpdate
720 // to sync the animated HDAdditionalLightData properties with the light component.
721 m_Animated = GetComponent<Animator>() != null;
724 // TODO: There are a lot of old != current checks and assignation in this function, maybe think about using another system ?
727 // We force the animation in the editor and in play mode when there is an animator component attached to the light
733 Vector3 shape = new Vector3(shapeWidth, shapeHeight, shapeRadius);
735 // Check if the intensity have been changed by the inspector or an animator
736 if (displayLightIntensity != timelineWorkaround.oldDisplayLightIntensity
737 || luxAtDistance != timelineWorkaround.oldLuxAtDistance
738 || lightTypeExtent != timelineWorkaround.oldLightTypeExtent
739 || transform.localScale != timelineWorkaround.oldLocalScale
740 || shape != timelineWorkaround.oldShape
741 || legacyLight.colorTemperature != timelineWorkaround.oldLightColorTemperature)
743 RefreshLightIntensity();
744 UpdateAreaLightEmissiveMesh();
745 timelineWorkaround.oldDisplayLightIntensity = displayLightIntensity;
746 timelineWorkaround.oldLuxAtDistance = luxAtDistance;
747 timelineWorkaround.oldLocalScale = transform.localScale;
748 timelineWorkaround.oldLightTypeExtent = lightTypeExtent;
749 timelineWorkaround.oldLightColorTemperature = legacyLight.colorTemperature;
750 timelineWorkaround.oldShape = shape;
753 // Same check for light angle to update intensity using spot angle
754 if (legacyLight.type == LightType.Spot && (timelineWorkaround.oldSpotAngle != legacyLight.spotAngle || timelineWorkaround.oldEnableSpotReflector != enableSpotReflector))
756 RefreshLightIntensity();
757 timelineWorkaround.oldSpotAngle = legacyLight.spotAngle;
758 timelineWorkaround.oldEnableSpotReflector = enableSpotReflector;
761 if (legacyLight.color != timelineWorkaround.oldLightColor
762 || transform.localScale != timelineWorkaround.oldLocalScale
763 || displayAreaLightEmissiveMesh != timelineWorkaround.oldDisplayAreaLightEmissiveMesh
764 || lightTypeExtent != timelineWorkaround.oldLightTypeExtent
765 || legacyLight.colorTemperature != timelineWorkaround.oldLightColorTemperature
766 || lightDimmer != timelineWorkaround.lightDimmer)
768 UpdateAreaLightEmissiveMesh();
769 timelineWorkaround.lightDimmer = lightDimmer;
770 timelineWorkaround.oldLightColor = legacyLight.color;
771 timelineWorkaround.oldLocalScale = transform.localScale;
772 timelineWorkaround.oldDisplayAreaLightEmissiveMesh = displayAreaLightEmissiveMesh;
773 timelineWorkaround.oldLightTypeExtent = lightTypeExtent;
774 timelineWorkaround.oldLightColorTemperature = legacyLight.colorTemperature;
778 // The editor can only access displayLightIntensity (because of SerializedProperties) so we update the intensity to get the real value
779 void RefreshLightIntensity()
781 intensity = displayLightIntensity;
784 public void UpdateAreaLightEmissiveMesh()
786 MeshRenderer emissiveMeshRenderer = GetComponent<MeshRenderer>();
787 MeshFilter emissiveMeshFilter = GetComponent<MeshFilter>();
789 bool displayEmissiveMesh = IsAreaLight(lightTypeExtent) && displayAreaLightEmissiveMesh;
791 // Ensure that the emissive mesh components are here
792 if (displayEmissiveMesh)
794 if (emissiveMeshRenderer == null)
795 emissiveMeshRenderer = gameObject.AddComponent<MeshRenderer>();
796 if (emissiveMeshFilter == null)
797 emissiveMeshFilter = gameObject.AddComponent<MeshFilter>();
799 else // Or remove them if the option is disabled
801 if (emissiveMeshRenderer != null)
802 DestroyImmediate(emissiveMeshRenderer);
803 if (emissiveMeshFilter != null)
804 DestroyImmediate(emissiveMeshFilter);
806 // We don't have anything to do left if the dislay emissive mesh option is disabled
812 // Update light area size from GameObject transform scale if the transform have changed
813 // else we update the light size from the shape fields
814 if (timelineWorkaround.oldLocalScale != transform.localScale)
815 lightSize = transform.localScale;
817 lightSize = new Vector3(shapeWidth, shapeHeight, transform.localScale.z);
819 if (lightTypeExtent == LightTypeExtent.Tube)
820 lightSize.y = k_MinAreaWidth;
821 lightSize.z = k_MinAreaWidth;
823 lightSize = Vector3.Max(Vector3.one * k_MinAreaWidth, lightSize);
824 legacyLight.transform.localScale = lightSize;
826 legacyLight.areaSize = lightSize;
829 switch (lightTypeExtent)
831 case LightTypeExtent.Rectangle:
832 shapeWidth = lightSize.x;
833 shapeHeight = lightSize.y;
835 case LightTypeExtent.Tube:
836 shapeWidth = lightSize.x;
842 // 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)
843 // 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
844 // the material is not re-created until one of the light properties is changed again.
845 if (emissiveMeshRenderer.sharedMaterial == null || emissiveMeshRenderer.sharedMaterial.name != gameObject.name)
847 emissiveMeshRenderer.sharedMaterial = new Material(Shader.Find("HDRP/Unlit"));
848 emissiveMeshRenderer.sharedMaterial.SetFloat("_IncludeIndirectLighting", 0.0f);
849 emissiveMeshRenderer.sharedMaterial.name = gameObject.name;
852 // Update Mesh emissive properties
853 emissiveMeshRenderer.sharedMaterial.SetColor("_UnlitColor", Color.black);
855 // m_Light.intensity is in luminance which is the value we need for emissive color
856 Color value = legacyLight.color.linear * legacyLight.intensity;
858 // We don't have access to the color temperature in the player because it's a private member of the Light component
860 if (useColorTemperature)
861 value *= Mathf.CorrelatedColorTemperatureToRGB(legacyLight.colorTemperature);
864 value *= lightDimmer;
866 emissiveMeshRenderer.sharedMaterial.SetColor("_EmissiveColor", value);
868 // Set the cookie (if there is one) and raise or remove the shader feature
869 emissiveMeshRenderer.sharedMaterial.SetTexture("_EmissiveColorMap", areaLightCookie);
870 CoreUtils.SetKeyword(emissiveMeshRenderer.sharedMaterial, "_EMISSIVE_COLOR_MAP", areaLightCookie != null);
873 public void CopyTo(HDAdditionalLightData data)
875 #pragma warning disable 618
876 data.directionalIntensity = directionalIntensity;
877 data.punctualIntensity = punctualIntensity;
878 data.areaIntensity = areaIntensity;
879 #pragma warning restore 618
880 data.enableSpotReflector = enableSpotReflector;
881 data.luxAtDistance = luxAtDistance;
882 data.m_InnerSpotPercent = m_InnerSpotPercent;
883 data.lightDimmer = lightDimmer;
884 data.volumetricDimmer = volumetricDimmer;
885 data.lightUnit = lightUnit;
886 data.fadeDistance = fadeDistance;
887 data.affectDiffuse = affectDiffuse;
888 data.affectSpecular = affectSpecular;
889 data.nonLightmappedOnly = nonLightmappedOnly;
890 data.lightTypeExtent = lightTypeExtent;
891 data.spotLightShape = spotLightShape;
892 data.shapeWidth = shapeWidth;
893 data.shapeHeight = shapeHeight;
894 data.aspectRatio = aspectRatio;
895 data.shapeRadius = shapeRadius;
896 data.maxSmoothness = maxSmoothness;
897 data.applyRangeAttenuation = applyRangeAttenuation;
898 data.useOldInspector = useOldInspector;
899 data.featuresFoldout = featuresFoldout;
900 data.showAdditionalSettings = showAdditionalSettings;
901 data.displayLightIntensity = displayLightIntensity;
902 data.displayAreaLightEmissiveMesh = displayAreaLightEmissiveMesh;
903 data.needsIntensityUpdate_1_0 = needsIntensityUpdate_1_0;
906 data.timelineWorkaround = timelineWorkaround;
910 void SetSpotLightShape(SpotLightShape shape)
912 m_SpotLightShape = shape;
916 void UpdateAreaLightBounds()
918 legacyLight.useShadowMatrixOverride = false;
919 legacyLight.useBoundingSphereOverride = true;
920 legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, 0.0f, legacyLight.range);
923 void UpdateBoxLightBounds()
925 legacyLight.useShadowMatrixOverride = true;
926 legacyLight.useBoundingSphereOverride = true;
928 // Need to inverse scale because culling != rendering convention apparently
929 Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f));
930 legacyLight.shadowMatrixOverride = HDShadowUtils.ExtractBoxLightProjectionMatrix(legacyLight.range, shapeWidth, shapeHeight, shadowNearPlane) * scaleMatrix;
932 // Very conservative bounding sphere taking the diagonal of the shape as the radius
933 float diag = new Vector3(shapeWidth * 0.5f, shapeHeight * 0.5f, legacyLight.range * 0.5f).magnitude;
934 legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, legacyLight.range * 0.5f, diag);
937 void UpdatePyramidLightBounds()
939 legacyLight.useShadowMatrixOverride = true;
940 legacyLight.useBoundingSphereOverride = true;
942 // Need to inverse scale because culling != rendering convention apparently
943 Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f));
944 legacyLight.shadowMatrixOverride = HDShadowUtils.ExtractSpotLightProjectionMatrix(legacyLight.range, legacyLight.spotAngle, shadowNearPlane, aspectRatio, 0.0f) * scaleMatrix;
946 // Very conservative bounding sphere taking the diagonal of the shape as the radius
947 float diag = new Vector3(shapeWidth * 0.5f, shapeHeight * 0.5f, legacyLight.range * 0.5f).magnitude;
948 legacyLight.boundingSphereOverride = new Vector4(0.0f, 0.0f, legacyLight.range * 0.5f, diag);
953 if (lightTypeExtent == LightTypeExtent.Punctual && legacyLight.type == LightType.Spot)
955 switch (spotLightShape)
957 case SpotLightShape.Box:
958 UpdateBoxLightBounds();
960 case SpotLightShape.Pyramid:
961 UpdatePyramidLightBounds();
964 legacyLight.useBoundingSphereOverride = false;
965 legacyLight.useShadowMatrixOverride = false;
969 else if (lightTypeExtent == LightTypeExtent.Rectangle || lightTypeExtent == LightTypeExtent.Tube)
971 UpdateAreaLightBounds();
975 legacyLight.useBoundingSphereOverride = false;
976 legacyLight.useShadowMatrixOverride = false;
980 // As we have our own default value, we need to initialize the light intensity correctly
981 public static void InitDefaultHDAdditionalLightData(HDAdditionalLightData lightData)
983 // Special treatment for Unity built-in area light. Change it to our rectangle light
984 var light = lightData.gameObject.GetComponent<Light>();
986 // Set light intensity and unit using its type
989 case LightType.Directional:
990 lightData.lightUnit = LightUnit.Lux;
991 lightData.intensity = k_DefaultDirectionalLightIntensity;
993 case LightType.Rectangle: // Rectangle by default when light is created
994 lightData.lightUnit = LightUnit.Lumen;
995 lightData.intensity = k_DefaultAreaLightIntensity;
996 light.shadows = LightShadows.None;
998 case LightType.Point:
1000 lightData.lightUnit = LightUnit.Lumen;
1001 lightData.intensity = k_DefaultPunctualLightIntensity;
1005 // Sanity check: lightData.lightTypeExtent is init to LightTypeExtent.Punctual (in case for unknow reasons we recreate additional data on an existing line)
1006 if (light.type == LightType.Rectangle && lightData.lightTypeExtent == LightTypeExtent.Punctual)
1008 lightData.lightTypeExtent = LightTypeExtent.Rectangle;
1009 light.type = LightType.Point; // Same as in HDLightEditor
1011 light.lightmapBakeType = LightmapBakeType.Realtime;
1015 // We don't use the global settings of shadow mask by default
1016 light.lightShadowCasterMode = LightShadowCasterMode.Everything;
1024 public void OnBeforeSerialize()
1029 public void OnAfterDeserialize()
1031 // Note: the field version is deprecated but we keep it for retro-compatibility reasons, you should use m_Version instead
1032 #pragma warning disable 618
1033 if (version <= 1.0f)
1034 #pragma warning restore 618
1036 // Note: We can't access to the light component in OnAfterSerialize as it is not init() yet,
1037 // so instead we use a boolean to do the upgrade in OnEnable().
1038 // However OnEnable is not call when the light is disabled, so the HDLightEditor also call
1039 // the UpgradeLight() code in this case
1040 needsIntensityUpdate_1_0 = true;
1044 private void OnEnable()
1049 public void UpgradeLight()
1051 // Disable the warning generated by deprecated fields (areaIntensity, directionalIntensity, ...)
1052 #pragma warning disable 618
1054 // If we are deserializing an old version, convert the light intensity to the new system
1055 if (needsIntensityUpdate_1_0)
1057 switch (lightTypeExtent)
1059 case LightTypeExtent.Punctual:
1060 switch (legacyLight.type)
1062 case LightType.Directional:
1063 lightUnit = LightUnit.Lux;
1064 intensity = directionalIntensity;
1066 case LightType.Spot:
1067 case LightType.Point:
1068 lightUnit = LightUnit.Lumen;
1069 intensity = punctualIntensity;
1073 case LightTypeExtent.Tube:
1074 case LightTypeExtent.Rectangle:
1075 lightUnit = LightUnit.Lumen;
1076 intensity = areaIntensity;
1079 needsIntensityUpdate_1_0 = false;
1083 // ShadowNearPlane have been move to HDRP as default legacy unity clamp it to 0.1 and we need to be able to go below that
1084 shadowNearPlane = legacyLight.shadowNearPlane;
1088 legacyLight.renderingLayerMask = LightLayerToRenderingLayerMask((int)lightLayers, legacyLight.renderingLayerMask);
1092 // When we upgrade the option to decouple light and shadow layers will be disabled
1093 // so we can sync the shadow layer mask (from the legacyLight) and the new light layer mask
1094 lightlayersMask = (LightLayerEnum)RenderingLayerMaskToLightLayer(legacyLight.renderingLayerMask);
1097 m_Version = currentVersion;
1098 version = currentVersion;
1100 #pragma warning restore 0618
1104 /// Converts a light layer into a rendering layer mask.
1106 /// Light layer is stored in the first 8 bit of the rendering layer mask.
1108 /// NOTE: light layers are obsolete, use directly renderingLayerMask.
1110 /// <param name="lightLayer">The light layer, only the first 8 bits will be used.</param>
1111 /// <param name="renderingLayerMask">Current renderingLayerMask, only the last 24 bits will be used.</param>
1112 /// <returns></returns>
1113 internal static int LightLayerToRenderingLayerMask(int lightLayer, int renderingLayerMask)
1115 var renderingLayerMask_u32 = (uint)renderingLayerMask;
1116 var lightLayer_u8 = (byte)lightLayer;
1117 return (int)((renderingLayerMask_u32 & 0xFFFFFF00) | lightLayer_u8);
1121 /// Converts a renderingLayerMask into a lightLayer.
1123 /// NOTE: light layers are obsolete, use directly renderingLayerMask.
1125 /// <param name="renderingLayerMask"></param>
1126 /// <returns></returns>
1127 internal static int RenderingLayerMaskToLightLayer(int renderingLayerMask)
1128 => (byte)renderingLayerMask;