Skip to content

Custom Middle grey for auto exposure #599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions com.unity.render-pipelines.core/Runtime/Utilities/ColorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ namespace UnityEngine.Rendering
/// </summary>
public static class ColorUtils
{
/// <summary>
/// Calibration constant (K) used for our virtual reflected light meter. Modifying this will lead to a change on how average scene luminance
/// gets mapped to exposure.
/// </summary>
static public float s_LightMeterCalibrationConstant = 12.5f;

/// <summary>
/// Factor used for our lens system w.r.t. exposure calculation. Modifying this will lead to a change on how linear exposure
/// multipliers are computed from EV100 values (and viceversa). s_LensAttenuation models transmission attenuation and lens vignetting.
/// Note that according to the standard ISO 12232, a lens saturates at s_LensAttenuation = 0.78f (under ISO 100).
/// </summary>
static public float s_LensAttenuation = 0.65f;

/// <summary>
/// Scale applied to exposure caused by lens imperfection. It is computed from s_LensAttenuation as follow:
/// (78 / ( S * q )) where S = 100 and q = s_LensAttenuation
/// </summary>
static public float lensImperfectionExposureScale
{
get => (78.0f / (100.0f * s_LensAttenuation));
}

/// <summary>
/// An analytical model of chromaticity of the standard illuminant, by Judd et al.
/// http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
Expand Down Expand Up @@ -215,10 +237,10 @@ public static float ConvertEV100ToExposure(float EV100)
// Compute the maximum luminance possible with H_sbs sensitivity
// maxLum = 78 / ( S * q ) * N^2 / t
// = 78 / ( S * q ) * 2^ EV_100
// = 78 / (100 * 0.65) * 2^ EV_100
// = 1.2 * 2^ EV
// = 78 / (100 * s_LensAttenuation) * 2^ EV_100
// = lensImperfectionExposureScale * 2^ EV
// Reference: http://en.wikipedia.org/wiki/Film_speed
float maxLuminance = 1.2f * Mathf.Pow(2.0f, EV100);
float maxLuminance = lensImperfectionExposureScale * Mathf.Pow(2.0f, EV100);
return 1.0f / maxLuminance;
}

Expand All @@ -231,10 +253,10 @@ public static float ConvertExposureToEV100(float exposure)
{
// Compute the maximum luminance possible with H_sbs sensitivity
// EV_100 = log2( S * q / (78 * exposure) )
// = log2( 100 * 0.65 / (78 * exposure) )
// = log2( 1.0f / (1.2 * exposure) )
// = log2( 100 * s_LensAttenuation / (78 * exposure) )
// = log2( 1.0f / (lensImperfectionExposureScale * exposure) )
// Reference: http://en.wikipedia.org/wiki/Film_speed
return Mathf.Log(1.0f / (1.2f * exposure), 2.0f);
return Mathf.Log(1.0f / (lensImperfectionExposureScale * exposure), 2.0f);
}

/// <summary>
Expand All @@ -244,13 +266,14 @@ public static float ConvertExposureToEV100(float exposure)
/// <returns>An exposure value, in EV100.</returns>
public static float ComputeEV100FromAvgLuminance(float avgLuminance)
{
// We later use the middle gray at 12.7% in order to have
// The middle grey used will be determined by the s_LightMeterCalibrationConstant.
// The suggested (ISO 2720) range is 10.64 to 13.4. Common values used by
// manufacturers range from 11.37 to 14. Ref: https://en.wikipedia.org/wiki/Light_meter
// The default is 12.5% as it is the closest to 12.7% in order to have
// a middle gray at 18% with a sqrt(2) room for specular highlights
// But here we deal with the spot meter measuring the middle gray
// which is fixed at 12.5 for matching standard camera
// constructor settings (i.e. calibration constant K = 12.5)
// Reference: http://en.wikipedia.org/wiki/Film_speed
const float K = 12.5f; // Reflected-light meter calibration constant
// Note that this gives equivalent results as using an incident light meter
// with a calibration constant of C=314.
float K = s_LightMeterCalibrationConstant;
return Mathf.Log(avgLuminance * 100f / K, 2f);
}

Expand Down
22 changes: 17 additions & 5 deletions com.unity.render-pipelines.core/ShaderLibrary/PhysicalCamera.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ float ComputeEV100(float aperture, float shutterSpeed, float ISO)
return log2((aperture * aperture) / shutterSpeed * 100.0 / ISO);
}

float ComputeEV100FromAvgLuminance(float avgLuminance, float calibrationConstant)
{
const float K = calibrationConstant;
return log2(avgLuminance * 100.0 / K);
}

float ComputeEV100FromAvgLuminance(float avgLuminance)
{
// We later use the middle gray at 12.7% in order to have
Expand All @@ -30,21 +36,27 @@ float ComputeEV100FromAvgLuminance(float avgLuminance)
// constructor settings (i.e. calibration constant K = 12.5)
// Reference: http://en.wikipedia.org/wiki/Film_speed
const float K = 12.5; // Reflected-light meter calibration constant
return log2(avgLuminance * 100.0 / K);
return ComputeEV100FromAvgLuminance(avgLuminance, K);
}

float ConvertEV100ToExposure(float EV100)
float ConvertEV100ToExposure(float EV100, float exposureScale)
{
// Compute the maximum luminance possible with H_sbs sensitivity
// maxLum = 78 / ( S * q ) * N^2 / t
// = 78 / ( S * q ) * 2^ EV_100
// = 78 / (100 * 0.65) * 2^ EV_100
// = 1.2 * 2^ EV
// = 78 / (100 * s_LensAttenuation) * 2^ EV_100
// = exposureScale * 2^ EV
// Reference: http://en.wikipedia.org/wiki/Film_speed
float maxLuminance = 1.2 * pow(2.0, EV100);
float maxLuminance = exposureScale * pow(2.0, EV100);
return 1.0 / maxLuminance;
}

float ConvertEV100ToExposure(float EV100)
{
const float exposureScale = 1.2;
return ConvertEV100ToExposure(EV100, exposureScale);
}

float ComputeISO(float aperture, float shutterSpeed, float targetEV100)
{
// Compute the required ISO to reach the target EV100
Expand Down
1 change: 1 addition & 0 deletions com.unity.render-pipelines.high-definition/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added Histogram guided automatic exposure.
- Added few exposure debug modes.
- Added support for multiple path-traced views at once (e.g., scene and game views).
- Added custom target mid grey for auto exposure.

### Fixed
- Fix when rescale probe all direction below zero (1219246)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ sealed class ExposureEditor : VolumeComponentEditor
SerializedDataParameter m_HistogramPercentages;
SerializedDataParameter m_HistogramCurveRemapping;

SerializedDataParameter m_TargetMidGray;

static readonly string[] s_MidGrayNames = { "Grey 12.5%", "Grey 14.0%", "Grey 18.0%" };

public override bool hasAdvancedMode => true;

public override void OnEnable()
{
var o = new PropertyFetcher<Exposure>(serializedObject);
Expand All @@ -48,7 +54,7 @@ public override void OnEnable()

m_HistogramPercentages = Unpack(o.Find(x => x.histogramPercentages));
m_HistogramCurveRemapping = Unpack(o.Find(x => x.histogramUseCurveRemapping));

m_TargetMidGray = Unpack(o.Find(x => x.targetMidGray));
}

public override void OnInspectorGUI()
Expand Down Expand Up @@ -108,6 +114,25 @@ public override void OnInspectorGUI()
PropertyField(m_AdaptationSpeedDarkToLight, EditorGUIUtility.TrTextContent("Speed Dark to Light"));
PropertyField(m_AdaptationSpeedLightToDark, EditorGUIUtility.TrTextContent("Speed Light to Dark"));
}

if (isInAdvancedMode)
{
EditorGUILayout.Space();

using (new EditorGUILayout.HorizontalScope())
{
// Override checkbox
DrawOverrideCheckbox(m_TargetMidGray);

// Property
using (new EditorGUI.DisabledScope(!m_TargetMidGray.overrideState.boolValue))
{
// Default unity field
m_TargetMidGray.value.intValue = EditorGUILayout.Popup(EditorGUIUtility.TrTextContent("Target Mid Grey", "Sets the desired Mid gray level used by the auto exposure (i.e. to what grey value the auto exposure system maps the average scene luminance)."),
m_TargetMidGray.value.intValue, s_MidGrayNames);
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,17 @@ void RegisterLightingDebug()
});
}

exposureFoldout.children.Add(
new DebugUI.FloatField
{
displayName = "Debug Lens Attenuation",
getter = () => Mathf.Clamp01(data.lightingDebugSettings.debugLensAttenuation),
setter = value => data.lightingDebugSettings.debugLensAttenuation = Mathf.Clamp01(value),
min = () => 0.1f,
max = () => 0.78f

});

exposureFoldout.children.Add(
new DebugUI.FloatField
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Shader "Hidden/HDRP/DebugExposure"
HLSLINCLUDE

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl"
#define DEBUG_DISPLAY
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl"
Expand Down Expand Up @@ -98,7 +97,7 @@ Shader "Hidden/HDRP/DebugExposure"

float GetEVAtLocation(float2 uv)
{
return ComputeEV100FromAvgLuminance(max(SampleLuminance(uv), 1e-4));
return ComputeEV100FromAvgLuminance(max(SampleLuminance(uv), 1e-4), MeterCalibrationConstant);
}

// Returns true if it drew the location of the indicator.
Expand Down Expand Up @@ -561,7 +560,7 @@ Shader "Hidden/HDRP/DebugExposure"
int displayTextOffsetX = DEBUG_FONT_TEXT_WIDTH;
textLocation = uint2(_MousePixelCoord.x + displayTextOffsetX, _MousePixelCoord.y);
DrawFloatExplicitPrecision(indicatorEV, textColor, unormCoord, 1, textLocation, outputColor.rgb);
textLocation = uint2(_MousePixelCoord.xy);
textLocation = _MousePixelCoord.xy;
DrawCharacter('X', float3(0.0f, 0.0f, 0.0f), unormCoord, textLocation, outputColor.rgb);

return outputColor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ public bool IsDebugDisplayEnabled()
public ExposureDebugMode exposureDebugMode = ExposureDebugMode.None;
/// <summary>Exposure compensation to apply on current scene exposure.</summary>
public float debugExposure = 0.0f;
/// <summary>Debug lens attenuation factor for the virtual camera.</summary>
public float debugLensAttenuation = 0.65f;
/// <summary>Whether to show tonemap curve in the histogram debug view or not.</summary>
public bool showTonemapCurveAlongHistogramView = true;
/// <summary>Whether to center the histogram debug view around the middle-grey point or not.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ public sealed class Exposure : VolumeComponent, IPostProcessComponent
[Tooltip("Sets whether histogram exposure mode will remap the computed exposure with a curve remapping (akin to Curve Remapping mode).")]
public BoolParameter histogramUseCurveRemapping = new BoolParameter(false);

/// <summary>
/// Sets the desired Mid gray level used by the auto exposure (i.e. to what grey value the auto exposure system maps the average scene luminance).
/// Note that the lens model used in HDRP is not of a perfect lens, hence it will not map precisely to the selected value.
/// </summary>
[Tooltip("Sets the desired Mid gray level used by the auto exposure (i.e. to what grey value the auto exposure system maps the average scene luminance).")]
public TargetMidGrayParameter targetMidGray = new TargetMidGrayParameter(TargetMidGray.Grey125);

/// <summary>
/// Tells if the effect needs to be rendered or not.
/// </summary>
Expand Down Expand Up @@ -205,6 +212,27 @@ public enum LuminanceSource
ColorBuffer
}

/// <summary>
/// The target grey value used by the exposure system. Note this is equivalent of changing the calibration constant K on the used virtual reflected light meter.
/// </summary>
public enum TargetMidGray
{
/// <summary>
/// Mid Grey 12.5% (reflected light meter K set as 12.5)
/// </summary>
Grey125,

/// <summary>
/// Mid Grey 14.0% (reflected light meter K set as 14.0)
/// </summary>
Grey14,

/// <summary>
/// Mid Grey 18.0% (reflected light meter K set as 18.0). Note that this value is outside of the suggested K range by the ISO standard.
/// </summary>
Grey18
}

/// <summary>
/// Methods that HDRP uses to change the exposure when the Camera moves from dark to light and vice versa.
/// </summary>
Expand Down Expand Up @@ -279,4 +307,18 @@ public sealed class AdaptationModeParameter : VolumeParameter<AdaptationMode>
/// <param name="overrideState">The initial override state for the parameter.</param>
public AdaptationModeParameter(AdaptationMode value, bool overrideState = false) : base(value, overrideState) {}
}

/// <summary>
/// A <see cref="VolumeParameter"/> that holds a <see cref="TargetMidGray"/> value.
/// </summary>
[Serializable]
public sealed class TargetMidGrayParameter : VolumeParameter<TargetMidGray>
{
/// <summary>
/// Creates a new <see cref="TargetMidGrayParameter"/> instance.
/// </summary>
/// <param name="value">The initial value to store in the parameter.</param>
/// <param name="overrideState">The initial override state for the parameter.</param>
public TargetMidGrayParameter(TargetMidGray value, bool overrideState = false) : base(value, overrideState) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer)
cmd.SetComputeIntParams(cs, HDShaderIDs._Variants, m_ExposureVariants);
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._PreviousExposureTexture, prevExposure);
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._SourceTexture, sourceTex);
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(0.0f, 0.0f, ColorUtils.lensImperfectionExposureScale, ColorUtils.s_LightMeterCalibrationConstant));

if (m_Exposure.meteringMode == MeteringMode.MaskWeighted && m_Exposure.weightTextureMask.value != null)
{
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureWeightMask, m_Exposure.weightTextureMask.value);
Expand Down Expand Up @@ -983,7 +985,7 @@ void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer)
PrepareExposureCurveData(m_Exposure.curveMap.value, out float min, out float max);
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureCurveTexture, m_ExposureCurveTexture);
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams, new Vector4(m_Exposure.compensation.value + m_DebugExposureCompensation, min, max, 0f));
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(min, max, 0f, 0f));
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(min, max, ColorUtils.lensImperfectionExposureScale, ColorUtils.s_LightMeterCalibrationConstant));

m_ExposureVariants[3] = 2;
}
Expand Down Expand Up @@ -1041,6 +1043,7 @@ void DoHistogramBasedExposure(CommandBuffer cmd, HDCamera camera, RTHandle sourc
// Now read the histogram
kernel = cs.FindKernel("KHistogramReduce");
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams, new Vector4(m_Exposure.compensation.value + m_DebugExposureCompensation, m_Exposure.limitMin.value, m_Exposure.limitMax.value, 0f));
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(0.0f, 0.0f, ColorUtils.lensImperfectionExposureScale, ColorUtils.s_LightMeterCalibrationConstant));
cmd.SetComputeVectorParam(cs, HDShaderIDs._AdaptationParams, new Vector4(m_Exposure.adaptationSpeedLightToDark.value, m_Exposure.adaptationSpeedDarkToLight.value, 0f, 0f));
cmd.SetComputeBufferParam(cs, kernel, HDShaderIDs._HistogramBuffer, m_HistogramBuffer);
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._PreviousExposureTexture, prevExposure);
Expand All @@ -1051,7 +1054,7 @@ void DoHistogramBasedExposure(CommandBuffer cmd, HDCamera camera, RTHandle sourc
if (m_Exposure.histogramUseCurveRemapping.value)
{
PrepareExposureCurveData(m_Exposure.curveMap.value, out float min, out float max);
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(min, max, 0f, 0f));
cmd.SetComputeVectorParam(cs, HDShaderIDs._ExposureParams2, new Vector4(min, max, ColorUtils.lensImperfectionExposureScale, ColorUtils.s_LightMeterCalibrationConstant));
m_ExposureVariants[3] = 2;
}
cmd.SetComputeIntParams(cs, HDShaderIDs._Variants, m_ExposureVariants);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ void KFixedExposure(uint2 dispatchThreadId : SV_DispatchThreadID)
{
float ev100 = ParamEV100;
ev100 -= ParamExposureCompensation;
_OutputTexture[dispatchThreadId] = float2(ConvertEV100ToExposure(ev100), ev100);
_OutputTexture[dispatchThreadId] = float2(ConvertEV100ToExposure(ev100, LensImperfectionExposureScale), ev100);
}

//
Expand All @@ -34,7 +34,7 @@ void KManualCameraExposure(uint2 dispatchThreadId : SV_DispatchThreadID)
{
float ev100 = ComputeEV100(ParamAperture, ParamShutterSpeed, ParamISO);
ev100 -= ParamExposureCompensation;
_OutputTexture[dispatchThreadId] = float2(ConvertEV100ToExposure(ev100), ev100);
_OutputTexture[dispatchThreadId] = float2(ConvertEV100ToExposure(ev100, LensImperfectionExposureScale), ev100);
}

//
Expand All @@ -53,7 +53,7 @@ void KPrePass(uint2 dispatchThreadId : SV_DispatchThreadID)

float weight = WeightSample(dispatchThreadId, PREPASS_TEX_SIZE.xx);

float logLuma = ComputeEV100FromAvgLuminance(max(luma, 1e-4));
float logLuma = ComputeEV100FromAvgLuminance(max(luma, 1e-4), MeterCalibrationConstant);
_OutputTexture[posInputs.positionSS] = float2(logLuma, weight);
}

Expand Down Expand Up @@ -120,15 +120,15 @@ void KReduction(uint2 groupId : SV_GroupID, uint2 groupThreadId : SV_GroupThread
// Automatic
float exposure = AdaptExposure(avgLuminance - ParamExposureCompensation);
exposure = clamp(exposure, ParamExposureLimitMin, ParamExposureLimitMax);
_OutputTexture[groupId.xy] = float2(ConvertEV100ToExposure(exposure), exposure);
_OutputTexture[groupId.xy] = float2(ConvertEV100ToExposure(exposure, LensImperfectionExposureScale), exposure);
break;
}
case 2u:
{
// Curve remapping
float exposure = CurveRemap(avgLuminance);
exposure = AdaptExposure(exposure - ParamExposureCompensation);
_OutputTexture[groupId.xy] = float2(ConvertEV100ToExposure(exposure), exposure);
_OutputTexture[groupId.xy] = float2(ConvertEV100ToExposure(exposure, LensImperfectionExposureScale), exposure);
break;
}
default:
Expand Down
Loading