浏览代码

Merge pull request #1612 from Unity-Technologies/LightUnits

HDRP: Add extra Light units (Candela, Luminance)
/main
GitHub 6 年前
当前提交
adaab50a
共有 8 个文件被更改,包括 575 次插入269 次删除
  1. 2
      com.unity.render-pipelines.high-definition/CHANGELOG.md
  2. 1
      com.unity.render-pipelines.high-definition/HDRP/Editor/Lighting/HDLightEditor.Styles.cs
  3. 308
      com.unity.render-pipelines.high-definition/HDRP/Editor/Lighting/HDLightEditor.cs
  4. 2
      com.unity.render-pipelines.high-definition/HDRP/Editor/Material/Lit/LitShaderPreprocessor.cs
  5. 45
      com.unity.render-pipelines.high-definition/HDRP/Editor/Upgraders/UpgradeMenuItem.cs
  6. 11
      com.unity.render-pipelines.high-definition/HDRP/Lighting/GlobalIlluminationUtils.cs
  7. 393
      com.unity.render-pipelines.high-definition/HDRP/Lighting/Light/HDAdditionalLightData.cs
  8. 82
      com.unity.render-pipelines.high-definition/HDRP/Lighting/LightUtils.cs

2
com.unity.render-pipelines.high-definition/CHANGELOG.md


- Fixed apply range attenuation option on punctual light
- Fixed issue when using more than one volume mask texture with density volumes.
- Fixed an error which prevented volumetric lighting from working if no density volumes with 3D textures were present.
- Fixed issue with color temperature not take correctly into account with static lighting
### Added
- Add option supportDitheringCrossFade on HDRP Asset to allow to remove shader variant during player build if needed

- Add an option to generate an emissive mesh for area lights (currently rectangle light only). The mesh fits the size, intensity and color of the light.
- Add an option to the HDRP asset to increase the resolution of volumetric lighting.
- Add additional ligth unit support for punctual light (Lumens, Candela) and area lights (Lumens, Luminance)
### Changed
- Re-enable shadow mask mode in debug view

1
com.unity.render-pipelines.high-definition/HDRP/Editor/Lighting/HDLightEditor.Styles.cs


public readonly GUIContent directionalIntensity = new GUIContent("Intensity (Lux)", "Illuminance of the directional light at ground level in lux.");
public readonly GUIContent punctualIntensity = new GUIContent("Intensity (Lumen)", "Luminous power of the light in lumen. Spotlight are considered as point light with barndoor so match intensity of a point light.");
public readonly GUIContent areaIntensity = new GUIContent("Intensity (Lumen)", "Luminous power of the light in lumen.");
public readonly GUIContent lightIntensity = new GUIContent("Intensity", "");
public readonly GUIContent maxSmoothness = new GUIContent("Max Smoothness", "Very low cost way of faking spherical area lighting. This will modify the roughness of the material lit. This is useful when the specular highlight is too small or too sharp.");
public readonly GUIContent affectDiffuse = new GUIContent("Affect Diffuse", "This will disable diffuse lighting for this light. Doesn't save performance, diffuse lighting is still computed.");

308
com.unity.render-pipelines.high-definition/HDRP/Editor/Lighting/HDLightEditor.cs


sealed class SerializedLightData
{
public SerializedProperty directionalIntensity;
public SerializedProperty punctualIntensity;
public SerializedProperty areaIntensity;
public SerializedProperty intensity;
public SerializedProperty enableSpotReflector;
public SerializedProperty spotInnerPercent;
public SerializedProperty lightDimmer;

public SerializedProperty maxSmoothness;
public SerializedProperty applyRangeAttenuation;
public SerializedProperty volumetricDimmer;
public SerializedProperty lightUnit;
public SerializedProperty displayAreaLightEmissiveMesh;
// Editor stuff

//Disc,
}
enum DirectionalLightUnit
{
Lux = LightUnit.Lux,
}
enum AreaLightUnit
{
Lumen = LightUnit.Lumen,
Luminance = LightUnit.Luminance,
}
enum PunctualLightUnit
{
Lumen = LightUnit.Lumen,
Candela = LightUnit.Candela,
}
const float k_MinAreaWidth = 0.01f; // Provide a small size of 1cm for line light
// Used for UI only; the processing code must use LightTypeExtent and LightType

AdditionalShadowData[] m_AdditionalShadowDatas;
// Used to detect if the scale have been changed via the transform component
Vector3 m_OldAreaLightSize;
bool m_UpdateAreaLightEmissiveMesh;
bool m_UpdateAreaLightEmissiveMeshComponents = false;
protected override void OnEnable()
{

using (var o = new PropertyFetcher<HDAdditionalLightData>(m_SerializedAdditionalLightData))
m_AdditionalLightData = new SerializedLightData
{
directionalIntensity = o.Find(x => x.directionalIntensity),
punctualIntensity = o.Find(x => x.punctualIntensity),
areaIntensity = o.Find(x => x.areaIntensity),
intensity = o.Find(x => x.displayLightIntensity),
lightUnit = o.Find(x => x.lightUnit),
displayAreaLightEmissiveMesh = o.Find(x => x.displayAreaLightEmissiveMesh),
fadeDistance = o.Find(x => x.fadeDistance),
affectDiffuse = o.Find(x => x.affectDiffuse),

edgeToleranceNormal = o.Find(x => x.edgeToleranceNormal),
edgeTolerance = o.Find(x => x.edgeTolerance)
};
// Update emissive mesh and light intensity when undo/redo
Undo.undoRedoPerformed += () => {
m_SerializedAdditionalLightData.ApplyModifiedProperties();
foreach (var hdLightData in m_AdditionalLightDatas)
if (hdLightData != null)
hdLightData.UpdateAreaLightEmissiveMesh();
};
}
public override void OnInspectorGUI()

CoreEditorUtils.DrawSplitter();
EditorGUILayout.Space();
UpdateAreaLightEmissiveMeshSize();
if (m_UpdateAreaLightEmissiveMesh)
UpdateAreaLightEmissiveMesh();
if (m_UpdateAreaLightEmissiveMeshComponents)
UpdateAreaLightEmissiveMeshComponents();
}
void DrawFoldout(SerializedProperty foldoutProperty, string title, Action func)

{
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
EditorGUI.BeginChangeCheck();
if (EditorGUI.EndChangeCheck())
UpdateLightIntensityUnit();
if (m_LightShape != LightShape.Directional)
settings.DrawRange(false);

if (EditorGUI.EndChangeCheck())
{
UpdateLightIntensity();
m_UpdateAreaLightEmissiveMesh = true;
UpdateLightScale();
m_UpdateAreaLightEmissiveMeshComponents = true;
bool IsAreaLightShape(LightShape shape)
void UpdateLightIntensityUnit()
return shape == LightShape.Rectangle || shape == LightShape.Line;
if (m_LightShape == LightShape.Directional)
m_AdditionalLightData.lightUnit.enumValueIndex = (int)DirectionalLightUnit.Lux;
else
m_AdditionalLightData.lightUnit.enumValueIndex = (int)LightUnit.Lumen;
void UpdateAreaLightEmissiveMesh()
// Refect light size changes on transform local scale
void UpdateLightScale()
foreach (var lightData in m_AdditionalLightDatas)
foreach (var hdLightData in m_AdditionalLightDatas)
GameObject lightGameObject = lightData.gameObject;
MeshRenderer emissiveMeshRenderer = lightData.GetComponent<MeshRenderer>();
MeshFilter emissiveMeshFilter = lightData.GetComponent<MeshFilter>();
Light light = lightGameObject.GetComponent<Light>();
bool displayAreaLightEmissiveMesh = IsAreaLightShape(m_LightShape) && m_LightShape != LightShape.Line && m_AdditionalLightData.displayAreaLightEmissiveMesh.boolValue;
// Ensure that the emissive mesh components are here
if (displayAreaLightEmissiveMesh)
{
if (emissiveMeshRenderer == null)
emissiveMeshRenderer = lightGameObject.AddComponent<MeshRenderer>();
if (emissiveMeshFilter == null)
emissiveMeshFilter = lightGameObject.AddComponent<MeshFilter>();
}
else // Or remove them if the option is disabled
{
if (emissiveMeshRenderer != null)
DestroyImmediate(emissiveMeshRenderer);
if (emissiveMeshFilter != null)
DestroyImmediate(emissiveMeshFilter);
// Skip to the next light
continue;
}
float areaLightIntensity = 0.0f;
// Update Mesh emissive value
case LightShape.Line:
hdLightData.transform.localScale = new Vector3(m_AdditionalLightData.shapeWidth.floatValue, 0, 0);
break;
emissiveMeshFilter.mesh = HDEditorUtils.LoadAsset< Mesh >("RenderPipelineResources/Quad.FBX");
lightGameObject.transform.localScale = new Vector3(lightData.shapeWidth, lightData.shapeHeight, 0);
// Do the same conversion as for light intensity
areaLightIntensity = LightUtils.ConvertRectLightIntensity(
m_AdditionalLightData.areaIntensity.floatValue,
lightData.shapeWidth,
lightData.shapeHeight);
hdLightData.transform.localScale = new Vector3(m_AdditionalLightData.shapeWidth.floatValue, m_AdditionalLightData.shapeHeight.floatValue, 0);
default:
case LightShape.Point:
case LightShape.Spot:
hdLightData.transform.localScale = Vector3.one * settings.range.floatValue;
if (emissiveMeshRenderer.sharedMaterial == null)
emissiveMeshRenderer.material = new Material(Shader.Find("HDRenderPipeline/Unlit"));
emissiveMeshRenderer.sharedMaterial.SetColor("_UnlitColor", Color.black);
// Note that we must use the light in linear RGB
emissiveMeshRenderer.sharedMaterial.SetColor("_EmissiveColor", light.color.linear * areaLightIntensity);
// This function updates the area light size when the local scale of the gameobject changes
void UpdateAreaLightEmissiveMeshSize()
LightUnit LightIntensityUnitPopup(LightShape shape)
// Early exit if the light type is not an area
if (!IsAreaLightShape(m_LightShape) || target == null || targets.Length > 1)
return ;
Vector3 lightSize = ((Light)target).transform.localScale;
lightSize = Vector3.Max(Vector3.one * k_MinAreaWidth, lightSize);
LightUnit selectedLightUnit;
LightUnit oldLigthUnit = (LightUnit)m_AdditionalLightData.lightUnit.enumValueIndex;
if (lightSize == m_OldAreaLightSize)
return ;
switch (m_LightShape)
EditorGUI.BeginChangeCheck();
switch (shape)
case LightShape.Rectangle:
m_AdditionalLightData.shapeWidth.floatValue = lightSize.x;
m_AdditionalLightData.shapeHeight.floatValue = lightSize.y;
case LightShape.Directional:
selectedLightUnit = (LightUnit)EditorGUILayout.EnumPopup((DirectionalLightUnit)m_AdditionalLightData.lightUnit.enumValueIndex);
break;
case LightShape.Point:
case LightShape.Spot:
selectedLightUnit = (LightUnit)EditorGUILayout.EnumPopup((PunctualLightUnit)m_AdditionalLightData.lightUnit.enumValueIndex);
selectedLightUnit = (LightUnit)EditorGUILayout.EnumPopup((AreaLightUnit)m_AdditionalLightData.lightUnit.enumValueIndex);
if (EditorGUI.EndChangeCheck())
ConvertLightIntensity(oldLigthUnit, selectedLightUnit);
UpdateLightIntensity();
m_UpdateAreaLightEmissiveMesh = true;
m_OldAreaLightSize = lightSize;
return selectedLightUnit;
// Caution: this function must match the one in HDAdditionalLightData.ConvertPhysicalLightIntensityToLightIntensity - any change need to be replicated
void UpdateLightIntensity()
void ConvertLightIntensity(LightUnit oldLightUnit, LightUnit newLightUnit)
// Clamp negative values.
m_AdditionalLightData.directionalIntensity.floatValue = Mathf.Max(0, m_AdditionalLightData.directionalIntensity.floatValue);
m_AdditionalLightData.punctualIntensity.floatValue = Mathf.Max(0, m_AdditionalLightData.punctualIntensity.floatValue);
m_AdditionalLightData.areaIntensity.floatValue = Mathf.Max(0, m_AdditionalLightData.areaIntensity.floatValue);
float intensity = m_AdditionalLightData.intensity.floatValue;
switch (m_LightShape)
// For punctual lights
if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Candela)
case LightShape.Directional:
settings.intensity.floatValue = m_AdditionalLightData.directionalIntensity.floatValue;
break;
if (m_LightShape == LightShape.Spot && m_AdditionalLightData.enableSpotReflector.boolValue)
{
// We have already calculate the correct value, just assign it
intensity = ((Light)target).intensity;
}
else
intensity = LightUtils.ConvertPointLightLumenToCandela(intensity);
}
if (oldLightUnit == LightUnit.Candela && newLightUnit == LightUnit.Lumen)
{
if (m_LightShape == LightShape.Spot && m_AdditionalLightData.enableSpotReflector.boolValue)
{
// We just need to multiply candela by solid angle in this case
if ((SpotLightShape)m_AdditionalLightData.spotLightShape.enumValueIndex == SpotLightShape.Cone)
intensity = LightUtils.ConvertSpotLightCandelaToLumen(intensity, ((Light)target).spotAngle * Mathf.Deg2Rad, true);
else if ((SpotLightShape)m_AdditionalLightData.spotLightShape.enumValueIndex == SpotLightShape.Pyramid)
{
float angleA, angleB;
LightUtils.CalculateAnglesForPyramid(m_AdditionalLightData.aspectRatio.floatValue, ((Light)target).spotAngle * Mathf.Deg2Rad, out angleA, out angleB);
case LightShape.Point:
settings.intensity.floatValue = LightUtils.ConvertPointLightIntensity(m_AdditionalLightData.punctualIntensity.floatValue);
break;
intensity = LightUtils.ConvertFrustrumLightCandelaToLumen(intensity, angleA, angleB);
}
else // Box
intensity = LightUtils.ConvertPointLightCandelaToLumen(intensity);
}
else
intensity = LightUtils.ConvertPointLightCandelaToLumen(intensity);
}
case LightShape.Spot:
// Spot should used conversion which take into account the angle, and thus the intensity vary with angle.
// This is not easy to manipulate for lighter, so we simply consider any spot light as just occluded point light. So reuse the same code.
// For area lights
if (oldLightUnit == LightUnit.Lumen && newLightUnit == LightUnit.Luminance)
{
if (m_LightShape == LightShape.Rectangle)
intensity = LightUtils.ConvertRectLightLumenToLuminance(intensity, m_AdditionalLightData.shapeWidth.floatValue, m_AdditionalLightData.shapeHeight.floatValue);
else if (m_LightShape == LightShape.Line)
intensity = LightUtils.CalculateLineLightLumenToLuminance(intensity, m_AdditionalLightData.shapeWidth.floatValue);
}
if (oldLightUnit == LightUnit.Luminance && newLightUnit == LightUnit.Lumen)
{
if (m_LightShape == LightShape.Rectangle)
intensity = LightUtils.ConvertRectLightLuminanceToLumen(intensity, m_AdditionalLightData.shapeWidth.floatValue, m_AdditionalLightData.shapeHeight.floatValue);
else if (m_LightShape == LightShape.Line)
intensity = LightUtils.CalculateLineLightLuminanceToLumen(intensity, m_AdditionalLightData.shapeWidth.floatValue);
}
var spotLightShape = (SpotLightShape)m_AdditionalLightData.spotLightShape.enumValueIndex;
m_AdditionalLightData.intensity.floatValue = intensity;
}
if (m_AdditionalLightData.enableSpotReflector.boolValue)
{
if (spotLightShape == SpotLightShape.Cone)
{
settings.intensity.floatValue = LightUtils.ConvertSpotLightIntensity(m_AdditionalLightData.punctualIntensity.floatValue, settings.spotAngle.floatValue * Mathf.Deg2Rad, true);
}
else if (spotLightShape == SpotLightShape.Pyramid)
{
float angleA, angleB;
LightUtils.CalculateAnglesForPyramid(m_AdditionalLightData.aspectRatio.floatValue, settings.spotAngle.floatValue,
out angleA, out angleB);
void UpdateAreaLightEmissiveMeshComponents()
{
foreach (var hdLightData in m_AdditionalLightDatas)
{
hdLightData.UpdateAreaLightEmissiveMesh();
settings.intensity.floatValue = LightUtils.ConvertFrustrumLightIntensity(m_AdditionalLightData.punctualIntensity.floatValue, angleA, angleB);
}
else // Box shape, fallback to punctual light.
{
settings.intensity.floatValue = LightUtils.ConvertPointLightIntensity(m_AdditionalLightData.punctualIntensity.floatValue);
}
}
else // Reflector disabled, fallback to punctual light.
{
settings.intensity.floatValue = LightUtils.ConvertPointLightIntensity(m_AdditionalLightData.punctualIntensity.floatValue);
}
break;
MeshRenderer emissiveMeshRenderer = hdLightData.GetComponent<MeshRenderer>();
MeshFilter emissiveMeshFilter = hdLightData.GetComponent<MeshFilter>();
case LightShape.Rectangle:
settings.intensity.floatValue = LightUtils.ConvertRectLightIntensity(m_AdditionalLightData.areaIntensity.floatValue, m_AdditionalLightData.shapeWidth.floatValue, m_AdditionalLightData.shapeHeight.floatValue);
break;
// If the display emissive mesh is disabled, skip to the next selected light
if (emissiveMeshFilter == null || emissiveMeshRenderer == null)
continue ;
case LightShape.Line:
settings.intensity.floatValue = LightUtils.CalculateLineLightIntensity(m_AdditionalLightData.areaIntensity.floatValue, m_AdditionalLightData.shapeWidth.floatValue);
break;
// We only load the mesh and it's material here, because we can't do that inside HDAdditionalLightData (Editor assembly)
// Every other properties of the mesh is updated in HDAdditionalLightData to support timeline and editor records
emissiveMeshFilter.mesh = UnityEditor.Experimental.Rendering.HDPipeline.HDEditorUtils.LoadAsset< Mesh >("RenderPipelineResources/Quad.FBX");
if (emissiveMeshRenderer.sharedMaterial == null)
emissiveMeshRenderer.material = new Material(Shader.Find("HDRenderPipeline/Unlit"));
m_UpdateAreaLightEmissiveMeshComponents = false;
EditorGUI.BeginChangeCheck();
if (EditorGUI.EndChangeCheck())
m_UpdateAreaLightEmissiveMesh = true;
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(m_AdditionalLightData.intensity, s_Styles.lightIntensity);
m_AdditionalLightData.lightUnit.enumValueIndex = (int)LightIntensityUnitPopup(m_LightShape);
EditorGUILayout.EndHorizontal();
switch (m_LightShape)
// Only display reflector option if it make sense
if (m_LightShape == LightShape.Spot)
case LightShape.Directional:
EditorGUILayout.PropertyField(m_AdditionalLightData.directionalIntensity, s_Styles.directionalIntensity);
break;
case LightShape.Point:
case LightShape.Spot:
EditorGUILayout.PropertyField(m_AdditionalLightData.punctualIntensity, s_Styles.punctualIntensity);
// Only display reflector option if it make sense
if (m_LightShape == LightShape.Spot)
{
var spotLightShape = (SpotLightShape)m_AdditionalLightData.spotLightShape.enumValueIndex;
if (spotLightShape == SpotLightShape.Cone || spotLightShape == SpotLightShape.Pyramid)
EditorGUILayout.PropertyField(m_AdditionalLightData.enableSpotReflector, s_Styles.enableSpotReflector);
}
break;
case LightShape.Rectangle:
case LightShape.Line:
EditorGUILayout.PropertyField(m_AdditionalLightData.areaIntensity, s_Styles.areaIntensity);
break;
}
if (EditorGUI.EndChangeCheck())
{
UpdateLightIntensity();
m_UpdateAreaLightEmissiveMesh = true;
var spotLightShape = (SpotLightShape)m_AdditionalLightData.spotLightShape.enumValueIndex;
if ((spotLightShape == SpotLightShape.Cone || spotLightShape == SpotLightShape.Pyramid)
&& m_AdditionalLightData.lightUnit.enumValueIndex == (int)PunctualLightUnit.Lumen)
EditorGUILayout.PropertyField(m_AdditionalLightData.enableSpotReflector, s_Styles.enableSpotReflector);
}
settings.DrawBounceIntensity();

EditorGUI.BeginChangeCheck(); // For GI we need to detect any change on additional data and call SetLightDirty
// No cookie with area light (maybe in future textured area light ?)
if (!IsAreaLightShape(m_LightShape))
if (!HDAdditionalLightData.IsAreaLight(m_AdditionalLightData.lightTypeExtent))
{
settings.DrawCookie();

EditorGUILayout.PropertyField(m_AdditionalLightData.applyRangeAttenuation, s_Styles.applyRangeAttenuation);
// Emissive mesh for area light only
if (IsAreaLightShape(m_LightShape))
if (HDAdditionalLightData.IsAreaLight(m_AdditionalLightData.lightTypeExtent))
m_UpdateAreaLightEmissiveMesh = true;
m_UpdateAreaLightEmissiveMeshComponents = true;
}
EditorGUI.indentLevel--;

2
com.unity.render-pipelines.high-definition/HDRP/Editor/Material/Lit/LitShaderPreprocessor.cs


return true;
bool isGBufferPass = snippet.passName == "GBuffer";
bool isForwardPass = snippet.passName == "Forward";
//bool isForwardPass = snippet.passName == "Forward";
bool isDepthOnlyPass = snippet.passName == "DepthOnly";
bool isTransparentForwardPass = snippet.passName == "TransparentDepthPostpass" || snippet.passName == "TransparentBackface" || snippet.passName == "TransparentDepthPrepass";

45
com.unity.render-pipelines.high-definition/HDRP/Editor/Upgraders/UpgradeMenuItem.cs


{
public class UpgradeMenuItems
{
//[MenuItem("Internal/HDRenderPipeline/Upgrade Scene Light Intensity to physical light unit", priority = CoreUtils.editMenuPriority2)]
static void UpgradeLightsPLU()
{
Light[] lights = Resources.FindObjectsOfTypeAll<Light>();
foreach (var l in lights)
{
var add = l.GetComponent<HDAdditionalLightData>();
if (add == null)
{
continue;
}
// We only need to update the new intensity parameters on additional data, no need to change intensity
if (add.lightTypeExtent == LightTypeExtent.Punctual)
{
switch (l.type)
{
case LightType.Point:
add.punctualIntensity = l.intensity / LightUtils.ConvertPointLightIntensity(1.0f);
break;
case LightType.Spot:
add.punctualIntensity = l.intensity / LightUtils.ConvertPointLightIntensity(1.0f);
break;
case LightType.Directional:
add.directionalIntensity = l.intensity;
break;
}
}
else if (add.lightTypeExtent == LightTypeExtent.Rectangle)
{
add.areaIntensity = l.intensity / LightUtils.ConvertRectLightIntensity(1.0f, add.shapeWidth, add.shapeHeight);
}
else if (add.lightTypeExtent == LightTypeExtent.Line)
{
add.areaIntensity = l.intensity / LightUtils.CalculateLineLightIntensity(1.0f, add.shapeWidth);
}
}
var scene = SceneManager.GetActiveScene();
EditorSceneManager.MarkSceneDirty(scene);
}
//[MenuItem("Internal/HDRenderPipeline/Update/Update material for subsurface")]
static void UpdateMaterialForSubsurface()

11
com.unity.render-pipelines.high-definition/HDRP/Lighting/GlobalIlluminationUtils.cs


add = HDUtils.s_DefaultHDAdditionalLightData;
}
// TODO: Only take into account the light dimmer when we have real time GI.
// TODO: Currently color temperature is not handled at runtime, need to expose useColorTemperature publicly
Color cct = new Color(1.0f, 1.0f, 1.0f);
#if UNITY_EDITOR
if (add.useColorTemperature)
cct = LightUtils.CorrelatedColorTemperatureToRGB(l.colorTemperature);
#endif
// TODO: Only take into account the light dimmer when we have real time GI.
ld.color.red *= cct.r;
ld.color.green *= cct.g;
ld.color.blue *= cct.b;
ld.indirectColor = add.affectDiffuse ? LightmapperUtils.ExtractIndirect(l) : LinearColor.Black();
// Note that the HDRI is correctly integrated in the GlobalIllumination system, we don't need to do anything regarding it.

393
com.unity.render-pipelines.high-definition/HDRP/Lighting/Light/HDAdditionalLightData.cs


public enum SpotLightShape { Cone, Pyramid, Box };
public enum LightUnit
{
Lumen,
Candela,
Lux,
Luminance,
}
// 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 TimelineWorkaournd
{
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;
}
public class HDAdditionalLightData : MonoBehaviour
[ExecuteInEditMode]
public class HDAdditionalLightData : MonoBehaviour, ISerializationCallbackReceiver
public const float currentVersion = 1.1f;
public float version = 1.0f;
[FormerlySerializedAs("m_Version")]
public float version = currentVersion;
[System.Obsolete("directionalIntensity is deprecated, use intensity and lightUnit instead")]
[System.Obsolete("punctualIntensity is deprecated, use intensity and lightUnit instead")]
[System.Obsolete("areaIntensity is deprecated, use intensity and lightUnit instead")]
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, 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 useOldInspector = false;
public bool featuresFoldout = true;
public bool showAdditionalSettings = false;
public float displayLightIntensity;
// 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
// We need these old states to make timeline and the animator record the intensity value and the emissive mesh changes (editor-only)
[System.NonSerialized]
TimelineWorkaournd 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;
m_Light.SetLightDirty(); // Should be apply only to parameter that's affect GI, but make the code cleaner
}
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)
{

DrawGizmos(true);
}
#endif
// 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;
}
}
// Caution: this function must match the one in HDLightEditor.UpdateLightIntensity - any change need to be replicated
public void ConvertPhysicalLightIntensityToLightIntensity()
// The editor can only access displayLightIntensity (because of SerializedProperties) so we update the intensity to get the real value
void RefreshLigthIntensity()
var light = gameObject.GetComponent<Light>();
intensity = displayLightIntensity;
}
if (lightTypeExtent == LightTypeExtent.Punctual)
{
switch (light.type)
{
case LightType.Directional:
light.intensity = Mathf.Max(0, directionalIntensity);
break;
public static bool IsAreaLight(LightTypeExtent lightType)
{
return lightType != LightTypeExtent.Punctual;
}
case LightType.Point:
light.intensity = LightUtils.ConvertPointLightIntensity(Mathf.Max(0, punctualIntensity));
break;
public static bool IsAreaLight(SerializedProperty lightType)
{
return IsAreaLight((LightTypeExtent)lightType.enumValueIndex);
}
case LightType.Spot:
public void UpdateAreaLightEmissiveMesh()
{
MeshRenderer emissiveMeshRenderer = GetComponent<MeshRenderer>();
MeshFilter emissiveMeshFilter = GetComponent<MeshFilter>();
if (enableSpotReflector)
{
if (spotLightShape == SpotLightShape.Cone)
{
light.intensity = LightUtils.ConvertSpotLightIntensity(Mathf.Max(0, punctualIntensity), light.spotAngle * Mathf.Deg2Rad, true);
}
else if (spotLightShape == SpotLightShape.Pyramid)
{
float angleA, angleB;
LightUtils.CalculateAnglesForPyramid(aspectRatio, light.spotAngle,
out angleA, out angleB);
bool displayEmissiveMesh = IsAreaLight(lightTypeExtent) && lightTypeExtent != LightTypeExtent.Line && displayAreaLightEmissiveMesh;
light.intensity = LightUtils.ConvertFrustrumLightIntensity(Mathf.Max(0, punctualIntensity), angleA, angleB);
}
else // Box shape, fallback to punctual light.
{
light.intensity = LightUtils.ConvertPointLightIntensity(Mathf.Max(0, punctualIntensity));
}
}
else
{
// Spot should used conversion which take into account the angle, and thus the intensity vary with angle.
// This is not easy to manipulate for lighter, so we simply consider any spot light as just occluded point light. So reuse the same code.
light.intensity = LightUtils.ConvertPointLightIntensity(Mathf.Max(0, punctualIntensity));
// TODO: What to do with box shape ?
// var spotLightShape = (SpotLightShape)m_AdditionalspotLightShape.enumValueIndex;
}
break;
}
// 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 if (lightTypeExtent == LightTypeExtent.Rectangle)
else // Or remove them if the option is disabled
light.intensity = LightUtils.ConvertRectLightIntensity(Mathf.Max(0, areaIntensity), shapeWidth, shapeHeight);
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 ;
else if (lightTypeExtent == LightTypeExtent.Line)
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)
light.intensity = LightUtils.CalculateLineLightIntensity(Mathf.Max(0, areaIntensity), shapeWidth);
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)

// 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)
{

// We don't use the global settings of shadow mask by default
light.lightShadowCasterMode = LightShadowCasterMode.Everything;
}
// At first init we need to initialize correctly the default value
lightData.ConvertPhysicalLightIntensityToLightIntensity();
public void OnBeforeSerialize() {}
public void OnAfterDeserialize()
{
// If we are deserializing an old version, convert the light intensity to the new system
if (version <= 1.0f)
{
needsIntensityUpdate_1_0 = true;
}
version = currentVersion;
}
private void OnEnable()
{
if (needsIntensityUpdate_1_0)
{
// Pragma to disable the warning got by using deprecated properties (areaIntensity, directionalIntensity, ...)
#pragma warning disable 0618
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;
}
#pragma warning restore 0618
}
}
}
}

82
com.unity.render-pipelines.high-definition/HDRP/Lighting/LightUtils.cs


// Also good ref: https://www.radiance-online.org/community/workshops/2004-fribourg/presentations/Wandachowicz_paper.pdf
// convert intensity (lumen) to candela
public static float ConvertPointLightIntensity(float intensity)
public static float ConvertPointLightLumenToCandela(float intensity)
// convert intensity (candela) to lumen
public static float ConvertPointLightCandelaToLumen(float intensity)
{
return intensity * (4.0f * Mathf.PI);
}
public static float ConvertSpotLightIntensity(float intensity, float angle, bool exact)
public static float ConvertSpotLightLumenToCandela(float intensity, float angle, bool exact)
public static float ConvertSpotLightCandelaToLumen(float intensity, float angle, bool exact)
{
return exact ? intensity * (2.0f * (1.0f - Mathf.Cos(angle / 2.0f)) * Mathf.PI) : intensity * Mathf.PI;
}
public static float ConvertFrustrumLightIntensity(float intensity, float angleA, float angleB)
public static float ConvertFrustrumLightLumenToCandela(float intensity, float angleA, float angleB)
public static float ConvertFrustrumLightCandelaToLumen(float intensity, float angleA, float angleB)
{
return intensity * (4.0f * Mathf.Asin(Mathf.Sin(angleA / 2.0f) * Mathf.Sin(angleB / 2.0f)));
}
public static float ConvertSphereLightIntensity(float intensity, float sphereRadius)
public static float ConvertSphereLightLumenToLuminance(float intensity, float sphereRadius)
// convert intensity (nits) to lumen
public static float ConvertSphereLightLuminanceToLumen(float intensity, float sphereRadius)
{
return intensity * ((4.0f * Mathf.PI * sphereRadius * sphereRadius) * Mathf.PI);
}
public static float ConvertDiscLightIntensity(float intensity, float discRadius)
public static float ConvertDiscLightLumenToLuminance(float intensity, float discRadius)
// convert intensity (nits) to lumen
public static float ConvertDiscLightLuminanceToLumen(float intensity, float discRadius)
{
return intensity * ((discRadius * discRadius * Mathf.PI) * Mathf.PI);
}
public static float ConvertRectLightIntensity(float intensity, float width, float height)
public static float ConvertRectLightLumenToLuminance(float intensity, float width, float height)
// convert intensity (nits) to lumen
public static float ConvertRectLightLuminanceToLumen(float intensity, float width, float height)
{
return intensity * ((width * height) * Mathf.PI);
}
public static float CalculateLineLightIntensity(float intensity, float lineWidth)
public static float CalculateLineLightLumenToLuminance(float intensity, float lineWidth)
{
//Line lights expect radiance (W / (sr * m^2)) in the shader.
//In the UI, we specify luminous flux (power) in lumens.

return intensity / (4.0f * Mathf.PI * lineWidth);
}
public static float CalculateLineLightLuminanceToLumen(float intensity, float lineWidth)
{
return intensity * (4.0f * Mathf.PI * lineWidth);
}
// spotAngle in radiant
public static void CalculateAnglesForPyramid(float aspectRatio, float spotAngle, out float angleA, out float angleB)
{
// Since the smallest angles is = to the fov, and we don't care of the angle order, simply make sure the aspect ratio is > 1

angleA = spotAngle * Mathf.Deg2Rad;
angleA = spotAngle;
var halfAngle = angleA * 0.5f; // half of the smallest angle
var length = Mathf.Tan(halfAngle); // half length of the smallest side of the rectangle

angleB = halfAngle * 2.0f;
}
// TODO: Do a cheaper fitting
// Given a correlated color temperature (in Kelvin), estimate the RGB equivalent. Curve fit error is max 0.008.
// return color in linear RGB space
public static Color CorrelatedColorTemperatureToRGB(float temperature)
{
float r, g, b;
// Temperature must fall between 1000 and 40000 degrees
// The fitting require to divide kelvin by 1000 (allow more precision)
float kelvin = Mathf.Clamp(temperature, 1000.0f, 40000.0f) / 1000.0f;
float kelvin2 = kelvin * kelvin;
// Using 6570 as a pivot is an approximation, pivot point for red is around 6580 and for blue and green around 6560.
// Calculate each color in turn (Note, clamp is not really necessary as all value belongs to [0..1] but can help for extremum).
// Red
r = kelvin < 6.570f ? 1.0f : Mathf.Clamp((1.35651f + 0.216422f * kelvin + 0.000633715f * kelvin2) / (-3.24223f + 0.918711f * kelvin), 0.0f, 1.0f);
// Green
g = kelvin < 6.570f ?
Mathf.Clamp((-399.809f + 414.271f * kelvin + 111.543f * kelvin2) / (2779.24f + 164.143f * kelvin + 84.7356f * kelvin2), 0.0f, 1.0f) :
Mathf.Clamp((1370.38f + 734.616f * kelvin + 0.689955f * kelvin2) / (-4625.69f + 1699.87f * kelvin), 0.0f, 1.0f);
//Blue
b = kelvin > 6.570f ? 1.0f : Mathf.Clamp((348.963f - 523.53f * kelvin + 183.62f * kelvin2) / (2848.82f - 214.52f * kelvin + 78.8614f * kelvin2), 0.0f, 1.0f);
return new Color(r, g, b, 1.0f);
}
}
}
正在加载...
取消
保存