using System; using System.Linq.Expressions; using UnityEngine; using UnityEngine.Rendering.HighDefinition; using UnityEngine.Rendering; namespace UnityEditor.Rendering.HighDefinition { using CED = CoreEditorDrawer; static partial class HDLightUI { public static class ScalableSettings { public static IntScalableSetting ShadowResolution(HDLightType lightType, HDRenderPipelineAsset hdrp) { switch (lightType) { case HDLightType.Directional: return HDAdditionalLightData.ScalableSettings.ShadowResolutionDirectional(hdrp); case HDLightType.Point: return HDAdditionalLightData.ScalableSettings.ShadowResolutionPunctual(hdrp); case HDLightType.Spot: return HDAdditionalLightData.ScalableSettings.ShadowResolutionPunctual(hdrp); case HDLightType.Area: return HDAdditionalLightData.ScalableSettings.ShadowResolutionArea(hdrp); default: throw new ArgumentOutOfRangeException(nameof(lightType)); } } } enum ShadowmaskMode { Shadowmask, DistanceShadowmask } enum Expandable { General = 1 << 0, Shape = 1 << 1, Emission = 1 << 2, Volumetric = 1 << 3, Shadows = 1 << 4, ShadowMap = 1 << 5, ContactShadow = 1 << 6, BakedShadow = 1 << 7, ShadowQuality = 1 << 8, CelestialBody = 1 << 9, } enum AdvancedMode { General = 1 << 0, Shape = 1 << 1, Emission = 1 << 2, Shadow = 1 << 3, } const float k_MinLightSize = 0.01f; // Provide a small size of 1cm for line light readonly static ExpandedState k_ExpandedState = new ExpandedState(~(-1), "HDRP"); readonly static LightUnitSliderUIDrawer k_LightUnitSliderUIDrawer = new LightUnitSliderUIDrawer(); public static readonly CED.IDrawer Inspector; static bool GetAdvanced(AdvancedMode mask, SerializedHDLight serialized, Editor owner) { return (serialized.showAdditionalSettings.intValue & (int)mask) != 0; } static void SetAdvanced(AdvancedMode mask, bool value, SerializedHDLight serialized, Editor owner) { if (value) { serialized.showAdditionalSettings.intValue |= (int)mask; } else { serialized.showAdditionalSettings.intValue &= ~(int)mask; } } static void SwitchAdvanced(AdvancedMode mask, SerializedHDLight serialized, Editor owner) { if ((serialized.showAdditionalSettings.intValue & (int)mask) != 0) { serialized.showAdditionalSettings.intValue &= ~(int)mask; } else { serialized.showAdditionalSettings.intValue |= (int)mask; } } static Action SliderWithTexture; static HDLightUI() { Inspector = CED.Group( CED.AdvancedFoldoutGroup(s_Styles.generalHeader, Expandable.General, k_ExpandedState, (serialized, owner) => GetAdvanced(AdvancedMode.General, serialized, owner), (serialized, owner) => SwitchAdvanced(AdvancedMode.General, serialized, owner), DrawGeneralContent, DrawGeneralAdvancedContent ), CED.FoldoutGroup(s_Styles.shapeHeader, Expandable.Shape, k_ExpandedState, DrawShapeContent), CED.Conditional((serialized, owner) => serialized.type == HDLightType.Directional && !serialized.settings.isCompletelyBaked, CED.FoldoutGroup(s_Styles.celestialBodyHeader, Expandable.CelestialBody, k_ExpandedState, DrawCelestialBodyContent)), CED.AdvancedFoldoutGroup(s_Styles.emissionHeader, Expandable.Emission, k_ExpandedState, (serialized, owner) => GetAdvanced(AdvancedMode.Emission, serialized, owner), (serialized, owner) => SwitchAdvanced(AdvancedMode.Emission, serialized, owner), DrawEmissionContent, DrawEmissionAdvancedContent ), CED.Conditional((serialized, owner) => serialized.type != HDLightType.Area && !serialized.settings.isCompletelyBaked, CED.FoldoutGroup(s_Styles.volumetricHeader, Expandable.Volumetric, k_ExpandedState, DrawVolumetric)), CED.Conditional((serialized, owner) => { HDLightType type = serialized.type; return type != HDLightType.Area || type == HDLightType.Area && serialized.areaLightShape != AreaLightShape.Tube; }, CED.TernaryConditional((serialized, owner) => !serialized.settings.isCompletelyBaked, CED.AdvancedFoldoutGroup(s_Styles.shadowHeader, Expandable.Shadows, k_ExpandedState, (serialized, owner) => GetAdvanced(AdvancedMode.Shadow, serialized, owner), (serialized, owner) => SwitchAdvanced(AdvancedMode.Shadow, serialized, owner), CED.Group( CED.FoldoutGroup(s_Styles.shadowMapSubHeader, Expandable.ShadowMap, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent | FoldoutOption.NoSpaceAtEnd, DrawShadowMapContent), CED.Conditional((serialized, owner) => GetAdvanced(AdvancedMode.Shadow, serialized, owner) && k_ExpandedState[Expandable.ShadowMap], CED.Group(GroupOption.Indent, DrawShadowMapAdvancedContent)), CED.space, CED.Conditional((serialized, owner) => GetAdvanced(AdvancedMode.Shadow, serialized, owner) && HasShadowQualitySettingsUI(HDShadowFilteringQuality.High, serialized, owner), CED.FoldoutGroup(s_Styles.highShadowQualitySubHeader, Expandable.ShadowQuality, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent, DrawHighShadowSettingsContent)), CED.Conditional((serialized, owner) => HasShadowQualitySettingsUI(HDShadowFilteringQuality.Medium, serialized, owner), CED.FoldoutGroup(s_Styles.mediumShadowQualitySubHeader, Expandable.ShadowQuality, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent, DrawMediumShadowSettingsContent)), CED.Conditional((serialized, owner) => HasShadowQualitySettingsUI(HDShadowFilteringQuality.Low, serialized, owner), CED.FoldoutGroup(s_Styles.lowShadowQualitySubHeader, Expandable.ShadowQuality, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent, DrawLowShadowSettingsContent)), CED.Conditional((serialized, owner) => serialized.type != HDLightType.Area, CED.FoldoutGroup(s_Styles.contactShadowsSubHeader, Expandable.ContactShadow, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent | FoldoutOption.NoSpaceAtEnd, DrawContactShadowsContent) ) ), CED.noop //will only add parameter in first sub header ), CED.FoldoutGroup(s_Styles.shadowHeader, Expandable.Shadows, k_ExpandedState, CED.FoldoutGroup(s_Styles.bakedShadowsSubHeader, Expandable.BakedShadow, k_ExpandedState, FoldoutOption.SubFoldout | FoldoutOption.Indent | FoldoutOption.NoSpaceAtEnd, DrawBakedShadowsContent)) ) ) ); //quicker than standard reflection as it is compiled var paramLabel = Expression.Parameter(typeof(GUIContent), "label"); var paramProperty = Expression.Parameter(typeof(SerializedProperty), "property"); var paramSettings = Expression.Parameter(typeof(LightEditor.Settings), "settings"); System.Reflection.MethodInfo sliderWithTextureInfo = typeof(EditorGUILayout) .GetMethod( "SliderWithTexture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, null, System.Reflection.CallingConventions.Any, new[] { typeof(GUIContent), typeof(SerializedProperty), typeof(float), typeof(float), typeof(float), typeof(Texture2D), typeof(GUILayoutOption[]) }, null); var sliderWithTextureCall = Expression.Call( sliderWithTextureInfo, paramLabel, paramProperty, Expression.Constant((float)typeof(LightEditor.Settings).GetField("kMinKelvin", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetRawConstantValue()), Expression.Constant((float)typeof(LightEditor.Settings).GetField("kMaxKelvin", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetRawConstantValue()), Expression.Constant((float)typeof(LightEditor.Settings).GetField("kSliderPower", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetRawConstantValue()), Expression.Field(paramSettings, typeof(LightEditor.Settings).GetField("m_KelvinGradientTexture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)), Expression.Constant(null, typeof(GUILayoutOption[]))); var lambda = Expression.Lambda>(sliderWithTextureCall, paramLabel, paramProperty, paramSettings); SliderWithTexture = lambda.Compile(); } static void DrawGeneralContent(SerializedHDLight serialized, Editor owner) { EditorGUI.BeginChangeCheck(); Rect lineRect = EditorGUILayout.GetControlRect(); HDLightType lightType = serialized.type; HDLightType updatedLightType; //Partial support for prefab. There is no way to fully support it at the moment. //Missing support on the Apply and Revert contextual menu on Label for Prefab overrides. They need to be done two times. //(This will continue unless we remove AdditionalDatas) using (new SerializedHDLight.LightTypeEditionScope(lineRect, s_Styles.shape, serialized)) { EditorGUI.showMixedValue = lightType == (HDLightType)(-1); int index = Array.FindIndex((HDLightType[])Enum.GetValues(typeof(HDLightType)), x => x == lightType); updatedLightType = (HDLightType)EditorGUI.Popup(lineRect, s_Styles.shape, index, s_Styles.shapeNames); } if (EditorGUI.EndChangeCheck()) { serialized.type = updatedLightType; //also register undo if (updatedLightType == HDLightType.Area) { switch (serialized.areaLightShape) { case AreaLightShape.Rectangle: serialized.shapeWidth.floatValue = Mathf.Max(serialized.shapeWidth.floatValue, k_MinLightSize); serialized.shapeHeight.floatValue = Mathf.Max(serialized.shapeHeight.floatValue, k_MinLightSize); break; case AreaLightShape.Tube: serialized.settings.shadowsType.SetEnumValue(LightShadows.None); serialized.shapeWidth.floatValue = Mathf.Max(serialized.shapeWidth.floatValue, k_MinLightSize); break; case AreaLightShape.Disc: //nothing to do break; case (AreaLightShape)(-1): // don't do anything, this is just to handle multi selection break; } } UpdateLightIntensityUnit(serialized, owner); // For GI we need to detect any change on additional data and call SetLightDirty + For intensity we need to detect light shape change serialized.needUpdateAreaLightEmissiveMeshComponents = true; serialized.FetchAreaLightEmissiveMeshComponents(); SetLightsDirty(owner); // Should be apply only to parameter that's affect GI, but make the code cleaner } EditorGUI.showMixedValue = false; // Draw the mode, for Tube and Disc lights, there is only one choice, so we can disable the enum. using (new EditorGUI.DisabledScope(serialized.areaLightShape == AreaLightShape.Tube || serialized.areaLightShape == AreaLightShape.Disc)) serialized.settings.DrawLightmapping(); if (updatedLightType == HDLightType.Area) { switch (serialized.areaLightShape) { case AreaLightShape.Tube: if (serialized.settings.isBakedOrMixed) EditorGUILayout.HelpBox("Tube Area Lights are realtime only.", MessageType.Error); break; case AreaLightShape.Disc: if (!serialized.settings.isCompletelyBaked) EditorGUILayout.HelpBox("Disc Area Lights are baked only.", MessageType.Error); break; } } } static void DrawGeneralAdvancedContent(SerializedHDLight serialized, Editor owner) { using (new EditorGUI.DisabledScope(!HDUtils.hdrpSettings.supportLightLayers)) { using (var change = new EditorGUI.ChangeCheckScope()) { EditorGUILayout.PropertyField(serialized.lightlayersMask, s_Styles.lightLayer); // If we're not in decoupled mode for light layers, we sync light with shadow layers: if (serialized.linkLightLayers.boolValue && change.changed && !serialized.lightlayersMask.hasMultipleDifferentValues) SyncLightAndShadowLayers(serialized, owner); } } } static void DrawShapeContent(SerializedHDLight serialized, Editor owner) { EditorGUI.BeginChangeCheck(); // For GI we need to detect any change on additional data and call SetLightDirty + For intensity we need to detect light shape change // LightShape is HD specific, it need to drive LightType from the original LightType // when it make sense, so the GI is still in sync with the light shape switch (serialized.type) { case HDLightType.Directional: EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.angularDiameter, s_Styles.angularDiameter); if (EditorGUI.EndChangeCheck()) { serialized.angularDiameter.floatValue = Mathf.Clamp(serialized.angularDiameter.floatValue, 0, 90); serialized.settings.bakedShadowAngleProp.floatValue = serialized.angularDiameter.floatValue; } break; case HDLightType.Point: EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.shapeRadius, s_Styles.lightRadius); if (EditorGUI.EndChangeCheck()) { //Also affect baked shadows serialized.settings.bakedShadowRadiusProp.floatValue = serialized.shapeRadius.floatValue; } break; case HDLightType.Spot: EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.spotLightShape, s_Styles.spotLightShape); if (EditorGUI.EndChangeCheck()) { UpdateLightIntensityUnit(serialized, owner); } switch (serialized.spotLightShape.GetEnumValue()) { case SpotLightShape.Box: // Box directional light. EditorGUILayout.PropertyField(serialized.shapeWidth, s_Styles.shapeWidthBox); EditorGUILayout.PropertyField(serialized.shapeHeight, s_Styles.shapeHeightBox); break; case SpotLightShape.Cone: // Cone spot projector EditorGUI.BeginChangeCheck(); EditorGUILayout.Slider(serialized.settings.spotAngle, HDAdditionalLightData.k_MinSpotAngle, HDAdditionalLightData.k_MaxSpotAngle, s_Styles.outterAngle); if (EditorGUI.EndChangeCheck()) { serialized.customSpotLightShadowCone.floatValue = Math.Min(serialized.customSpotLightShadowCone.floatValue, serialized.settings.spotAngle.floatValue); } EditorGUILayout.PropertyField(serialized.spotInnerPercent, s_Styles.spotInnerPercent); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.shapeRadius, s_Styles.lightRadius); if (EditorGUI.EndChangeCheck()) { //Also affect baked shadows serialized.settings.bakedShadowRadiusProp.floatValue = serialized.shapeRadius.floatValue; } break; case SpotLightShape.Pyramid: // pyramid spot projector EditorGUI.BeginChangeCheck(); serialized.settings.DrawSpotAngle(); if (EditorGUI.EndChangeCheck()) { serialized.customSpotLightShadowCone.floatValue = Math.Min(serialized.customSpotLightShadowCone.floatValue, serialized.settings.spotAngle.floatValue); } EditorGUILayout.Slider(serialized.aspectRatio, HDAdditionalLightData.k_MinAspectRatio, HDAdditionalLightData.k_MaxAspectRatio, s_Styles.aspectRatioPyramid); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.shapeRadius, s_Styles.lightRadius); if (EditorGUI.EndChangeCheck()) { //Also affect baked shadows serialized.settings.bakedShadowRadiusProp.floatValue = serialized.shapeRadius.floatValue; } break; case (SpotLightShape)(-1): //multiple different values using (new EditorGUI.DisabledScope(true)) EditorGUILayout.LabelField("Multiple different spot Shapes in selection"); break; default: Debug.Assert(false, "Not implemented spot light shape"); break; } break; case HDLightType.Area: EditorGUI.BeginChangeCheck(); Rect lineRect = EditorGUILayout.GetControlRect(); AreaLightShape updatedAreaLightShape; //Partial support for prefab. There is no way to fully support it at the moment. //Missing support on the Apply and Revert contextual menu on Label for Prefab overrides. They need to be done two times. //(This will continue unless we have our own handling for Disc or remove AdditionalDatas) using (new SerializedHDLight.AreaLightShapeEditionScope(lineRect, s_Styles.shape, serialized)) { AreaLightShape areaLightShape = serialized.areaLightShape; EditorGUI.showMixedValue = areaLightShape == (AreaLightShape)(-1); int index = Array.FindIndex((AreaLightShape[])Enum.GetValues(typeof(AreaLightShape)), x => x == areaLightShape); updatedAreaLightShape = (AreaLightShape)EditorGUI.Popup(lineRect, s_Styles.areaLightShape, index, s_Styles.areaShapeNames); } if (EditorGUI.EndChangeCheck()) { serialized.areaLightShape = updatedAreaLightShape; //also register undo UpdateLightIntensityUnit(serialized, owner); } EditorGUI.showMixedValue = false; switch (updatedAreaLightShape) { case AreaLightShape.Rectangle: EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.shapeWidth, s_Styles.shapeWidthRect); EditorGUILayout.PropertyField(serialized.shapeHeight, s_Styles.shapeHeightRect); if (ShaderConfig.s_BarnDoor == 1) { EditorGUILayout.PropertyField(serialized.barnDoorAngle, s_Styles.barnDoorAngle); EditorGUILayout.PropertyField(serialized.barnDoorLength, s_Styles.barnDoorLength); } if (EditorGUI.EndChangeCheck()) { serialized.settings.areaSizeX.floatValue = serialized.shapeWidth.floatValue; serialized.settings.areaSizeY.floatValue = serialized.shapeHeight.floatValue; if (ShaderConfig.s_BarnDoor == 1) { serialized.barnDoorAngle.floatValue = Mathf.Clamp(serialized.barnDoorAngle.floatValue, 0.0f, 90.0f); serialized.barnDoorLength.floatValue = Mathf.Clamp(serialized.barnDoorLength.floatValue, 0.0f, float.MaxValue); } } break; case AreaLightShape.Tube: EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.shapeWidth, s_Styles.shapeWidthTube); if (EditorGUI.EndChangeCheck()) { // Fake line with a small rectangle in vanilla unity for GI serialized.settings.areaSizeX.floatValue = serialized.shapeWidth.floatValue; serialized.settings.areaSizeY.floatValue = k_MinLightSize; } break; case AreaLightShape.Disc: //draw the built-in area light control at the moment as everything is handled by built-in serialized.settings.DrawArea(); serialized.displayAreaLightEmissiveMesh.boolValue = false; //force deactivate emissive mesh for Disc (not supported) break; case (AreaLightShape)(-1): //multiple different values using (new EditorGUI.DisabledScope(true)) EditorGUILayout.LabelField("Multiple different area Shapes in selection"); break; default: Debug.Assert(false, "Not implemented area light shape"); break; } break; case (HDLightType)(-1): //multiple different values using (new EditorGUI.DisabledScope(true)) EditorGUILayout.LabelField("Multiple different Types in selection"); break; default: Debug.Assert(false, "Not implemented light type"); break; } if (EditorGUI.EndChangeCheck()) { // Light size must be non-zero, else we get NaNs. serialized.shapeWidth.floatValue = Mathf.Max(serialized.shapeWidth.floatValue, k_MinLightSize); serialized.shapeHeight.floatValue = Mathf.Max(serialized.shapeHeight.floatValue, k_MinLightSize); serialized.shapeRadius.floatValue = Mathf.Max(serialized.shapeRadius.floatValue, 0.0f); serialized.needUpdateAreaLightEmissiveMeshComponents = true; SetLightsDirty(owner); // Should be apply only to parameter that's affect GI, but make the code cleaner } } static void DrawCelestialBodyContent(SerializedHDLight serialized, Editor owner) { EditorGUI.BeginChangeCheck(); { EditorGUILayout.PropertyField(serialized.interactsWithSky, s_Styles.interactsWithSky); using (new EditorGUI.DisabledScope(!serialized.interactsWithSky.boolValue)) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.flareSize, s_Styles.flareSize); EditorGUILayout.PropertyField(serialized.flareFalloff, s_Styles.flareFalloff); EditorGUILayout.PropertyField(serialized.flareTint, s_Styles.flareTint); EditorGUILayout.PropertyField(serialized.surfaceTexture, s_Styles.surfaceTexture); EditorGUILayout.PropertyField(serialized.surfaceTint, s_Styles.surfaceTint); EditorGUILayout.PropertyField(serialized.distance, s_Styles.distance); EditorGUI.indentLevel--; } } if (EditorGUI.EndChangeCheck()) { // Clamp the value and also affect baked shadows. serialized.flareSize.floatValue = Mathf.Clamp(serialized.flareSize.floatValue, 0, 90); serialized.flareFalloff.floatValue = Mathf.Max(serialized.flareFalloff.floatValue, 0); serialized.distance.floatValue = Mathf.Max(serialized.distance.floatValue, 0); } } static void UpdateLightIntensityUnit(SerializedHDLight serialized, Editor owner) { HDLightType lightType = serialized.type; // Box are local directional light if (lightType == HDLightType.Directional || (lightType == HDLightType.Spot && (serialized.spotLightShape.GetEnumValue() == SpotLightShape.Box))) { serialized.lightUnit.SetEnumValue((LightUnit)DirectionalLightUnit.Lux); // We need to reset luxAtDistance to neutral when changing to (local) directional light, otherwise first display value ins't correct serialized.luxAtDistance.floatValue = 1.0f; } } static void DrawLightIntensityUnitPopup(Rect rect, SerializedHDLight serialized, Editor owner) { LightUnit selectedLightUnit; LightUnit oldLigthUnit = serialized.lightUnit.GetEnumValue(); EditorGUI.showMixedValue = serialized.lightUnit.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); EditorGUI.BeginProperty(rect, GUIContent.none, serialized.lightUnit); switch (serialized.type) { case HDLightType.Directional: selectedLightUnit = (LightUnit)EditorGUI.EnumPopup(rect, (DirectionalLightUnit)serialized.lightUnit.GetEnumValue()); break; case HDLightType.Point: selectedLightUnit = (LightUnit)EditorGUI.EnumPopup(rect, (PunctualLightUnit)serialized.lightUnit.GetEnumValue()); break; case HDLightType.Spot: if (serialized.spotLightShape.GetEnumValue() == SpotLightShape.Box) selectedLightUnit = (LightUnit)EditorGUI.EnumPopup(rect, (DirectionalLightUnit)serialized.lightUnit.GetEnumValue()); else selectedLightUnit = (LightUnit)EditorGUI.EnumPopup(rect, (PunctualLightUnit)serialized.lightUnit.GetEnumValue()); break; default: selectedLightUnit = (LightUnit)EditorGUI.EnumPopup(rect, (AreaLightUnit)serialized.lightUnit.GetEnumValue()); break; } EditorGUI.EndProperty(); EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { ConvertLightIntensity(oldLigthUnit, selectedLightUnit, serialized, owner); serialized.lightUnit.SetEnumValue(selectedLightUnit); } } internal static void ConvertLightIntensity(LightUnit oldLightUnit, LightUnit newLightUnit, SerializedHDLight serialized, Editor owner) { serialized.intensity.floatValue = ConvertLightIntensity(oldLightUnit, newLightUnit, serialized, owner, serialized.intensity.floatValue); } internal static float ConvertLightIntensity(LightUnit oldLightUnit, LightUnit newLightUnit, SerializedHDLight serialized, Editor owner, float intensity) { Light light = (Light)owner.target; // For punctual lights HDLightType lightType = serialized.type; switch (lightType) { case HDLightType.Directional: case HDLightType.Point: case HDLightType.Spot: // Lumen -> if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Candela) intensity = LightUtils.ConvertPunctualLightLumenToCandela(lightType, intensity, light.intensity, serialized.enableSpotReflector.boolValue); else if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Lux) intensity = LightUtils.ConvertPunctualLightLumenToLux(lightType, intensity, light.intensity, serialized.enableSpotReflector.boolValue, serialized.luxAtDistance.floatValue); else if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Ev100) intensity = LightUtils.ConvertPunctualLightLumenToEv(lightType, intensity, light.intensity, serialized.enableSpotReflector.boolValue); // Candela -> else if (oldLightUnit == LightUnit.Candela && newLightUnit == LightUnit.Lumen) intensity = LightUtils.ConvertPunctualLightCandelaToLumen(lightType, serialized.spotLightShape.GetEnumValue(), intensity, serialized.enableSpotReflector.boolValue, light.spotAngle, serialized.aspectRatio.floatValue); else if (oldLightUnit == LightUnit.Candela && newLightUnit == LightUnit.Lux) intensity = LightUtils.ConvertCandelaToLux(intensity, serialized.luxAtDistance.floatValue); else if (oldLightUnit == LightUnit.Candela && newLightUnit == LightUnit.Ev100) intensity = LightUtils.ConvertCandelaToEv(intensity); // Lux -> else if (oldLightUnit == LightUnit.Lux && newLightUnit == LightUnit.Lumen) intensity = LightUtils.ConvertPunctualLightLuxToLumen(lightType, serialized.spotLightShape.GetEnumValue(), intensity, serialized.enableSpotReflector.boolValue, light.spotAngle, serialized.aspectRatio.floatValue, serialized.luxAtDistance.floatValue); else if (oldLightUnit == LightUnit.Lux && newLightUnit == LightUnit.Candela) intensity = LightUtils.ConvertLuxToCandela(intensity, serialized.luxAtDistance.floatValue); else if (oldLightUnit == LightUnit.Lux && newLightUnit == LightUnit.Ev100) intensity = LightUtils.ConvertLuxToEv(intensity, serialized.luxAtDistance.floatValue); // EV100 -> else if (oldLightUnit == LightUnit.Ev100 && newLightUnit == LightUnit.Lumen) intensity = LightUtils.ConvertPunctualLightEvToLumen(lightType, serialized.spotLightShape.GetEnumValue(), intensity, serialized.enableSpotReflector.boolValue, light.spotAngle, serialized.aspectRatio.floatValue); else if (oldLightUnit == LightUnit.Ev100 && newLightUnit == LightUnit.Candela) intensity = LightUtils.ConvertEvToCandela(intensity); else if (oldLightUnit == LightUnit.Ev100 && newLightUnit == LightUnit.Lux) intensity = LightUtils.ConvertEvToLux(intensity, serialized.luxAtDistance.floatValue); break; case HDLightType.Area: if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Nits) intensity = LightUtils.ConvertAreaLightLumenToLuminance(serialized.areaLightShape, intensity, serialized.shapeWidth.floatValue, serialized.shapeHeight.floatValue); if (oldLightUnit == LightUnit.Nits && newLightUnit == LightUnit.Lumen) intensity = LightUtils.ConvertAreaLightLuminanceToLumen(serialized.areaLightShape, intensity, serialized.shapeWidth.floatValue, serialized.shapeHeight.floatValue); if (oldLightUnit == LightUnit.Nits && newLightUnit == LightUnit.Ev100) intensity = LightUtils.ConvertLuminanceToEv(intensity); if (oldLightUnit == LightUnit.Ev100 && newLightUnit == LightUnit.Nits) intensity = LightUtils.ConvertEvToLuminance(intensity); if (oldLightUnit == LightUnit.Ev100 && newLightUnit == LightUnit.Lumen) intensity = LightUtils.ConvertAreaLightEvToLumen(serialized.areaLightShape, intensity, serialized.shapeWidth.floatValue, serialized.shapeHeight.floatValue); if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Ev100) intensity = LightUtils.ConvertAreaLightLumenToEv(serialized.areaLightShape, intensity, serialized.shapeWidth.floatValue, serialized.shapeHeight.floatValue); break; default: case (HDLightType)(-1): // multiple different values break; // do nothing } return intensity; } static void DrawLightIntensityGUILayout(SerializedHDLight serialized, Editor owner) { // Match const defined in EditorGUI.cs const int k_IndentPerLevel = 15; const int k_ValueUnitSeparator = 2; const int k_UnitWidth = 100; float indent = k_IndentPerLevel * EditorGUI.indentLevel; Rect lineRect = EditorGUILayout.GetControlRect(); Rect labelRect = lineRect; labelRect.width = EditorGUIUtility.labelWidth; // Expand to reach both lines of the intensity field. var interlineOffset = EditorGUIUtility.singleLineHeight + 2f; labelRect.height += interlineOffset; //handling of prefab overrides in a parent label GUIContent parentLabel = s_Styles.lightIntensity; parentLabel = EditorGUI.BeginProperty(labelRect, parentLabel, serialized.lightUnit); parentLabel = EditorGUI.BeginProperty(labelRect, parentLabel, serialized.intensity); { // Restore the original rect for actually drawing the label. labelRect.height -= interlineOffset; EditorGUI.LabelField(labelRect, parentLabel); } EditorGUI.EndProperty(); EditorGUI.EndProperty(); // Draw the light unit slider + icon + tooltip Rect lightUnitSliderRect = lineRect; // TODO: Move the value and unit rects to new line lightUnitSliderRect.x += EditorGUIUtility.labelWidth + k_ValueUnitSeparator; lightUnitSliderRect.width -= EditorGUIUtility.labelWidth + k_ValueUnitSeparator; var lightType = serialized.type; var lightUnit = serialized.lightUnit.GetEnumValue(); k_LightUnitSliderUIDrawer.SetSerializedObject(serialized.serializedObject); k_LightUnitSliderUIDrawer.Draw(lightType, lightUnit, serialized.intensity, lightUnitSliderRect, serialized, owner); // We use PropertyField to draw the value to keep the handle at left of the field // This will apply the indent again thus we need to remove it time for alignment Rect valueRect = EditorGUILayout.GetControlRect(); labelRect.width = EditorGUIUtility.labelWidth; valueRect.width += indent - k_ValueUnitSeparator - k_UnitWidth; Rect unitRect = valueRect; unitRect.x += valueRect.width - indent + k_ValueUnitSeparator; unitRect.width = k_UnitWidth + .5f; // Draw the unit textfield EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(valueRect, serialized.intensity, s_Styles.empty); DrawLightIntensityUnitPopup(unitRect, serialized, owner); if (EditorGUI.EndChangeCheck()) { serialized.intensity.floatValue = Mathf.Max(serialized.intensity.floatValue, 0.0f); } } static void DrawEmissionContent(SerializedHDLight serialized, Editor owner) { using (var changes = new EditorGUI.ChangeCheckScope()) { if (GraphicsSettings.lightsUseLinearIntensity && GraphicsSettings.lightsUseColorTemperature) { // Use the color temperature bool to create a popup dropdown to choose between the two modes. var colorTemperaturePopupValue = Convert.ToInt32(serialized.settings.useColorTemperature.boolValue); var lightAppearanceOptions = new[] { "Color", "Filter and Temperature" }; colorTemperaturePopupValue = EditorGUILayout.Popup(s_Styles.lightAppearance, colorTemperaturePopupValue, lightAppearanceOptions); serialized.settings.useColorTemperature.boolValue = Convert.ToBoolean(colorTemperaturePopupValue); if (serialized.settings.useColorTemperature.boolValue) { EditorGUI.indentLevel += 1; EditorGUILayout.PropertyField(serialized.settings.color, s_Styles.colorFilter); // Light unit slider const int k_ValueUnitSeparator = 2; var lineRect = EditorGUILayout.GetControlRect(); var labelRect = lineRect; labelRect.width = EditorGUIUtility.labelWidth; EditorGUI.LabelField(labelRect, s_Styles.colorTemperature); var temperatureSliderRect = lineRect; temperatureSliderRect.x += EditorGUIUtility.labelWidth + k_ValueUnitSeparator; temperatureSliderRect.width -= EditorGUIUtility.labelWidth + k_ValueUnitSeparator; k_LightUnitSliderUIDrawer.DrawTemperatureSlider(serialized.settings, serialized.settings.colorTemperature, temperatureSliderRect); // Value and unit label // Match const defined in EditorGUI.cs const int k_IndentPerLevel = 15; const int k_UnitWidth = 100 + k_IndentPerLevel; int indent = k_IndentPerLevel * EditorGUI.indentLevel; Rect valueRect = EditorGUILayout.GetControlRect(); valueRect.width += indent - k_ValueUnitSeparator - k_UnitWidth; Rect unitRect = valueRect; unitRect.x += valueRect.width - indent + k_ValueUnitSeparator; unitRect.width = k_UnitWidth + .5f; EditorGUI.PropertyField(valueRect, serialized.settings.colorTemperature, s_Styles.empty); EditorGUI.Popup(unitRect, 0, new[] { "Kelvin" }); EditorGUI.indentLevel -= 1; } else EditorGUILayout.PropertyField(serialized.settings.color, s_Styles.color); } else EditorGUILayout.PropertyField(serialized.settings.color, s_Styles.color); } DrawLightIntensityGUILayout(serialized, owner); HDLightType lightType = serialized.type; SpotLightShape spotLightShape = serialized.spotLightShape.GetEnumValue(); LightUnit lightUnit = serialized.lightUnit.GetEnumValue(); if (lightType != HDLightType.Directional // Box are local directional light and shouldn't display the Lux At widget. It use only lux && !(lightType == HDLightType.Spot && (spotLightShape == SpotLightShape.Box)) && lightUnit == (LightUnit)PunctualLightUnit.Lux) { EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.luxAtDistance, s_Styles.luxAtDistance); if (EditorGUI.EndChangeCheck()) { serialized.luxAtDistance.floatValue = Mathf.Max(serialized.luxAtDistance.floatValue, 0.01f); } EditorGUI.indentLevel--; } if (lightType == HDLightType.Spot && (spotLightShape == SpotLightShape.Cone || spotLightShape == SpotLightShape.Pyramid) // Display reflector only in advance mode && (lightUnit == (int)PunctualLightUnit.Lumen && GetAdvanced(AdvancedMode.Emission, serialized, owner))) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.enableSpotReflector, s_Styles.enableSpotReflector); EditorGUI.indentLevel--; } if (lightType != HDLightType.Directional) { EditorGUI.BeginChangeCheck(); #if UNITY_2020_1_OR_NEWER serialized.settings.DrawRange(); #else serialized.settings.DrawRange(false); #endif // Make sure the range is not 0.0 serialized.settings.range.floatValue = Mathf.Max(0.001f, serialized.settings.range.floatValue); if (EditorGUI.EndChangeCheck()) { // For GI we need to detect any change on additional data and call SetLightDirty + For intensity we need to detect light shape change serialized.needUpdateAreaLightEmissiveMeshComponents = true; SetLightsDirty(owner); // Should be apply only to parameter that's affect GI, but make the code cleaner } } serialized.settings.DrawBounceIntensity(); EditorGUI.BeginChangeCheck(); // For GI we need to detect any change on additional data and call SetLightDirty if (lightType != HDLightType.Area) { serialized.settings.DrawCookie(); // When directional light use a cookie, it can control the size if (serialized.settings.cookie != null && lightType == HDLightType.Directional) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.shapeWidth, s_Styles.cookieSizeX); EditorGUILayout.PropertyField(serialized.shapeHeight, s_Styles.cookieSizeY); EditorGUI.indentLevel--; } ShowCookieTextureWarnings(serialized.settings.cookie, serialized.settings.isCompletelyBaked || serialized.settings.isBakedOrMixed); } else if (serialized.areaLightShape == AreaLightShape.Rectangle || serialized.areaLightShape == AreaLightShape.Disc) { EditorGUILayout.ObjectField(serialized.areaLightCookie, s_Styles.areaLightCookie); ShowCookieTextureWarnings(serialized.areaLightCookie.objectReferenceValue as Texture, serialized.settings.isCompletelyBaked || serialized.settings.isBakedOrMixed); } if (serialized.type == HDLightType.Point || serialized.type == HDLightType.Spot || (serialized.type == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle)) { EditorGUI.BeginChangeCheck(); UnityEngine.Object iesAsset = EditorGUILayout.ObjectField( s_Styles.iesTexture, serialized.type == HDLightType.Point ? serialized.iesPoint.objectReferenceValue : serialized.iesSpot.objectReferenceValue, typeof(IESObject), false); if (EditorGUI.EndChangeCheck()) { SerializedProperty pointTex = serialized.iesPoint; SerializedProperty spotTex = serialized.iesSpot; if (iesAsset == null) { pointTex.objectReferenceValue = null; spotTex.objectReferenceValue = null; } else { string guid; long localID; AssetDatabase.TryGetGUIDAndLocalFileIdentifier(iesAsset, out guid, out localID); string path = AssetDatabase.GUIDToAssetPath(guid); UnityEngine.Object[] textures = AssetDatabase.LoadAllAssetRepresentationsAtPath(path); foreach (var subAsset in textures) { if (AssetDatabase.IsSubAsset(subAsset) && subAsset.name.EndsWith("-Cube-IES")) { pointTex.objectReferenceValue = subAsset; } else if (AssetDatabase.IsSubAsset(subAsset) && subAsset.name.EndsWith("-2D-IES")) { spotTex.objectReferenceValue = subAsset; } } } serialized.iesPoint.serializedObject.ApplyModifiedProperties(); serialized.iesSpot.serializedObject.ApplyModifiedProperties(); } } if (serialized.type == HDLightType.Spot && serialized.spotLightShape.enumValueIndex == (int)SpotLightShape.Cone && serialized.iesSpot.objectReferenceValue != null) { EditorGUILayout.PropertyField(serialized.spotIESCutoffPercent, s_Styles.spotIESCutoffPercent); } if (EditorGUI.EndChangeCheck()) { serialized.needUpdateAreaLightEmissiveMeshComponents = true; SetLightsDirty(owner); // Should be apply only to parameter that's affect GI, but make the code cleaner } } static void ShowCookieTextureWarnings(Texture cookie, bool useBaking) { if (cookie == null) return; // The texture type is stored in the texture importer so we need to get it: TextureImporter texImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(cookie)) as TextureImporter; if (texImporter != null && texImporter.textureType == TextureImporterType.Cookie) { using (new EditorGUILayout.HorizontalScope()) { int indentSpace = (int)EditorGUI.IndentedRect(new Rect()).x; GUILayout.Space(indentSpace); using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { int oldIndentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; GUIStyle wordWrap = new GUIStyle(EditorStyles.miniLabel) { wordWrap = true }; EditorGUILayout.LabelField(s_Styles.cookieTextureTypeError, wordWrap); if (GUILayout.Button("Fix", GUILayout.ExpandHeight(true))) { texImporter.textureType = TextureImporterType.Default; texImporter.SaveAndReimport(); } EditorGUI.indentLevel = oldIndentLevel; } } } if (useBaking && !UnityEditor.EditorSettings.enableCookiesInLightmapper) EditorGUILayout.HelpBox(s_Styles.cookieBaking, MessageType.Warning); if (cookie.width != cookie.height) EditorGUILayout.HelpBox(s_Styles.cookieNonPOT, MessageType.Warning); if (cookie.width < LightCookieManager.k_MinCookieSize || cookie.height < LightCookieManager.k_MinCookieSize) EditorGUILayout.HelpBox(s_Styles.cookieTooSmall, MessageType.Warning); } static void DrawEmissionAdvancedContent(SerializedHDLight serialized, Editor owner) { HDLightType lightType = serialized.type; EditorGUI.BeginChangeCheck(); // For GI we need to detect any change on additional data and call SetLightDirty bool bakedOnly = serialized.settings.isCompletelyBaked; if (!bakedOnly) { EditorGUILayout.PropertyField(serialized.affectDiffuse, s_Styles.affectDiffuse); EditorGUILayout.PropertyField(serialized.affectSpecular, s_Styles.affectSpecular); if (lightType != HDLightType.Directional) { EditorGUILayout.PropertyField(serialized.applyRangeAttenuation, s_Styles.applyRangeAttenuation); EditorGUILayout.PropertyField(serialized.fadeDistance, s_Styles.fadeDistance); } EditorGUILayout.PropertyField(serialized.lightDimmer, s_Styles.lightDimmer); } else if (lightType == HDLightType.Point || lightType == HDLightType.Spot) EditorGUILayout.PropertyField(serialized.applyRangeAttenuation, s_Styles.applyRangeAttenuation); // Emissive mesh for area light only (and not supported on Disc currently) if (lightType == HDLightType.Area && serialized.areaLightShape != AreaLightShape.Disc) { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.displayAreaLightEmissiveMesh, s_Styles.displayAreaLightEmissiveMesh); if (EditorGUI.EndChangeCheck()) { serialized.FetchAreaLightEmissiveMeshComponents(); serialized.needUpdateAreaLightEmissiveMeshComponents = true; } bool showSubArea = serialized.displayAreaLightEmissiveMesh.boolValue && !serialized.displayAreaLightEmissiveMesh.hasMultipleDifferentValues; ++EditorGUI.indentLevel; Rect lineRect = EditorGUILayout.GetControlRect(); ShadowCastingMode newCastShadow; EditorGUI.showMixedValue = serialized.areaLightEmissiveMeshCastShadow.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); using (new SerializedHDLight.AreaLightEmissiveMeshDrawScope(lineRect, s_Styles.areaLightEmissiveMeshCastShadow, showSubArea, serialized.areaLightEmissiveMeshCastShadow, serialized.deportedAreaLightEmissiveMeshCastShadow)) { newCastShadow = (ShadowCastingMode)EditorGUI.EnumPopup(lineRect, s_Styles.areaLightEmissiveMeshCastShadow, (ShadowCastingMode)serialized.areaLightEmissiveMeshCastShadow.intValue); } if (EditorGUI.EndChangeCheck()) { serialized.UpdateAreaLightEmissiveMeshCastShadow(newCastShadow); } EditorGUI.showMixedValue = false; lineRect = EditorGUILayout.GetControlRect(); SerializedHDLight.MotionVector newMotionVector; EditorGUI.showMixedValue = serialized.areaLightEmissiveMeshMotionVector.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); using (new SerializedHDLight.AreaLightEmissiveMeshDrawScope(lineRect, s_Styles.areaLightEmissiveMeshMotionVector, showSubArea, serialized.areaLightEmissiveMeshMotionVector, serialized.deportedAreaLightEmissiveMeshMotionVector)) { newMotionVector = (SerializedHDLight.MotionVector)EditorGUI.EnumPopup(lineRect, s_Styles.areaLightEmissiveMeshMotionVector, (SerializedHDLight.MotionVector)serialized.areaLightEmissiveMeshMotionVector.intValue); } if (EditorGUI.EndChangeCheck()) { serialized.UpdateAreaLightEmissiveMeshMotionVectorGeneration(newMotionVector); } EditorGUI.showMixedValue = false; EditorGUI.showMixedValue = serialized.areaLightEmissiveMeshLayer.hasMultipleDifferentValues || serialized.lightLayer.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); bool toggle; using (new SerializedHDLight.AreaLightEmissiveMeshDrawScope(lineRect, s_Styles.areaLightEmissiveMeshSameLayer, showSubArea, serialized.areaLightEmissiveMeshLayer, serialized.deportedAreaLightEmissiveMeshLayer)) { toggle = EditorGUILayout.Toggle(s_Styles.areaLightEmissiveMeshSameLayer, serialized.areaLightEmissiveMeshLayer.intValue == -1); } if (EditorGUI.EndChangeCheck()) { serialized.UpdateAreaLightEmissiveMeshLayer(serialized.lightLayer.intValue); if (toggle) serialized.areaLightEmissiveMeshLayer.intValue = -1; } EditorGUI.showMixedValue = false; ++EditorGUI.indentLevel; if (toggle || serialized.areaLightEmissiveMeshLayer.hasMultipleDifferentValues) { using (new EditorGUI.DisabledScope(true)) { lineRect = EditorGUILayout.GetControlRect(); EditorGUI.showMixedValue = serialized.areaLightEmissiveMeshLayer.hasMultipleDifferentValues || serialized.lightLayer.hasMultipleDifferentValues; EditorGUI.LayerField(lineRect, s_Styles.areaLightEmissiveMeshCustomLayer, serialized.lightLayer.intValue); EditorGUI.showMixedValue = false; } } else { EditorGUI.showMixedValue = serialized.areaLightEmissiveMeshLayer.hasMultipleDifferentValues; lineRect = EditorGUILayout.GetControlRect(); int layer; EditorGUI.BeginChangeCheck(); using (new SerializedHDLight.AreaLightEmissiveMeshDrawScope(lineRect, s_Styles.areaLightEmissiveMeshCustomLayer, showSubArea, serialized.areaLightEmissiveMeshLayer, serialized.deportedAreaLightEmissiveMeshLayer)) { layer = EditorGUI.LayerField(lineRect, s_Styles.areaLightEmissiveMeshCustomLayer, serialized.areaLightEmissiveMeshLayer.intValue); } if (EditorGUI.EndChangeCheck()) { serialized.UpdateAreaLightEmissiveMeshLayer(layer); } // or if the value of layer got changed using the layer change including child mechanism (strangely apply even if object not editable), // discard the change: the child is not saved anyway so the value in HDAdditionalLightData is the only serialized one. else if (!EditorGUI.showMixedValue && serialized.deportedAreaLightEmissiveMeshLayer != null && !serialized.deportedAreaLightEmissiveMeshLayer.Equals(null) && serialized.areaLightEmissiveMeshLayer.intValue != serialized.deportedAreaLightEmissiveMeshLayer.intValue) { GUI.changed = true; //force register change to handle update and apply later serialized.UpdateAreaLightEmissiveMeshLayer(layer); } EditorGUI.showMixedValue = false; } --EditorGUI.indentLevel; --EditorGUI.indentLevel; } EditorGUILayout.PropertyField(serialized.includeForRayTracing, s_Styles.includeLightForRayTracing); if (EditorGUI.EndChangeCheck()) { serialized.needUpdateAreaLightEmissiveMeshComponents = true; serialized.fadeDistance.floatValue = Mathf.Max(serialized.fadeDistance.floatValue, 0.01f); SetLightsDirty(owner); // Should be apply only to parameter that's affect GI, but make the code cleaner } } static void DrawVolumetric(SerializedHDLight serialized, Editor owner) { EditorGUILayout.PropertyField(serialized.useVolumetric, s_Styles.volumetricEnable); using (new EditorGUI.DisabledScope(!serialized.useVolumetric.boolValue)) { EditorGUILayout.PropertyField(serialized.volumetricDimmer, s_Styles.volumetricDimmer); EditorGUILayout.Slider(serialized.volumetricShadowDimmer, 0.0f, 1.0f, s_Styles.volumetricShadowDimmer); HDLightType lightType = serialized.type; if (lightType != HDLightType.Directional) { EditorGUILayout.PropertyField(serialized.volumetricFadeDistance, s_Styles.volumetricFadeDistance); } } } static bool DrawEnableShadowMap(SerializedHDLight serialized, Editor owne) { Rect lineRect = EditorGUILayout.GetControlRect(); bool newShadowsEnabled; EditorGUI.BeginProperty(lineRect, s_Styles.enableShadowMap, serialized.settings.shadowsType); { bool oldShadowEnabled = serialized.settings.shadowsType.GetEnumValue() != LightShadows.None; newShadowsEnabled = EditorGUI.Toggle(lineRect, s_Styles.enableShadowMap, oldShadowEnabled); if (oldShadowEnabled ^ newShadowsEnabled) { serialized.settings.shadowsType.SetEnumValue(newShadowsEnabled ? LightShadows.Hard : LightShadows.None); } } EditorGUI.EndProperty(); return newShadowsEnabled; } static void DrawShadowMapContent(SerializedHDLight serialized, Editor owner) { var hdrp = HDRenderPipeline.currentAsset; bool newShadowsEnabled = DrawEnableShadowMap(serialized, owner); using (new EditorGUI.DisabledScope(!newShadowsEnabled)) { EditorGUILayout.PropertyField(serialized.shadowUpdateMode, s_Styles.shadowUpdateMode); if (serialized.shadowUpdateMode.intValue > 0 && serialized.type != HDLightType.Directional) { #if UNITY_2021_1_OR_NEWER EditorGUILayout.PropertyField(serialized.shadowAlwaysDrawDynamic, s_Styles.shadowAlwaysDrawDynamic); #endif } EditorGUILayout.PropertyField(serialized.shadowUpdateUponTransformChange, s_Styles.shadowUpdateOnLightTransformChange); HDLightType lightType = serialized.type; using (var change = new EditorGUI.ChangeCheckScope()) { var hasEditorLightShapeMultipleValues = lightType == (HDLightType)(-1); if (hasEditorLightShapeMultipleValues) { serialized.shadowResolution.LevelAndIntGUILayout( s_Styles.shadowResolution, null, null ); } else { var scalableSetting = ScalableSettings.ShadowResolution(lightType, hdrp); serialized.shadowResolution.LevelAndIntGUILayout( s_Styles.shadowResolution, scalableSetting, hdrp.name ); } if (change.changed) serialized.shadowResolution.@override.intValue = Mathf.Max(HDShadowManager.k_MinShadowMapResolution, serialized.shadowResolution.@override.intValue); } if (lightType != HDLightType.Directional) EditorGUILayout.Slider(serialized.shadowNearPlane, HDShadowUtils.k_MinShadowNearPlane, HDShadowUtils.k_MaxShadowNearPlane, s_Styles.shadowNearPlane); if (serialized.settings.isMixed) { using (new EditorGUI.DisabledScope(!HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.supportShadowMask)) { Rect nonLightmappedOnlyRect = EditorGUILayout.GetControlRect(); EditorGUI.BeginProperty(nonLightmappedOnlyRect, s_Styles.nonLightmappedOnly, serialized.nonLightmappedOnly); { EditorGUI.BeginChangeCheck(); ShadowmaskMode shadowmask = serialized.nonLightmappedOnly.boolValue ? ShadowmaskMode.Shadowmask : ShadowmaskMode.DistanceShadowmask; shadowmask = (ShadowmaskMode)EditorGUI.EnumPopup(nonLightmappedOnlyRect, s_Styles.nonLightmappedOnly, shadowmask); if (EditorGUI.EndChangeCheck()) { Undo.RecordObjects(owner.targets, "Light Update Shadowmask Mode"); serialized.nonLightmappedOnly.boolValue = shadowmask == ShadowmaskMode.Shadowmask; foreach (Light target in owner.targets) target.lightShadowCasterMode = shadowmask == ShadowmaskMode.Shadowmask ? LightShadowCasterMode.NonLightmappedOnly : LightShadowCasterMode.Everything; } } EditorGUI.EndProperty(); } } if (lightType == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle) { EditorGUILayout.Slider(serialized.areaLightShadowCone, HDAdditionalLightData.k_MinAreaLightShadowCone, HDAdditionalLightData.k_MaxAreaLightShadowCone, s_Styles.areaLightShadowCone); } if (HDRenderPipeline.pipelineSupportsRayTracing && HDRenderPipeline.pipelineSupportsScreenSpaceShadows) { bool isPunctual = lightType == HDLightType.Point || (lightType == HDLightType.Spot && serialized.spotLightShape.GetEnumValue() == SpotLightShape.Cone); if (isPunctual || (lightType == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle)) { EditorGUILayout.PropertyField(serialized.useRayTracedShadows, s_Styles.useRayTracedShadows); if (serialized.useRayTracedShadows.boolValue) { if (hdrp != null && lightType == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle && (hdrp.currentPlatformRenderPipelineSettings.supportedLitShaderMode != RenderPipelineSettings.SupportedLitShaderMode.DeferredOnly)) EditorGUILayout.HelpBox("Ray traced area light shadows are approximated for the Lit shader when not in deferred mode.", MessageType.Warning); EditorGUI.indentLevel++; // We only support semi transparent shadows for punctual lights if (isPunctual) EditorGUILayout.PropertyField(serialized.semiTransparentShadow, s_Styles.semiTransparentShadow); EditorGUILayout.PropertyField(serialized.numRayTracingSamples, s_Styles.numRayTracingSamples); EditorGUILayout.PropertyField(serialized.filterTracedShadow, s_Styles.denoiseTracedShadow); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.filterSizeTraced, s_Styles.denoiserRadius); // We only support distance based filtering if we have a punctual light source (point or spot) if (isPunctual) EditorGUILayout.PropertyField(serialized.distanceBasedFiltering, s_Styles.distanceBasedFiltering); EditorGUI.indentLevel--; EditorGUI.indentLevel--; } } } // For the moment, we only support screen space rasterized shadows for directional lights if (lightType == HDLightType.Directional && HDRenderPipeline.pipelineSupportsScreenSpaceShadows) { EditorGUILayout.PropertyField(serialized.useScreenSpaceShadows, s_Styles.useScreenSpaceShadows); if (HDRenderPipeline.pipelineSupportsRayTracing) { using (new EditorGUI.DisabledScope(!serialized.useScreenSpaceShadows.boolValue)) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.useRayTracedShadows, s_Styles.useRayTracedShadows); using (new EditorGUI.DisabledScope(!serialized.useRayTracedShadows.boolValue)) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.numRayTracingSamples, s_Styles.numRayTracingSamples); EditorGUILayout.PropertyField(serialized.colorShadow, s_Styles.colorShadow); EditorGUILayout.PropertyField(serialized.filterTracedShadow, s_Styles.denoiseTracedShadow); using (new EditorGUI.DisabledScope(!serialized.filterTracedShadow.boolValue)) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.filterSizeTraced, s_Styles.denoiserRadius); EditorGUI.indentLevel--; } EditorGUI.indentLevel--; } EditorGUI.indentLevel--; } } } } } static void DrawShadowMapAdvancedContent(SerializedHDLight serialized, Editor owner) { using (new EditorGUI.DisabledScope(serialized.settings.shadowsType.GetEnumValue() == LightShadows.None)) { HDLightType lightType = serialized.type; if (lightType == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle) { EditorGUILayout.Slider(serialized.evsmExponent, HDAdditionalLightData.k_MinEvsmExponent, HDAdditionalLightData.k_MaxEvsmExponent, s_Styles.evsmExponent); EditorGUILayout.Slider(serialized.evsmLightLeakBias, HDAdditionalLightData.k_MinEvsmLightLeakBias, HDAdditionalLightData.k_MaxEvsmLightLeakBias, s_Styles.evsmLightLeakBias); EditorGUILayout.Slider(serialized.evsmVarianceBias, HDAdditionalLightData.k_MinEvsmVarianceBias, HDAdditionalLightData.k_MaxEvsmVarianceBias, s_Styles.evsmVarianceBias); EditorGUILayout.IntSlider(serialized.evsmBlurPasses, HDAdditionalLightData.k_MinEvsmBlurPasses, HDAdditionalLightData.k_MaxEvsmBlurPasses, s_Styles.evsmAdditionalBlurPasses); } else { EditorGUILayout.Slider(serialized.slopeBias, 0.0f, 1.0f, s_Styles.slopeBias); EditorGUILayout.Slider(serialized.normalBias, 0.0f, 5.0f, s_Styles.normalBias); if (lightType == HDLightType.Spot && serialized.spotLightShape.GetEnumValue() != SpotLightShape.Box) { EditorGUILayout.PropertyField(serialized.useCustomSpotLightShadowCone, s_Styles.useCustomSpotLightShadowCone); if (serialized.useCustomSpotLightShadowCone.boolValue) { EditorGUILayout.Slider(serialized.customSpotLightShadowCone, 1.0f, serialized.settings.spotAngle.floatValue, s_Styles.customSpotLightShadowCone); } } } // Dimmer and Tint don't have effect on baked shadow if (!serialized.settings.isCompletelyBaked) { EditorGUILayout.Slider(serialized.shadowDimmer, 0.0f, 1.0f, s_Styles.shadowDimmer); EditorGUILayout.PropertyField(serialized.shadowTint, s_Styles.shadowTint); EditorGUILayout.PropertyField(serialized.penumbraTint, s_Styles.penumbraTint); } if (lightType != HDLightType.Directional) { EditorGUILayout.PropertyField(serialized.shadowFadeDistance, s_Styles.shadowFadeDistance); } // Shadow Layers using (new EditorGUI.DisabledScope(!HDUtils.hdrpSettings.supportLightLayers)) { using (var change = new EditorGUI.ChangeCheckScope()) { EditorGUILayout.PropertyField(serialized.linkLightLayers, s_Styles.linkLightAndShadowLayersText); // Undo the changes in the light component because the SyncLightAndShadowLayers will change the value automatically when link is ticked if (change.changed) Undo.RecordObjects(owner.targets, "Undo Light Layers Changed"); } if (!serialized.linkLightLayers.hasMultipleDifferentValues && !serialized.lightlayersMask.hasMultipleDifferentValues) { using (new EditorGUI.DisabledGroupScope(serialized.linkLightLayers.boolValue)) { HDEditorUtils.DrawLightLayerMaskFromInt(s_Styles.shadowLayerMaskText, serialized.settings.renderingLayerMask); } if (serialized.linkLightLayers.boolValue) SyncLightAndShadowLayers(serialized, owner); } } } } static void SyncLightAndShadowLayers(SerializedHDLight serialized, Editor owner) { // If we're not in decoupled mode for light layers, we sync light with shadow layers: foreach (Light target in owner.targets) if (target.renderingLayerMask != serialized.lightlayersMask.intValue) target.renderingLayerMask = serialized.lightlayersMask.intValue; } static void DrawContactShadowsContent(SerializedHDLight serialized, Editor owner) { var hdrp = HDRenderPipeline.currentAsset; SerializedScalableSettingValueUI.LevelAndToggleGUILayout( serialized.contactShadows, s_Styles.contactShadows, HDAdditionalLightData.ScalableSettings.UseContactShadow(hdrp), hdrp.name ); if (HDRenderPipeline.pipelineSupportsRayTracing && serialized.contactShadows.@override.boolValue) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serialized.rayTracedContactShadow, s_Styles.rayTracedContactShadow); EditorGUI.indentLevel--; } } static void DrawBakedShadowsContent(SerializedHDLight serialized, Editor owner) { DrawEnableShadowMap(serialized, owner); if (serialized.type != HDLightType.Directional) EditorGUILayout.Slider(serialized.shadowNearPlane, HDShadowUtils.k_MinShadowNearPlane, HDShadowUtils.k_MaxShadowNearPlane, s_Styles.shadowNearPlane); } static bool HasShadowQualitySettingsUI(HDShadowFilteringQuality quality, SerializedHDLight serialized, Editor owner) { // Handle quality where there is nothing to draw directly here // No PCSS for now with directional light if (quality == HDShadowFilteringQuality.Medium || quality == HDShadowFilteringQuality.Low) return false; // Draw shadow settings using the current shadow algorithm HDShadowInitParameters hdShadowInitParameters = HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.hdShadowInitParams; return hdShadowInitParameters.shadowFilteringQuality == quality; } static void DrawLowShadowSettingsContent(SerializedHDLight serialized, Editor owner) { // Currently there is nothing to display here // when adding something, update IsShadowSettings } static void DrawMediumShadowSettingsContent(SerializedHDLight serialized, Editor owner) { // Currently there is nothing to display here // when adding something, update IsShadowSettings } static void DrawHighShadowSettingsContent(SerializedHDLight serialized, Editor owner) { EditorGUILayout.PropertyField(serialized.blockerSampleCount, s_Styles.blockerSampleCount); EditorGUILayout.PropertyField(serialized.filterSampleCount, s_Styles.filterSampleCount); EditorGUILayout.PropertyField(serialized.minFilterSize, s_Styles.minFilterSize); GUIContent styleForScale = s_Styles.radiusScaleForSoftness; if (serialized.type == HDLightType.Directional) { styleForScale = s_Styles.diameterScaleForSoftness; } EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serialized.scaleForSoftness, styleForScale); if (EditorGUI.EndChangeCheck()) { //Clamp the value and also affect baked shadows serialized.scaleForSoftness.floatValue = Mathf.Max(serialized.scaleForSoftness.floatValue, 0); } } static void DrawVeryHighShadowSettingsContent(SerializedHDLight serialized, Editor owner) { EditorGUILayout.PropertyField(serialized.kernelSize, s_Styles.kernelSize); EditorGUILayout.PropertyField(serialized.lightAngle, s_Styles.lightAngle); EditorGUILayout.PropertyField(serialized.maxDepthBias, s_Styles.maxDepthBias); } static void SetLightsDirty(Editor owner) { foreach (Light light in owner.targets) light.SetLightDirty(); // Should be apply only to parameter that's affect GI, but make the code cleaner } } }