您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

594 行
24 KiB

using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Experimental.Rendering.HDPipeline;
#endif
using UnityEngine.Serialization;
namespace UnityEngine.Experimental.Rendering.HDPipeline
{
// This enum extent the original LightType enum with new light type from HD
public enum LightTypeExtent
{
Punctual, // Fallback on LightShape type
Rectangle,
Line,
// Sphere,
// Disc,
};
public enum SpotLightShape { Cone, Pyramid, Box };
public enum LightUnit
{
Lumen,
Candela,
Lux,
Luminance,
}
// Light layering
public enum LightLayerEnum
{
Nothing = 0, // Custom name for "Nothing" option
LightLayerDefault = 1 << 0,
LightLayer1 = 1 << 1,
LightLayer2 = 1 << 2,
LightLayer3 = 1 << 3,
LightLayer4 = 1 << 4,
LightLayer5 = 1 << 5,
LightLayer6 = 1 << 6,
LightLayer7 = 1 << 7,
Everything = 0xFF, // Custom name for "Everything" option
}
// This structure contains all the old values for every recordable fields from the HD light editor
// so we can force timeline to record changes on other fields from the LateUpdate function (editor only)
struct TimelineWorkaround
{
public float oldDisplayLightIntensity;
public float oldSpotAngle;
public bool oldEnableSpotReflector;
public Color oldLightColor;
public Vector3 oldLocalScale;
public bool oldDisplayAreaLightEmissiveMesh;
public LightTypeExtent oldLightTypeExtent;
public float oldLightColorTemperature;
public Vector3 oldShape;
}
//@TODO: We should continuously move these values
// into the engine when we can see them being generally useful
[RequireComponent(typeof(Light))]
[ExecuteInEditMode]
public class HDAdditionalLightData : MonoBehaviour, ISerializationCallbackReceiver
{
private const int currentVersion = 2;
[HideInInspector, SerializeField]
[FormerlySerializedAs("m_Version")]
[System.Obsolete("version is deprecated, use m_Version instead")]
private float version = currentVersion;
// Currently m_Version is not used and produce a warning, remove these pragmas at the next version incrementation
#pragma warning disable 414
[SerializeField]
private int m_Version = currentVersion;
#pragma warning restore 414
// 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)
// we add intensity (for each type of light we want to manage).
[System.Obsolete("directionalIntensity is deprecated, use intensity and lightUnit instead")]
public float directionalIntensity = Mathf.PI; // In Lux
[System.Obsolete("punctualIntensity is deprecated, use intensity and lightUnit instead")]
public float punctualIntensity = 600.0f; // Light default to 600 lumen, i.e ~48 candela
[System.Obsolete("areaIntensity is deprecated, use intensity and lightUnit instead")]
public float areaIntensity = 200.0f; // Light default to 200 lumen to better match point light
public const float k_DefaultDirectionalLightIntensity = Mathf.PI; // In lux
public const float k_DefaultPunctualLightIntensity = 600.0f; // In lumens
public const float k_DefaultAreaLightIntensity = 200.0f; // In lumens
public float intensity
{
get { return displayLightIntensity; }
set { SetLightIntensity(value); }
}
// Only for Spotlight, should be hide for other light
public bool enableSpotReflector = false;
[Range(0.0f, 100.0f)]
public float m_InnerSpotPercent = 0.0f; // To display this field in the UI this need to be public
public float GetInnerSpotPercent01()
{
return Mathf.Clamp(m_InnerSpotPercent, 0.0f, 100.0f) / 100.0f;
}
[Range(0.0f, 1.0f)]
public float lightDimmer = 1.0f;
[Range(0.0f, 1.0f)]
public float volumetricDimmer = 1.0f;
// Used internally to convert any light unit input into light intensity
public LightUnit lightUnit;
// Not used for directional lights.
public float fadeDistance = 10000.0f;
public bool affectDiffuse = true;
public bool affectSpecular = true;
// This property work only with shadow mask and allow to say we don't render any lightMapped object in the shadow map
public bool nonLightmappedOnly = false;
public LightTypeExtent lightTypeExtent = LightTypeExtent.Punctual;
// Only for Spotlight, should be hide for other light
public SpotLightShape spotLightShape = SpotLightShape.Cone;
// Only for Rectangle/Line/box projector lights
public float shapeWidth = 0.5f;
// Only for Rectangle/box projector lights
public float shapeHeight = 0.5f;
// Only for pyramid projector
public float aspectRatio = 1.0f;
// Only for Sphere/Disc
public float shapeRadius = 0.0f;
// Only for Spot/Point - use to cheaply fake specular spherical area light
[Range(0.0f, 1.0f)]
public float maxSmoothness = 1.0f;
// 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
public bool applyRangeAttenuation = true;
// This is specific for the LightEditor GUI and not use at runtime
public bool useOldInspector = false;
public bool featuresFoldout = true;
public bool showAdditionalSettings = false;
public float displayLightIntensity;
// When true, a mesh will be display to represent the area light (Can only be change in editor, component is added in Editor)
public bool displayAreaLightEmissiveMesh = false;
// Duplication of HDLightEditor.k_MinAreaWidth, maybe do something about that
const float k_MinAreaWidth = 0.01f; // Provide a small size of 1cm for line light
public LightLayerEnum lightLayers = LightLayerEnum.LightLayerDefault;
// This function return a mask of light layers as uint and handle the case of Everything as being 0xFF and not -1
public uint GetLightLayers()
{
int value = Convert.ToInt32(lightLayers);
return value < 0 ? (uint)LightLayerEnum.Everything : (uint)value;
}
#if UNITY_EDITOR
// We need these old states to make timeline and the animator record the intensity value and the emissive mesh changes (editor-only)
[System.NonSerialized]
TimelineWorkaround timelineWorkaround;
#endif
// For light that used the old intensity system we update them
[System.NonSerialized]
bool needsIntensityUpdate_1_0 = false;
// Runtime datas used to compute light intensity
Light _light;
Light m_Light
{
get
{
if (_light == null)
_light = GetComponent<Light>();
return _light;
}
}
void SetLightIntensity(float intensity)
{
displayLightIntensity = intensity;
if (lightUnit == LightUnit.Lumen)
{
switch (lightTypeExtent)
{
case LightTypeExtent.Punctual:
SetLightIntensityPunctual(intensity);
break;
case LightTypeExtent.Line:
m_Light.intensity = LightUtils.CalculateLineLightLumenToLuminance(intensity, shapeWidth);
break;
case LightTypeExtent.Rectangle:
m_Light.intensity = LightUtils.ConvertRectLightLumenToLuminance(intensity, shapeWidth, shapeHeight);
break;
}
}
else
m_Light.intensity = intensity;
#if UNITY_EDITOR
m_Light.SetLightDirty(); // Should be apply only to parameter that's affect GI, but make the code cleaner
#endif
}
void SetLightIntensityPunctual(float intensity)
{
switch (m_Light.type)
{
case LightType.Directional:
m_Light.intensity = intensity; // Always in lux
break;
case LightType.Point:
if (lightUnit == LightUnit.Candela)
m_Light.intensity = intensity;
else
m_Light.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
break;
case LightType.Spot:
if (lightUnit == LightUnit.Candela)
{
// When using candela, reflector don't have any effect. Our intensity is candela = lumens/steradian and the user
// provide desired value for an angle of 1 steradian.
m_Light.intensity = intensity;
}
else // lumen
{
if (enableSpotReflector)
{
// If reflector is enabled all the lighting from the sphere is focus inside the solid angle of current shape
if (spotLightShape == SpotLightShape.Cone)
{
m_Light.intensity = LightUtils.ConvertSpotLightLumenToCandela(intensity, m_Light.spotAngle * Mathf.Deg2Rad, true);
}
else if (spotLightShape == SpotLightShape.Pyramid)
{
float angleA, angleB;
LightUtils.CalculateAnglesForPyramid(aspectRatio, m_Light.spotAngle * Mathf.Deg2Rad, out angleA, out angleB);
m_Light.intensity = LightUtils.ConvertFrustrumLightLumenToCandela(intensity, angleA, angleB);
}
else // Box shape, fallback to punctual light.
{
m_Light.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
}
}
else
{
// No reflector, angle act as occlusion of point light.
m_Light.intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
}
}
break;
}
}
#if UNITY_EDITOR
// Force to retrieve color light's m_UseColorTemperature because it's private
[System.NonSerialized]
SerializedProperty useColorTemperatureProperty;
[System.NonSerialized]
SerializedObject lightSerializedObject;
public bool useColorTemperature
{
get
{
if (useColorTemperatureProperty == null)
{
lightSerializedObject = new SerializedObject(m_Light);
useColorTemperatureProperty = lightSerializedObject.FindProperty("m_UseColorTemperature");
}
lightSerializedObject.Update();
return useColorTemperatureProperty.boolValue;
}
}
private void DrawGizmos(bool selected)
{
var light = gameObject.GetComponent<Light>();
var gizmoColor = light.color;
gizmoColor.a = selected ? 1.0f : 0.3f; // Fade for the gizmo
Gizmos.color = Handles.color = gizmoColor;
if (lightTypeExtent == LightTypeExtent.Punctual)
{
switch (light.type)
{
case LightType.Directional:
HDLightEditorUtilities.DrawDirectionalLightGizmo(light);
break;
case LightType.Point:
HDLightEditorUtilities.DrawPointlightGizmo(light, selected);
break;
case LightType.Spot:
if (spotLightShape == SpotLightShape.Cone)
HDLightEditorUtilities.DrawSpotlightGizmo(light, selected);
else if (spotLightShape == SpotLightShape.Pyramid)
HDLightEditorUtilities.DrawFrustumlightGizmo(light);
else if (spotLightShape == SpotLightShape.Box)
HDLightEditorUtilities.DrawFrustumlightGizmo(light);
break;
}
}
else
{
switch (lightTypeExtent)
{
case LightTypeExtent.Rectangle:
HDLightEditorUtilities.DrawArealightGizmo(light);
break;
case LightTypeExtent.Line:
HDLightEditorUtilities.DrawArealightGizmo(light);
break;
}
}
if (selected)
{
DrawVerticalRay();
}
}
// Trace a ray down to better locate the light location
private void DrawVerticalRay()
{
Ray ray = new Ray(transform.position, Vector3.down);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Handles.color = Color.green;
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawLine(transform.position, hit.point);
Handles.DrawWireDisc(hit.point, hit.normal, 0.5f);
Handles.color = Color.red;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawLine(transform.position, hit.point);
Handles.DrawWireDisc(hit.point, hit.normal, 0.5f);
}
}
private void OnDrawGizmos()
{
// DrawGizmos(false);
}
private void OnDrawGizmosSelected()
{
DrawGizmos(true);
}
// TODO: There are a lot of old != current checks and assignation in this function, maybe think about using another system ?
void LateUpdate()
{
Vector3 shape = new Vector3(shapeWidth, shapeHeight, shapeRadius);
// Check if the intensity have been changed by the inspector or an animator
if (timelineWorkaround.oldDisplayLightIntensity != displayLightIntensity
|| lightTypeExtent != timelineWorkaround.oldLightTypeExtent
|| transform.localScale != timelineWorkaround.oldLocalScale
|| shape != timelineWorkaround.oldShape
|| m_Light.colorTemperature != timelineWorkaround.oldLightColorTemperature)
{
RefreshLigthIntensity();
UpdateAreaLightEmissiveMesh();
timelineWorkaround.oldDisplayLightIntensity = displayLightIntensity;
timelineWorkaround.oldLocalScale = transform.localScale;
timelineWorkaround.oldLightTypeExtent = lightTypeExtent;
timelineWorkaround.oldLightColorTemperature = m_Light.colorTemperature;
timelineWorkaround.oldShape = shape;
}
// Same check for light angle to update intensity using spot angle
if (m_Light.type == LightType.Spot && (timelineWorkaround.oldSpotAngle != m_Light.spotAngle || timelineWorkaround.oldEnableSpotReflector != enableSpotReflector))
{
RefreshLigthIntensity();
timelineWorkaround.oldSpotAngle = m_Light.spotAngle;
timelineWorkaround.oldEnableSpotReflector = enableSpotReflector;
}
if (m_Light.color != timelineWorkaround.oldLightColor
|| transform.localScale !=timelineWorkaround.oldLocalScale
|| displayAreaLightEmissiveMesh != timelineWorkaround.oldDisplayAreaLightEmissiveMesh
|| lightTypeExtent !=timelineWorkaround.oldLightTypeExtent
|| m_Light.colorTemperature != timelineWorkaround.oldLightColorTemperature)
{
UpdateAreaLightEmissiveMesh();
timelineWorkaround.oldLightColor = m_Light.color;
timelineWorkaround.oldLocalScale = transform.localScale;
timelineWorkaround.oldDisplayAreaLightEmissiveMesh = displayAreaLightEmissiveMesh;
timelineWorkaround.oldLightTypeExtent = lightTypeExtent;
timelineWorkaround.oldLightColorTemperature = m_Light.colorTemperature;
}
}
// The editor can only access displayLightIntensity (because of SerializedProperties) so we update the intensity to get the real value
void RefreshLigthIntensity()
{
intensity = displayLightIntensity;
}
public static bool IsAreaLight(LightTypeExtent lightType)
{
return lightType != LightTypeExtent.Punctual;
}
public static bool IsAreaLight(SerializedProperty lightType)
{
return IsAreaLight((LightTypeExtent)lightType.enumValueIndex);
}
public void UpdateAreaLightEmissiveMesh()
{
MeshRenderer emissiveMeshRenderer = GetComponent<MeshRenderer>();
MeshFilter emissiveMeshFilter = GetComponent<MeshFilter>();
bool displayEmissiveMesh = IsAreaLight(lightTypeExtent) && lightTypeExtent != LightTypeExtent.Line && displayAreaLightEmissiveMesh;
// Ensure that the emissive mesh components are here
if (displayEmissiveMesh)
{
if (emissiveMeshRenderer == null)
emissiveMeshRenderer = gameObject.AddComponent<MeshRenderer>();
if (emissiveMeshFilter == null)
emissiveMeshFilter = gameObject.AddComponent<MeshFilter>();
}
else // Or remove them if the option is disabled
{
if (emissiveMeshRenderer != null)
DestroyImmediate(emissiveMeshRenderer);
if (emissiveMeshFilter != null)
DestroyImmediate(emissiveMeshFilter);
// We don't have anything to do left if the dislay emissive mesh option is disabled
return ;
}
Vector3 lightSize;
// Update light area size from GameObject transform scale if the transform have changed
// else we update the light size from the shape fields
if (timelineWorkaround.oldLocalScale != transform.localScale)
lightSize = transform.localScale;
else
lightSize = new Vector3(shapeWidth, shapeHeight, 0);
lightSize = Vector3.Max(Vector3.one * k_MinAreaWidth, lightSize);
m_Light.transform.localScale = lightSize;
m_Light.areaSize = lightSize;
switch (lightTypeExtent)
{
case LightTypeExtent.Rectangle:
shapeWidth = lightSize.x;
shapeHeight = lightSize.y;
break;
default:
break;
}
if (emissiveMeshRenderer.sharedMaterial == null)
emissiveMeshRenderer.material = new Material(Shader.Find("HDRenderPipeline/Unlit"));
// Update Mesh emissive properties
emissiveMeshRenderer.sharedMaterial.SetColor("_UnlitColor", Color.black);
// m_Light.intensity is in luminance which is the value we need for emissive color
Color value = m_Light.color.linear * m_Light.intensity;
if (useColorTemperature)
value *= LightUtils.CorrelatedColorTemperatureToRGB(m_Light.colorTemperature);
emissiveMeshRenderer.sharedMaterial.SetColor("_EmissiveColor", value);
}
#endif
// As we have our own default value, we need to initialize the light intensity correctly
public static void InitDefaultHDAdditionalLightData(HDAdditionalLightData lightData)
{
// Special treatment for Unity built-in area light. Change it to our rectangle light
var light = lightData.gameObject.GetComponent<Light>();
// Set light intensity and unit using its type
switch (light.type)
{
case LightType.Directional:
lightData.lightUnit = LightUnit.Lux;
lightData.intensity = k_DefaultDirectionalLightIntensity;
break;
case LightType.Area: // Rectangle by default when light is created
lightData.lightUnit = LightUnit.Lumen;
lightData.intensity = k_DefaultAreaLightIntensity;
break;
case LightType.Point:
case LightType.Spot:
lightData.lightUnit = LightUnit.Lumen;
lightData.intensity = k_DefaultPunctualLightIntensity;
break;
}
// Sanity check: lightData.lightTypeExtent is init to LightTypeExtent.Punctual (in case for unknow reasons we recreate additional data on an existing line)
if (light.type == LightType.Area && lightData.lightTypeExtent == LightTypeExtent.Punctual)
{
lightData.lightTypeExtent = LightTypeExtent.Rectangle;
light.type = LightType.Point; // Same as in HDLightEditor
#if UNITY_EDITOR
light.lightmapBakeType = LightmapBakeType.Realtime;
#endif
}
// We don't use the global settings of shadow mask by default
light.lightShadowCasterMode = LightShadowCasterMode.Everything;
}
public void OnBeforeSerialize() {}
public void OnAfterDeserialize()
{
// Note: the field version is deprecated but we keep it for retro-compatibility reasons, you should use m_Version instead
#pragma warning disable 0618
if (version <= 1.0f)
#pragma warning restore 0618
{
// Note: We can't access to the light component in OnAfterSerialize as it is not init() yet,
// so instead we use a boolean to do the upgrade in OnEnable().
// However OnEnable is not call when the light is disabled, so the HDLightEditor also call
// the UpgradeLight() code in this case
needsIntensityUpdate_1_0 = true;
}
}
private void OnEnable()
{
UpgradeLight();
}
public void UpgradeLight()
{
// Disable the warning generated by deprecated fields (areaIntensity, directionalIntensity, ...)
#pragma warning disable 0618
// If we are deserializing an old version, convert the light intensity to the new system
if (needsIntensityUpdate_1_0)
{
switch (lightTypeExtent)
{
case LightTypeExtent.Punctual:
switch (m_Light.type)
{
case LightType.Directional:
lightUnit = LightUnit.Lux;
intensity = directionalIntensity;
break;
case LightType.Spot:
case LightType.Point:
lightUnit = LightUnit.Lumen;
intensity = punctualIntensity;
break;
}
break;
case LightTypeExtent.Line:
case LightTypeExtent.Rectangle:
lightUnit = LightUnit.Lumen;
intensity = areaIntensity;
break;
}
needsIntensityUpdate_1_0 = false;
}
m_Version = currentVersion;
version = currentVersion;
#pragma warning restore 0618
}
}
}