You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
23 KiB
470 lines
23 KiB
using System; |
|
using System.Linq; |
|
using System.Linq.Expressions; |
|
using System.Reflection; |
|
using UnityEngine; |
|
using UnityEngine.Rendering; |
|
using UnityEngine.Rendering.Universal; |
|
|
|
namespace UnityEditor.Rendering.Universal |
|
{ |
|
using CED = CoreEditorDrawer<UniversalRenderPipelineSerializedLight>; |
|
|
|
internal partial class UniversalRenderPipelineLightUI |
|
{ |
|
[URPHelpURL("light-component")] |
|
enum Expandable |
|
{ |
|
General = 1 << 0, |
|
Shape = 1 << 1, |
|
Emission = 1 << 2, |
|
Rendering = 1 << 3, |
|
Shadows = 1 << 4, |
|
LightCookie = 1 << 5 |
|
} |
|
|
|
static readonly ExpandedState<Expandable, Light> k_ExpandedState = new(~-1, "URP"); |
|
|
|
public static readonly CED.IDrawer Inspector = CED.Group( |
|
CED.Conditional( |
|
(_, __) => |
|
{ |
|
if (SceneView.lastActiveSceneView == null) |
|
return false; |
|
|
|
#if UNITY_2019_1_OR_NEWER |
|
var sceneLighting = SceneView.lastActiveSceneView.sceneLighting; |
|
#else |
|
var sceneLighting = SceneView.lastActiveSceneView.m_SceneLighting; |
|
#endif |
|
return !sceneLighting; |
|
}, |
|
(_, __) => EditorGUILayout.HelpBox(Styles.DisabledLightWarning.text, MessageType.Warning)), |
|
CED.FoldoutGroup(LightUI.Styles.generalHeader, |
|
Expandable.General, |
|
k_ExpandedState, |
|
DrawGeneralContent), |
|
CED.Conditional( |
|
(serializedLight, editor) => !serializedLight.settings.lightType.hasMultipleDifferentValues && serializedLight.settings.light.type == LightType.Spot, |
|
CED.FoldoutGroup(LightUI.Styles.shapeHeader, Expandable.Shape, k_ExpandedState, DrawSpotShapeContent)), |
|
CED.Conditional( |
|
(serializedLight, editor) => |
|
{ |
|
if (serializedLight.settings.lightType.hasMultipleDifferentValues) |
|
return false; |
|
var lightType = serializedLight.settings.light.type; |
|
return lightType == LightType.Rectangle || lightType == LightType.Disc; |
|
}, |
|
CED.FoldoutGroup(LightUI.Styles.shapeHeader, Expandable.Shape, k_ExpandedState, DrawAreaShapeContent)), |
|
CED.FoldoutGroup(LightUI.Styles.emissionHeader, |
|
Expandable.Emission, |
|
k_ExpandedState, |
|
CED.Group( |
|
LightUI.DrawColor, |
|
DrawEmissionContent)), |
|
CED.FoldoutGroup(LightUI.Styles.renderingHeader, |
|
Expandable.Rendering, |
|
k_ExpandedState, |
|
DrawRenderingContent), |
|
CED.FoldoutGroup(LightUI.Styles.shadowHeader, |
|
Expandable.Shadows, |
|
k_ExpandedState, |
|
DrawShadowsContent), |
|
CED.FoldoutGroup(Styles.lightCookieHeader, |
|
Expandable.LightCookie, |
|
k_ExpandedState, |
|
DrawLightCookieContent) |
|
); |
|
|
|
static Func<int> s_SetGizmosDirty = SetGizmosDirty(); |
|
static Func<int> SetGizmosDirty() |
|
{ |
|
var type = Type.GetType("UnityEditor.AnnotationUtility,UnityEditor"); |
|
var method = type.GetMethod("SetGizmosDirty", BindingFlags.Static | BindingFlags.NonPublic); |
|
var lambda = Expression.Lambda<Func<int>>(Expression.Call(method)); |
|
return lambda.Compile(); |
|
} |
|
|
|
static Action<GUIContent, SerializedProperty, LightEditor.Settings> k_SliderWithTexture = GetSliderWithTexture(); |
|
static Action<GUIContent, SerializedProperty, LightEditor.Settings> GetSliderWithTexture() |
|
{ |
|
//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<System.Action<GUIContent, SerializedProperty, LightEditor.Settings>>(sliderWithTextureCall, paramLabel, paramProperty, paramSettings); |
|
return lambda.Compile(); |
|
} |
|
|
|
static void DrawGeneralContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
DrawGeneralContentInternal(serializedLight, owner, isInPreset: false); |
|
} |
|
|
|
static void DrawGeneralContentPreset(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
DrawGeneralContentInternal(serializedLight, owner, isInPreset: true); |
|
} |
|
|
|
static void DrawGeneralContentInternal(UniversalRenderPipelineSerializedLight serializedLight, Editor owner, bool isInPreset) |
|
{ |
|
// To the user, we will only display it as a area light, but under the hood, we have Rectangle and Disc. This is not to confuse people |
|
// who still use our legacy light inspector. |
|
|
|
int selectedLightType = serializedLight.settings.lightType.intValue; |
|
|
|
// Handle all lights that are not in the default set |
|
if (!Styles.LightTypeValues.Contains(serializedLight.settings.lightType.intValue)) |
|
{ |
|
if (serializedLight.settings.lightType.intValue == (int)LightType.Disc) |
|
{ |
|
selectedLightType = (int)LightType.Rectangle; |
|
} |
|
} |
|
|
|
var rect = EditorGUILayout.GetControlRect(); |
|
EditorGUI.BeginProperty(rect, Styles.Type, serializedLight.settings.lightType); |
|
EditorGUI.BeginChangeCheck(); |
|
int type = EditorGUI.IntPopup(rect, Styles.Type, selectedLightType, Styles.LightTypeTitles, Styles.LightTypeValues); |
|
|
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
s_SetGizmosDirty(); |
|
serializedLight.settings.lightType.intValue = type; |
|
} |
|
EditorGUI.EndProperty(); |
|
|
|
Light light = serializedLight.settings.light; |
|
var lightType = light.type; |
|
if (LightType.Directional != lightType && light == RenderSettings.sun) |
|
{ |
|
EditorGUILayout.HelpBox(Styles.SunSourceWarning.text, MessageType.Warning); |
|
} |
|
|
|
if (!serializedLight.settings.lightType.hasMultipleDifferentValues) |
|
{ |
|
using (new EditorGUI.DisabledScope(serializedLight.settings.isAreaLightType)) |
|
serializedLight.settings.DrawLightmapping(); |
|
|
|
if (serializedLight.settings.isAreaLightType && serializedLight.settings.lightmapping.intValue != (int)LightmapBakeType.Baked) |
|
{ |
|
serializedLight.settings.lightmapping.intValue = (int)LightmapBakeType.Baked; |
|
serializedLight.Apply(); |
|
} |
|
|
|
if (lightType != LightType.Rectangle && !serializedLight.settings.isCompletelyBaked && UniversalRenderPipeline.asset.supportsLightLayers && !isInPreset) |
|
{ |
|
EditorGUI.BeginChangeCheck(); |
|
DrawLightLayerMask(serializedLight.lightLayerMask, LightUI.Styles.lightLayer); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
if (!serializedLight.customShadowLayers.boolValue) |
|
SyncLightAndShadowLayers(serializedLight, serializedLight.lightLayerMask); |
|
} |
|
} |
|
} |
|
} |
|
|
|
internal static void SyncLightAndShadowLayers(UniversalRenderPipelineSerializedLight serializedLight, SerializedProperty serialized) |
|
{ |
|
// If we're not in decoupled mode for light layers, we sync light with shadow layers. |
|
// In mixed state, it makes sense to do it only on Light that links the mode. |
|
foreach (var lightTarget in serializedLight.serializedObject.targetObjects) |
|
{ |
|
var additionData = (lightTarget as Component).gameObject.GetComponent<UniversalAdditionalLightData>(); |
|
if (additionData.customShadowLayers) |
|
continue; |
|
|
|
Light target = lightTarget as Light; |
|
if (target.renderingLayerMask != serialized.intValue) |
|
target.renderingLayerMask = serialized.intValue; |
|
} |
|
} |
|
|
|
internal static void DrawLightLayerMask(SerializedProperty property, GUIContent style) |
|
{ |
|
Rect controlRect = EditorGUILayout.GetControlRect(true); |
|
int lightLayer = property.intValue; |
|
|
|
EditorGUI.BeginProperty(controlRect, style, property); |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
lightLayer = EditorGUI.MaskField(controlRect, style, lightLayer, UniversalRenderPipelineGlobalSettings.instance.prefixedLightLayerNames); |
|
if (EditorGUI.EndChangeCheck()) |
|
property.intValue = lightLayer; |
|
|
|
EditorGUI.EndProperty(); |
|
} |
|
|
|
static void DrawSpotShapeContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
serializedLight.settings.DrawInnerAndOuterSpotAngle(); |
|
} |
|
|
|
static void DrawAreaShapeContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
int selectedShape = serializedLight.settings.isAreaLightType ? serializedLight.settings.lightType.intValue : 0; |
|
|
|
// Handle all lights that are not in the default set |
|
if (!Styles.LightTypeValues.Contains(serializedLight.settings.lightType.intValue)) |
|
{ |
|
if (serializedLight.settings.lightType.intValue == (int)LightType.Disc) |
|
{ |
|
selectedShape = (int)LightType.Disc; |
|
} |
|
} |
|
|
|
var rect = EditorGUILayout.GetControlRect(); |
|
EditorGUI.BeginProperty(rect, Styles.AreaLightShapeContent, serializedLight.settings.lightType); |
|
EditorGUI.BeginChangeCheck(); |
|
int shape = EditorGUI.IntPopup(rect, Styles.AreaLightShapeContent, selectedShape, Styles.AreaLightShapeTitles, Styles.AreaLightShapeValues); |
|
|
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
Undo.RecordObject(serializedLight.settings.light, "Adjust Light Shape"); |
|
serializedLight.settings.lightType.intValue = shape; |
|
} |
|
EditorGUI.EndProperty(); |
|
|
|
using (new EditorGUI.IndentLevelScope()) |
|
serializedLight.settings.DrawArea(); |
|
} |
|
|
|
static void DrawEmissionContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
serializedLight.settings.DrawIntensity(); |
|
serializedLight.settings.DrawBounceIntensity(); |
|
|
|
if (!serializedLight.settings.lightType.hasMultipleDifferentValues) |
|
{ |
|
var lightType = serializedLight.settings.light.type; |
|
if (lightType != LightType.Directional) |
|
{ |
|
#if UNITY_2020_1_OR_NEWER |
|
serializedLight.settings.DrawRange(); |
|
#else |
|
serializedLight.settings.DrawRange(false); |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
static void DrawRenderingContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
serializedLight.settings.DrawRenderMode(); |
|
EditorGUILayout.PropertyField(serializedLight.settings.cullingMask, Styles.CullingMask); |
|
} |
|
|
|
static void DrawShadowsContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
if (serializedLight.settings.lightType.hasMultipleDifferentValues) |
|
{ |
|
EditorGUILayout.HelpBox("Cannot multi edit shadows from different light types.", MessageType.Info); |
|
return; |
|
} |
|
|
|
serializedLight.settings.DrawShadowsType(); |
|
|
|
if (serializedLight.settings.shadowsType.hasMultipleDifferentValues) |
|
{ |
|
EditorGUILayout.HelpBox("Cannot multi edit different shadow types", MessageType.Info); |
|
return; |
|
} |
|
|
|
if (serializedLight.settings.light.shadows == LightShadows.None) |
|
return; |
|
|
|
var lightType = serializedLight.settings.light.type; |
|
|
|
using (new EditorGUI.IndentLevelScope()) |
|
{ |
|
if (serializedLight.settings.isBakedOrMixed) |
|
{ |
|
switch (lightType) |
|
{ |
|
// Baked Shadow radius |
|
case LightType.Point: |
|
case LightType.Spot: |
|
serializedLight.settings.DrawBakedShadowRadius(); |
|
break; |
|
case LightType.Directional: |
|
serializedLight.settings.DrawBakedShadowAngle(); |
|
break; |
|
} |
|
} |
|
|
|
if (lightType != LightType.Rectangle && !serializedLight.settings.isCompletelyBaked) |
|
{ |
|
EditorGUILayout.LabelField(Styles.ShadowRealtimeSettings, EditorStyles.boldLabel); |
|
using (new EditorGUI.IndentLevelScope()) |
|
{ |
|
// Resolution |
|
if (lightType == LightType.Point || lightType == LightType.Spot) |
|
DrawShadowsResolutionGUI(serializedLight); |
|
|
|
EditorGUILayout.Slider(serializedLight.settings.shadowsStrength, 0f, 1f, Styles.ShadowStrength); |
|
|
|
// Bias |
|
DrawAdditionalShadowData(serializedLight); |
|
|
|
// this min bound should match the calculation in SharedLightData::GetNearPlaneMinBound() |
|
float nearPlaneMinBound = Mathf.Min(0.01f * serializedLight.settings.range.floatValue, 0.1f); |
|
EditorGUILayout.Slider(serializedLight.settings.shadowsNearPlane, nearPlaneMinBound, 10.0f, Styles.ShadowNearPlane); |
|
} |
|
|
|
if (UniversalRenderPipeline.asset.supportsLightLayers) |
|
{ |
|
EditorGUI.BeginChangeCheck(); |
|
EditorGUILayout.PropertyField(serializedLight.customShadowLayers, Styles.customShadowLayers); |
|
// Undo the changes in the light component because the SyncLightAndShadowLayers will change the value automatically when link is ticked |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
if (serializedLight.customShadowLayers.boolValue) |
|
{ |
|
serializedLight.settings.light.renderingLayerMask = serializedLight.shadowLayerMask.intValue; |
|
} |
|
else |
|
{ |
|
serializedLight.serializedAdditionalDataObject.ApplyModifiedProperties(); // we need to push above modification the modification on object as it is used to sync |
|
SyncLightAndShadowLayers(serializedLight, serializedLight.lightLayerMask); |
|
} |
|
} |
|
|
|
if (serializedLight.customShadowLayers.boolValue) |
|
{ |
|
using (new EditorGUI.IndentLevelScope()) |
|
{ |
|
EditorGUI.BeginChangeCheck(); |
|
DrawLightLayerMask(serializedLight.shadowLayerMask, Styles.ShadowLayer); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
serializedLight.settings.light.renderingLayerMask = serializedLight.shadowLayerMask.intValue; |
|
serializedLight.Apply(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!UnityEditor.Lightmapping.bakedGI && !serializedLight.settings.lightmapping.hasMultipleDifferentValues && serializedLight.settings.isBakedOrMixed) |
|
EditorGUILayout.HelpBox(Styles.BakingWarning.text, MessageType.Warning); |
|
} |
|
|
|
static void DrawAdditionalShadowData(UniversalRenderPipelineSerializedLight serializedLight) |
|
{ |
|
// 0: Custom bias - 1: Bias values defined in Pipeline settings |
|
int selectedUseAdditionalData = serializedLight.additionalLightData.usePipelineSettings ? 1 : 0; |
|
Rect r = EditorGUILayout.GetControlRect(true); |
|
EditorGUI.BeginProperty(r, Styles.shadowBias, serializedLight.useAdditionalDataProp); |
|
{ |
|
using (var checkScope = new EditorGUI.ChangeCheckScope()) |
|
{ |
|
selectedUseAdditionalData = EditorGUI.IntPopup(r, Styles.shadowBias, selectedUseAdditionalData, Styles.displayedDefaultOptions, Styles.optionDefaultValues); |
|
if (checkScope.changed) |
|
{ |
|
foreach (var additionData in serializedLight.lightsAdditionalData) |
|
additionData.usePipelineSettings = selectedUseAdditionalData != 0; |
|
|
|
serializedLight.Apply(); |
|
} |
|
} |
|
} |
|
EditorGUI.EndProperty(); |
|
|
|
if (!serializedLight.useAdditionalDataProp.hasMultipleDifferentValues) |
|
{ |
|
if (selectedUseAdditionalData != 1) // Custom Bias |
|
{ |
|
using (new EditorGUI.IndentLevelScope()) |
|
{ |
|
using (var checkScope = new EditorGUI.ChangeCheckScope()) |
|
{ |
|
EditorGUILayout.Slider(serializedLight.settings.shadowsBias, 0f, 10f, Styles.ShadowDepthBias); |
|
EditorGUILayout.Slider(serializedLight.settings.shadowsNormalBias, 0f, 10f, Styles.ShadowNormalBias); |
|
if (checkScope.changed) |
|
serializedLight.Apply(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void DrawShadowsResolutionGUI(UniversalRenderPipelineSerializedLight serializedLight) |
|
{ |
|
int shadowResolutionTier = serializedLight.additionalLightData.additionalLightsShadowResolutionTier; |
|
|
|
using (new EditorGUILayout.HorizontalScope()) |
|
{ |
|
using (var checkScope = new EditorGUI.ChangeCheckScope()) |
|
{ |
|
Rect r = EditorGUILayout.GetControlRect(true); |
|
r.width += 30; |
|
|
|
shadowResolutionTier = EditorGUI.IntPopup(r, Styles.ShadowResolution, shadowResolutionTier, Styles.ShadowResolutionDefaultOptions, Styles.ShadowResolutionDefaultValues); |
|
if (shadowResolutionTier == UniversalAdditionalLightData.AdditionalLightsShadowResolutionTierCustom) |
|
{ |
|
// show the custom value field GUI. |
|
var newResolution = EditorGUILayout.IntField(serializedLight.settings.shadowsResolution.intValue, GUILayout.ExpandWidth(false)); |
|
serializedLight.settings.shadowsResolution.intValue = Mathf.Max(UniversalAdditionalLightData.AdditionalLightsShadowMinimumResolution, Mathf.NextPowerOfTwo(newResolution)); |
|
} |
|
else |
|
{ |
|
if (GraphicsSettings.renderPipelineAsset is UniversalRenderPipelineAsset urpAsset) |
|
EditorGUILayout.LabelField($"{urpAsset.GetAdditionalLightsShadowResolution(shadowResolutionTier)} ({urpAsset.name})", GUILayout.ExpandWidth(false)); |
|
} |
|
if (checkScope.changed) |
|
{ |
|
serializedLight.additionalLightsShadowResolutionTierProp.intValue = shadowResolutionTier; |
|
serializedLight.Apply(); |
|
} |
|
} |
|
} |
|
|
|
EditorGUILayout.HelpBox(Styles.ShadowInfo.text, MessageType.Info); |
|
} |
|
|
|
static void DrawLightCookieContent(UniversalRenderPipelineSerializedLight serializedLight, Editor owner) |
|
{ |
|
var settings = serializedLight.settings; |
|
if (settings.lightType.hasMultipleDifferentValues) |
|
{ |
|
EditorGUILayout.HelpBox("Cannot multi edit light cookies from different light types.", MessageType.Info); |
|
return; |
|
} |
|
|
|
settings.DrawCookie(); |
|
|
|
// Draw 2D cookie size for directional lights |
|
bool isDirectionalLight = settings.light.type == LightType.Directional; |
|
if (isDirectionalLight) |
|
{ |
|
if (settings.cookie != null) |
|
{ |
|
EditorGUI.BeginChangeCheck(); |
|
EditorGUILayout.PropertyField(serializedLight.lightCookieSizeProp, Styles.LightCookieSize); |
|
EditorGUILayout.PropertyField(serializedLight.lightCookieOffsetProp, Styles.LightCookieOffset); |
|
if (EditorGUI.EndChangeCheck()) |
|
Experimental.Lightmapping.SetLightDirty((UnityEngine.Light)serializedLight.serializedObject.targetObject); |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|