diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 650011149f7..56b1fe6f8d6 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -128,6 +128,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added an initial version of SSGI. - Added back-compatibility with builtin stereo matrices. - Added CustomPassUtils API to simplify Blur, Copy and DrawRenderers custom passes. +- Added Histogram guided automatic exposure. +- Added few exposure debug modes. ### Fixed - Fix when rescale probe all direction below zero (1219246) diff --git a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/ExposureEditor.cs b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/ExposureEditor.cs index a08652ed5d8..35c7c585bbe 100644 --- a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/ExposureEditor.cs +++ b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/ExposureEditor.cs @@ -1,4 +1,5 @@ using UnityEditor.Rendering; +using UnityEngine; using UnityEngine.Rendering.HighDefinition; namespace UnityEditor.Rendering.HighDefinition @@ -22,6 +23,9 @@ sealed class ExposureEditor : VolumeComponentEditor SerializedDataParameter m_WeightTextureMask; + SerializedDataParameter m_HistogramPercentages; + SerializedDataParameter m_HistogramCurveRemapping; + public override void OnEnable() { var o = new PropertyFetcher(serializedObject); @@ -41,6 +45,10 @@ public override void OnEnable() m_AdaptationSpeedLightToDark = Unpack(o.Find(x => x.adaptationSpeedLightToDark)); m_WeightTextureMask = Unpack(o.Find(x => x.weightTextureMask)); + + m_HistogramPercentages = Unpack(o.Find(x => x.histogramPercentages)); + m_HistogramCurveRemapping = Unpack(o.Find(x => x.histogramUseCurveRemapping)); + } public override void OnInspectorGUI() @@ -65,10 +73,11 @@ public override void OnInspectorGUI() if(m_MeteringMode.value.intValue == (int)MeteringMode.MaskWeighted) PropertyField(m_WeightTextureMask); - PropertyField(m_LuminanceSource); + // Temporary hiding the field since we don't support anything but color buffer for now. + //PropertyField(m_LuminanceSource); - if (m_LuminanceSource.value.intValue == (int)LuminanceSource.LightingBuffer) - EditorGUILayout.HelpBox("Luminance source buffer isn't supported yet.", MessageType.Warning); + //if (m_LuminanceSource.value.intValue == (int)LuminanceSource.LightingBuffer) + // EditorGUILayout.HelpBox("Luminance source buffer isn't supported yet.", MessageType.Warning); if (mode == (int)ExposureMode.CurveMapping) PropertyField(m_CurveMap); @@ -76,7 +85,19 @@ public override void OnInspectorGUI() PropertyField(m_Compensation); PropertyField(m_LimitMin); PropertyField(m_LimitMax); - + + if(mode == (int)ExposureMode.AutomaticHistogram) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Histogram", EditorStyles.miniLabel); + PropertyField(m_HistogramPercentages); + PropertyField(m_HistogramCurveRemapping, EditorGUIUtility.TrTextContent("Use Curve Remapping")); + if (m_HistogramCurveRemapping.value.boolValue) + { + PropertyField(m_CurveMap); + } + } + EditorGUILayout.Space(); EditorGUILayout.LabelField("Adaptation", EditorStyles.miniLabel); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs index 2c960245d8e..192034208aa 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs @@ -218,6 +218,7 @@ public class DebugData internal int renderingFulscreenDebugModeEnumIndex; internal int terrainTextureEnumIndex; internal int colorPickerDebugModeEnumIndex; + internal int exposureDebugModeEnumIndex; internal int msaaSampleDebugModeEnumIndex; internal int debugCameraToFreezeEnumIndex; internal int volumeComponentEnumIndex; @@ -407,7 +408,7 @@ public bool IsDebugDisplayEnabled() /// True if any material debug display is enabled. public bool IsDebugMaterialDisplayEnabled() { - return data.materialDebugSettings.IsDebugDisplayEnabled(); + return data.materialDebugSettings.IsDebugDisplayEnabled(); } /// @@ -419,6 +420,15 @@ public bool IsDebugFullScreenEnabled() return data.fullScreenDebugMode != FullScreenDebugMode.None; } + /// + /// Returns true if any full screen exposure debug display is enabled. + /// + /// True if any full screen exposure debug display is enabled. + public bool IsDebugExposureModeEnabled() + { + return data.lightingDebugSettings.exposureDebugMode != ExposureDebugMode.None; + } + /// /// Returns true if material validation is enabled. /// @@ -626,6 +636,15 @@ internal void SetProbeVolumeAtlasSliceMode(ProbeVolumeAtlasSliceMode value) data.lightingDebugSettings.probeVolumeAtlasSliceMode = value; } + /// + /// Set the current Exposure Debug Mode. + /// + /// Desired Probe Volume Debug Mode. + internal void SetExposureDebugMode(ExposureDebugMode value) + { + data.lightingDebugSettings.exposureDebugMode = value; + } + /// /// Set the current Mip Map Debug Mode. /// @@ -872,6 +891,44 @@ void RegisterLightingDebug() } }); + var exposureFoldout = new DebugUI.Foldout + { + displayName = "Exposure ", + children = + { + new DebugUI.EnumField + { + displayName = "Debug Mode", + getter = () => (int) data.lightingDebugSettings.exposureDebugMode, + setter = value => SetExposureDebugMode((ExposureDebugMode) value), + autoEnum = typeof(ExposureDebugMode), onValueChanged = RefreshLightingDebug, + getIndex = () => data.exposureDebugModeEnumIndex, + setIndex = value => data.exposureDebugModeEnumIndex = value + } + } + }; + + if (data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.HistogramView) + { + exposureFoldout.children.Add( + new DebugUI.BoolField() + { + displayName = "Show Tonemap curve", + getter = () => data.lightingDebugSettings.showTonemapCurveAlongHistogramView, + setter = value => data.lightingDebugSettings.showTonemapCurveAlongHistogramView = value + }); + } + + exposureFoldout.children.Add( + new DebugUI.FloatField + { + displayName = "Debug Exposure Compensation", + getter = () => data.lightingDebugSettings.debugExposure, + setter = value => data.lightingDebugSettings.debugExposure = value + }); + + lighting.children.Add(exposureFoldout); + lighting.children.Add(new DebugUI.EnumField { displayName = "Debug Mode", getter = () => (int)data.lightingDebugSettings.debugLightingMode, setter = value => SetDebugLightingMode((DebugLightingMode)value), autoEnum = typeof(DebugLightingMode), onValueChanged = RefreshLightingDebug, getIndex = () => data.lightingDebugModeEnumIndex, setIndex = value => { data.ResetExclusiveEnumIndices(); data.lightingDebugModeEnumIndex = value; } }); lighting.children.Add(new DebugUI.BitField { displayName = "Hierarchy Debug Mode", getter = () => data.lightingDebugSettings.debugLightFilterMode, setter = value => SetDebugLightFilterMode((DebugLightFilterMode)value), enumType = typeof(DebugLightFilterMode), onValueChanged = RefreshLightingDebug, }); @@ -1125,8 +1182,6 @@ void RegisterLightingDebug() list.Add(new DebugUI.FloatField { displayName = "Debug Overlay Screen Ratio", getter = () => data.debugOverlayRatio, setter = v => data.debugOverlayRatio = v, min = () => 0.1f, max = () => 1f}); - list.Add(new DebugUI.FloatField { displayName = "Debug Exposure Compensation", getter = () => data.lightingDebugSettings.debugExposure, setter = value => data.lightingDebugSettings.debugExposure = value }); - m_DebugLightingItems = list.ToArray(); var panel = DebugManager.instance.GetPanel(k_PanelLighting, true); panel.children.Add(m_DebugLightingItems); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl index 9b5f009aa19..1ed2109002a 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl @@ -180,6 +180,7 @@ void DrawInteger(int intValue, float3 fontColor, uint2 currentUnormCoord, inout fixedUnormCoord.x += numEntries * DEBUG_FONT_TEXT_SCALE_WIDTH; // 3. Display the number + [unroll] // Needed to supress warning as some odd code gen is happening here. Is bad for perf, but it is a debug display. for (uint j = 0; j < maxStringSize; ++j) { // Numeric value incurrent font start on the second row at 0 @@ -213,7 +214,7 @@ void DrawInteger(int intValue, float3 fontColor, uint2 currentUnormCoord, inout DrawInteger(intValue, fontColor, currentUnormCoord, fixedUnormCoord, color, 0, false); } -void DrawFloat(float floatValue, float3 fontColor, uint2 currentUnormCoord, inout uint2 fixedUnormCoord, inout float3 color) +void DrawFloatExplicitPrecision(float floatValue, float3 fontColor, uint2 currentUnormCoord, uint digitCount, inout uint2 fixedUnormCoord, inout float3 color) { if (IsNaN(floatValue)) { @@ -227,12 +228,17 @@ void DrawFloat(float floatValue, float3 fontColor, uint2 currentUnormCoord, inou bool forceNegativeSign = floatValue >= 0.0f ? false : true; DrawInteger(intValue, fontColor, currentUnormCoord, fixedUnormCoord, color, 0, forceNegativeSign); DrawCharacter('.', fontColor, currentUnormCoord, fixedUnormCoord, color); - int fracValue = int(frac(abs(floatValue)) * 1e6); // 6 digit - int leading0 = 6 - (int(log10(fracValue)) + 1); // Counting leading0 to add in front of the float + int fracValue = int(frac(abs(floatValue)) * pow(10, digitCount)); + int leading0 = digitCount - (int(log10(fracValue)) + 1); // Counting leading0 to add in front of the float DrawInteger(fracValue, fontColor, currentUnormCoord, fixedUnormCoord, color, leading0, false); } } +void DrawFloat(float floatValue, float3 fontColor, uint2 currentUnormCoord, inout uint2 fixedUnormCoord, inout float3 color) +{ + DrawFloatExplicitPrecision(floatValue, fontColor, currentUnormCoord, 6, fixedUnormCoord, color); +} + // Debug rendering is performed at the end of the frame (after post-processing). // Debug textures are never flipped upside-down automatically. Therefore, we must always flip manually. bool ShouldFlipDebugTexture() diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader new file mode 100644 index 00000000000..bdd95e18d60 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader @@ -0,0 +1,552 @@ +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" + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ACES.hlsl" + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" + + #pragma vertex Vert + #pragma target 4.5 + #pragma only_renderers d3d11 playstation xboxone vulkan metal switch + + #define PERCENTILE_AS_BARS 0 + + // Contains the scene color post-processed (tonemapped etc.) + TEXTURE2D_X(_DebugFullScreenTexture); + + // Tonemap related + TEXTURE3D(_LogLut3D); + SAMPLER(sampler_LogLut3D); + + float4 _ExposureDebugParams; + float4 _LogLut3D_Params; // x: 1 / lut_size, y: lut_size - 1, z: contribution, w: unused + // Custom tonemapping settings + float4 _CustomToneCurve; + float4 _ToeSegmentA; + float4 _ToeSegmentB; + float4 _MidSegmentA; + float4 _MidSegmentB; + float4 _ShoSegmentA; + float4 _ShoSegmentB; + + #define _DrawTonemapCurve _ExposureDebugParams.x + #define _TonemapType _ExposureDebugParams.y + + + struct Attributes + { + uint vertexID : SV_VertexID; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + Varyings Vert(Attributes input) + { + Varyings output; + output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID); + output.texcoord = GetNormalizedFullScreenTriangleTexCoord(input.vertexID); + + return output; + } + + float3 Tonemap(float3 colorLinear) + { + if(_TonemapType == TONEMAPPINGMODE_NEUTRAL) + { + colorLinear = NeutralTonemap(colorLinear); + } + if (_TonemapType == TONEMAPPINGMODE_ACES) + { + // Note: input is actually ACEScg (AP1 w/ linear encoding) + float3 aces = ACEScg_to_ACES(colorLinear); + colorLinear = AcesTonemap(aces); + } + if (_TonemapType == TONEMAPPINGMODE_CUSTOM) // Custom + { + colorLinear = CustomTonemap(colorLinear, _CustomToneCurve.xyz, _ToeSegmentA, _ToeSegmentB.xy, _MidSegmentA, _MidSegmentB.xy, _ShoSegmentA, _ShoSegmentB.xy); + } + if (_TonemapType == TONEMAPPINGMODE_EXTERNAL) // External + { + float3 colorLutSpace = saturate(LinearToLogC(colorLinear)); + float3 colorLut = ApplyLut3D(TEXTURE3D_ARGS(_LogLut3D, sampler_LogLut3D), colorLutSpace, _LogLut3D_Params.xy); + colorLinear = lerp(colorLinear, colorLut, _LogLut3D_Params.z); + } + + return colorLinear; + } + + float3 ToHeat(float value) + { + float3 r = value * 2.1f - float3(1.8f, 1.14f, 0.3f); + return 1.0f - r * r; + } + + float GetEVAtLocation(float2 uv) + { + return ComputeEV100FromAvgLuminance(max(SampleLuminance(uv), 1e-4)); + } + + // Returns true if it drew the location of the indicator. + void DrawHeatSideBar(float2 uv, float2 startSidebar, float2 endSidebar, float evValueRange, float3 indicatorColor, float2 sidebarSize, float extremeMargin, inout float3 sidebarColor) + { + float2 extremesSize = float2(extremeMargin, 0); + float2 borderSize = 2 * _ScreenSize.zw * _RTHandleScale.xy; + int indicatorHalfSize = 5; + + + if (all(uv > startSidebar) && all(uv < endSidebar)) + { + float inRange = (uv.x - startSidebar.x) / (endSidebar.x - startSidebar.x); + evValueRange = clamp(evValueRange, 0.0f, 1.0f); + int distanceInPixels = abs(evValueRange - inRange) * sidebarSize.x * _ScreenSize.x; + if (distanceInPixels < indicatorHalfSize) + { + sidebarColor = indicatorColor; + } + else if (distanceInPixels < indicatorHalfSize + 1) + { + sidebarColor = 0.0f; + } + else + { + sidebarColor = ToHeat(inRange); + } + } + else if (all(uv > startSidebar - extremesSize) && all(uv < endSidebar)) + { + sidebarColor = float3(0,0,0); + } + else if (all(uv > startSidebar) && all(uv < endSidebar + extremesSize)) + { + sidebarColor = float3(1, 1, 1); + } + else if(all(uv > startSidebar - (extremesSize + borderSize)) && all(uv < endSidebar + (extremesSize + borderSize))) + { + sidebarColor = 0.0f; + } + } + + float GetHistogramValue(float coord, out bool isEdge) + { + float barSize = _ScreenSize.x / HISTOGRAM_BINS; + float bin = coord / barSize; + + float locWithinBin = barSize * frac(bin); + + isEdge = locWithinBin < 1 || locWithinBin > (barSize - 1); + return UnpackWeight(_HistogramBuffer[(uint)(bin)]); + } + + float ComputePercentile(float2 uv, float histSum, out float minPercentileBin, out float maxPercentileBin) + { + float sumBelowValue = 0.0f; + float sumForMin = 0.0f; + float sumForMax = 0.0f; + + minPercentileBin = -1; + maxPercentileBin = -1; + + float ev = GetEVAtLocation(uv); + + for (int i = 0; i < HISTOGRAM_BINS; ++i) + { + float evAtBin = BinLocationToEV(i); + float evAtNextBin = BinLocationToEV(i+1); + + float histVal = UnpackWeight(_HistogramBuffer[i]); + + if (ev >= evAtBin) + { + sumBelowValue += histVal; + } + + //TODO: This could be more precise, now it locks to bin location + if (minPercentileBin < 0) + { + sumForMin += histVal; + if (sumForMin / histSum >= _HistogramMinPercentile) + { + + minPercentileBin = i; + } + } + + if (maxPercentileBin < 0) + { + sumForMax += histVal; + if (sumForMax / histSum > _HistogramMaxPercentile) + { + maxPercentileBin = i; + } + } + } + + return sumBelowValue / histSum; + } + + void DrawHistogramIndicatorBar(float coord, float uvXLocation, float widthNDC, float3 color, inout float3 outColor) + { + float halfWidthInScreen = widthNDC * _ScreenSize.x; + float minScreenPos = (uvXLocation - widthNDC * 0.5) * _ScreenSize.x; + float maxScreenPos = (uvXLocation + widthNDC * 0.5) * _ScreenSize.x; + + if (coord > minScreenPos && coord < maxScreenPos) + { + outColor = color; + } + } + + void DrawTriangleIndicator(float2 coord, float labelBarHeight, float uvXLocation, float widthNDC, float3 color, inout float3 outColor) + { + float halfWidthInScreen = widthNDC * _ScreenSize.x; + float arrowStart = labelBarHeight * 0.4f; + + float heightInIndicator = ((coord.y - arrowStart) / (labelBarHeight - arrowStart)); + float indicatorWidth = 1.0f - heightInIndicator; + + float minScreenPos = (uvXLocation - widthNDC * indicatorWidth * 0.5) * _ScreenSize.x; + float maxScreenPos = (uvXLocation + widthNDC * indicatorWidth * 0.5) * _ScreenSize.x; + + uint triangleBorder = 2; + if (coord.x > minScreenPos && coord.x < maxScreenPos && coord.y >= arrowStart) + { + outColor = color; + } + else if (coord.x > minScreenPos - triangleBorder && coord.x < maxScreenPos + triangleBorder && coord.y > arrowStart - triangleBorder) + { + outColor = 0; + } + } + + void DrawHistogramFrame(float2 uv, uint2 unormCoord, float frameHeight, float3 backgroundColor, float alpha, float maxHist, float minPercentLoc, float maxPercentLoc, inout float3 outColor) + { + float2 borderSize = 2 * _ScreenSize.zw * _RTHandleScale.xy; + float heightLabelBar = (DEBUG_FONT_TEXT_WIDTH * 1.25) * _ScreenSize.w * _RTHandleScale.y; + + if (uv.y > frameHeight) return; + + // ---- Draw General frame ---- + if (uv.x < borderSize.x || uv.x >(1.0f - borderSize.x)) + { + outColor = 0.0; + return; + } + else if (uv.y > frameHeight - borderSize.y || uv.y < borderSize.y) + { + outColor = 0.0; + return; + } + else + { + outColor = lerp(outColor, backgroundColor, alpha); + } + + // ---- Draw label bar ----- + if (uv.y < heightLabelBar) + { + outColor = outColor * 0.075f; + } + + // ---- Draw Buckets frame ---- + + bool isEdgeOfBin = false; + float val = GetHistogramValue(unormCoord.x, isEdgeOfBin); + val /= maxHist; + + val *= 0.95*(frameHeight - heightLabelBar); + val += heightLabelBar; + + if (uv.y < val && uv.y > heightLabelBar) + { + isEdgeOfBin = isEdgeOfBin || (uv.y > val - _ScreenSize.w); +#if PERCENTILE_AS_BARS == 0 + uint bin = uint((unormCoord.x * (HISTOGRAM_BINS)) / (_ScreenSize.x)); + if (bin <= uint(minPercentLoc) && minPercentLoc > 0) + { + outColor.rgb = float3(0, 0, 1); + } + else if(bin >= uint(maxPercentLoc) && maxPercentLoc > 0) + { + outColor.rgb = float3(1, 0, 0); + } + else +#endif + outColor.rgb = float3(1.0f, 1.0f, 1.0f); + if (isEdgeOfBin) outColor.rgb = 0; + } + + // ---- Draw labels ---- + + // Number of labels + int labelCount = 12; + float oneOverLabelCount = rcp(labelCount); + float labelDeltaScreenSpace = _ScreenSize.x * oneOverLabelCount; + + int minLabelLocationX = DEBUG_FONT_TEXT_WIDTH * 0.25; + int maxLabelLocationX = _ScreenSize.x - (DEBUG_FONT_TEXT_WIDTH * 3); + + int labelLocationY = 0.0f; + + [unroll] + for (int i = 0; i <= labelCount; ++i) + { + float t = oneOverLabelCount * i; + float labelValue = lerp(ParamExposureLimitMin, ParamExposureLimitMax, t); + uint2 labelLoc = uint2((uint)lerp(minLabelLocationX, maxLabelLocationX, t), labelLocationY); + DrawFloatExplicitPrecision(labelValue, float3(1.0f, 1.0f, 1.0f), unormCoord, 1, labelLoc, outColor.rgb); + } + + // ---- Draw indicators ---- + float currExposure = _ExposureTexture[int2(0, 0)].y; + float targetExposure = _ExposureDebugTexture[int2(0, 0)].x; + + float evInRange = (currExposure - ParamExposureLimitMin) / (ParamExposureLimitMax - ParamExposureLimitMin); + float targetEVInRange = (targetExposure - ParamExposureLimitMin) / (ParamExposureLimitMax - ParamExposureLimitMin); + + float halfIndicatorSize = 0.007f; + float halfWidthInScreen = halfIndicatorSize * _ScreenSize.x; + + float labelFrameHeightScreen = heightLabelBar * (_ScreenSize.y / _RTHandleScale.y); + + if (uv.y < heightLabelBar) + { + DrawTriangleIndicator(float2(unormCoord.xy), labelFrameHeightScreen, targetEVInRange, halfIndicatorSize, float3(0.9f, 0.75f, 0.1f), outColor); + DrawTriangleIndicator(float2(unormCoord.xy), labelFrameHeightScreen, evInRange, halfIndicatorSize, float3(0.15f, 0.15f, 0.1f), outColor); + + // Find location for percentiles bars. +#if PERCENTILE_AS_BARS + DrawHistogramIndicatorBar(float(unormCoord.x), minPercentLoc, 0.003f, float3(0, 0, 1), outColor); + DrawHistogramIndicatorBar(float(unormCoord.x), maxPercentLoc, 0.003f, float3(1, 0, 0), outColor); +#endif + } + + // ---- Draw Tonemap curve ---- + if (_DrawTonemapCurve) + { + float exposureAtLoc = lerp(ParamExposureLimitMin, ParamExposureLimitMax, uv.x); + const float K = 12.5; // Reflected-light meter calibration constant + float luminanceFromExposure = _ExposureTexture[int2(0, 0)].x * (exp2(exposureAtLoc) * (K / 100.0f)); + + val = saturate(Luminance(Tonemap(luminanceFromExposure))); + val *= 0.95 * (frameHeight - heightLabelBar); + val += heightLabelBar; + + float curveWidth = 4 * _ScreenSize.w; + + if (uv.y < val && uv.y >(val - curveWidth)) + { + outColor = outColor * 0.1 + 0.9 * 0; + } + } + } + + float3 FragMetering(Varyings input) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = input.texcoord.xy; + float3 color = SAMPLE_TEXTURE2D_X_LOD(_DebugFullScreenTexture, s_linear_clamp_sampler, uv, 0.0).xyz; + float weight = WeightSample(input.positionCS.xy, _ScreenSize.xy); + + float pipFraction = 0.33f; + uint borderSize = 3; + float2 topRight = pipFraction * _ScreenSize.xy; + + if (all(input.positionCS.xy < topRight)) + { + float2 scaledUV = uv / pipFraction; + float3 pipColor = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, s_linear_clamp_sampler, scaledUV, 0.0).xyz; + float weight = WeightSample(scaledUV.xy * _ScreenSize.xy / _RTHandleScale.xy, _ScreenSize.xy); + + return pipColor * weight; + } + else if (all(input.positionCS.xy < (topRight + borderSize))) + { + return float3(0.33f, 0.33f, 0.33f); + } + else + { + return color; + } + } + + float3 FragSceneEV100(Varyings input) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = input.texcoord.xy; + + float3 textColor = 0.0f; + + float2 sidebarSize = float2(0.9, 0.02) * _RTHandleScale.xy; + + float heightLabelBar = (DEBUG_FONT_TEXT_WIDTH * 1.25f) * _ScreenSize.w * _RTHandleScale.y; + + float2 sidebarBottomLeft = float2(0.05 * _RTHandleScale.x, heightLabelBar); + float2 endPointSidebar = sidebarBottomLeft + sidebarSize; + + float3 outputColor = 0; + float ev = GetEVAtLocation(uv); + + float evInRange = (ev - ParamExposureLimitMin) / (ParamExposureLimitMax - ParamExposureLimitMin); + + if (ev < ParamExposureLimitMax && ev > ParamExposureLimitMin) + { + outputColor = ToHeat(evInRange); + } + else if (ev > ParamExposureLimitMax) + { + outputColor = 1.0f; + } + else if (ev < ParamExposureLimitMin) + { + outputColor = 0.0f; + } + + // Get value at indicator + float2 indicatorUV = _MousePixelCoord.zw; + float indicatorEV = GetEVAtLocation(indicatorUV); + float indicatorEVRange = (indicatorEV - ParamExposureLimitMin) / (ParamExposureLimitMax - ParamExposureLimitMin); + + float extremeMargin = 5 * _ScreenSize.z * _RTHandleScale.x; + DrawHeatSideBar(uv, sidebarBottomLeft, endPointSidebar, indicatorEVRange, 0.66f, sidebarSize, extremeMargin, outputColor); + + int2 unormCoord = input.positionCS.xy; + + // Label bar + float2 borderSize = 2 * _ScreenSize.zw * _RTHandleScale.xy; + if (uv.y < heightLabelBar && + uv.x >= (sidebarBottomLeft.x - borderSize.x) && uv.x <= (borderSize.x + endPointSidebar.x)) + { + outputColor = outputColor * 0.075f; + } + + // Number of labels + int labelCount = 8; + float oneOverLabelCount = rcp(labelCount); + float labelDeltaScreenSpace = _ScreenSize.x * oneOverLabelCount; + + int minLabelLocationX = (sidebarBottomLeft.x - borderSize.x) * (_ScreenSize.x / _RTHandleScale.x) + DEBUG_FONT_TEXT_WIDTH * 0.25; + int maxLabelLocationX = (borderSize.x + endPointSidebar.x) * (_ScreenSize.x / _RTHandleScale.x) - (DEBUG_FONT_TEXT_WIDTH * 3); + + int labelLocationY = 0.0f; + + [unroll] + for (int i = 0; i <= labelCount; ++i) + { + float t = oneOverLabelCount * i; + float labelValue = lerp(ParamExposureLimitMin, ParamExposureLimitMax, t); + uint2 labelLoc = uint2((uint)lerp(minLabelLocationX, maxLabelLocationX, t), labelLocationY); + DrawFloatExplicitPrecision(labelValue, float3(1.0f, 1.0f, 1.0f), unormCoord, 1, labelLoc, outputColor.rgb); + } + + int displayTextOffsetX = DEBUG_FONT_TEXT_WIDTH; + int2 textLocation = int2(_MousePixelCoord.x + displayTextOffsetX, _MousePixelCoord.y); + DrawFloatExplicitPrecision(indicatorEV, textColor, unormCoord, 1, textLocation, outputColor.rgb); + textLocation = _MousePixelCoord.xy; + DrawCharacter('X', float3(0.0f, 0.0f, 0.0f), unormCoord, textLocation, outputColor.rgb); + + return outputColor; + } + + + + float3 FragHistogram(Varyings input) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = input.texcoord.xy; + + float3 color = SAMPLE_TEXTURE2D_X_LOD(_DebugFullScreenTexture, s_linear_clamp_sampler, uv, 0.0).xyz; + float weight = WeightSample(input.positionCS.xy, _ScreenSize.xy); + + float3 outputColor = color; + + // Get some overall info from the histogram + float maxValue = 0; + float sum = 0; + for (int i = 0; i < HISTOGRAM_BINS; ++i) + { + float histogramVal = UnpackWeight(_HistogramBuffer[i]); + maxValue = max(histogramVal, maxValue); + sum += histogramVal; + } + + float minPercentileBin = 0; + float maxPercentileBin = 0; + float percentile = ComputePercentile(uv, sum, minPercentileBin, maxPercentileBin); + + if (percentile < _HistogramMinPercentile) + { + outputColor = (input.positionCS.x + input.positionCS.y) % 2 == 0 ? float3(0.0f, 0.0f, 1.0) : color*0.33; + } + if (percentile > _HistogramMaxPercentile) + { + outputColor = (input.positionCS.x + input.positionCS.y) % 2 == 0 ? float3(1.0, 0.0f, 0.0f) : color * 0.33; + } + + float histFrameHeight = 0.2 * _RTHandleScale.y; + float minPercentileLoc = max(minPercentileBin, 0); + float maxPercentileLoc = min(maxPercentileBin, HISTOGRAM_BINS - 1); +#if PERCENTILE_AS_BARS + minPercentileLoc /= (HISTOGRAM_BINS - 1); + maxPercentileLoc /= (HISTOGRAM_BINS - 1); +#endif + + DrawHistogramFrame(uv, input.positionCS.xy, histFrameHeight, float3(0.125,0.125,0.125), 0.4f, maxValue, minPercentileLoc, maxPercentileLoc, outputColor); + + + return outputColor; + } + + ENDHLSL + + SubShader + { + Tags{ "RenderPipeline" = "HDRenderPipeline" } + Pass + { + ZWrite Off + ZTest Always + Blend Off + Cull Off + + HLSLPROGRAM + #pragma fragment FragSceneEV100 + ENDHLSL + } + + Pass + { + ZWrite Off + ZTest Always + Blend Off + Cull Off + + HLSLPROGRAM + #pragma fragment FragMetering + ENDHLSL + } + + Pass + { + ZWrite Off + ZTest Always + Blend Off + Cull Off + + HLSLPROGRAM + #pragma fragment FragHistogram + ENDHLSL + } + + } + Fallback Off +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader.meta b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader.meta new file mode 100644 index 00000000000..4383c002901 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 0ef322534f047a34c96d29419d56d17a +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs b/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs index 2ca520cafff..846455a2694 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs @@ -168,6 +168,24 @@ public enum ShadowMapDebugMode SingleShadow, } + /// + /// Exposure debug mode. + /// + [GenerateHLSL] + public enum ExposureDebugMode + { + /// No exposure debug. + None, + /// Display the EV100 values of the scene, color-coded. + SceneEV100Values, + /// Display the Histogram used for exposure. + HistogramView, + /// Visualize the scene color weighted as the metering mode selected. + MeteringWeighted, + + } + + /// /// Probe Volume Debug Modes. /// @@ -291,8 +309,12 @@ public bool IsDebugDisplayEnabled() /// Maximum number of lights against which the light overdraw gradient is displayed. public uint maxDebugLightCount = 24; + /// Exposure debug mode. + public ExposureDebugMode exposureDebugMode = ExposureDebugMode.None; /// Exposure compensation to apply on current scene exposure. public float debugExposure = 0.0f; + /// Whether to show tonemap curve in the histogram debug view or not. + public bool showTonemapCurveAlongHistogramView = true; /// Display the light cookies atlas. public bool displayCookieAtlas = false; diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs.hlsl index 28338f1bab5..bc46e7e5126 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs.hlsl @@ -62,6 +62,14 @@ #define SHADOWMAPDEBUGMODE_VISUALIZE_SHADOW_MAP (4) #define SHADOWMAPDEBUGMODE_SINGLE_SHADOW (5) +// +// UnityEngine.Rendering.HighDefinition.ExposureDebugMode: static fields +// +#define EXPOSUREDEBUGMODE_NONE (0) +#define EXPOSUREDEBUGMODE_SCENE_EV100VALUES (1) +#define EXPOSUREDEBUGMODE_HISTOGRAM_VIEW (2) +#define EXPOSUREDEBUGMODE_METERING_WEIGHTED (3) + // // UnityEngine.Rendering.HighDefinition.ProbeVolumeDebugMode: static fields // diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Exposure.cs b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Exposure.cs index 0007c9c6076..7c942954e1e 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Exposure.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Exposure.cs @@ -86,11 +86,25 @@ public sealed class Exposure : VolumeComponent, IPostProcessComponent public MinFloatParameter adaptationSpeedLightToDark = new MinFloatParameter(1f, 0.001f); /// - /// Sets the texture mask used to weight the pixels in the buffer when computing exposure. Used only with . + /// Sets the texture mask used to weight the pixels in the buffer when computing exposure. /// - [Tooltip("Sets the texture mask to be used to weight the pixels in the buffer for the sake of computing exposure..")] + [Tooltip("Sets the texture mask to be used to weight the pixels in the buffer for the sake of computing exposure.")] public NoInterpTextureParameter weightTextureMask = new NoInterpTextureParameter(null); + /// + /// These values are the lower and upper percentages of the histogram that will be used to + /// find a stable average luminance. Values outside of this range will be discarded and won't + /// contribute to the average luminance. + /// + [Tooltip("Sets the range of values (in terms of percentages) of the histogram that are accepted while finding a stable average exposure. Anything outside the value is discarded.")] + public FloatRangeParameter histogramPercentages = new FloatRangeParameter(new Vector2(40.0f, 90.0f), 0.0f, 100.0f); + + /// + /// Sets whether histogram exposure mode will remap the computed exposure with a curve remapping (akin to Curve Remapping mode) + /// + [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); + /// /// Tells if the effect needs to be rendered or not. /// @@ -117,6 +131,11 @@ public enum ExposureMode /// Automatic, + /// + /// Automatically sets the exposure depending on what is on screen and can filter out outliers based on provided settings. + /// + AutomaticHistogram, + /// /// Maps the current Scene exposure to a custom curve. /// diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs index b103590431d..ef60f9e25e3 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs @@ -6,6 +6,7 @@ namespace UnityEngine.Rendering.HighDefinition /// Available tonemapping modes. /// /// + [GenerateHLSL] public enum TonemappingMode { /// diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl new file mode 100644 index 00000000000..01a14dbe92d --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl @@ -0,0 +1,17 @@ +// +// This file was automatically generated. Please don't edit by hand. +// + +#ifndef TONEMAPPING_CS_HLSL +#define TONEMAPPING_CS_HLSL +// +// UnityEngine.Rendering.HighDefinition.TonemappingMode: static fields +// +#define TONEMAPPINGMODE_NONE (0) +#define TONEMAPPINGMODE_NEUTRAL (1) +#define TONEMAPPINGMODE_ACES (2) +#define TONEMAPPINGMODE_CUSTOM (3) +#define TONEMAPPINGMODE_EXTERNAL (4) + + +#endif diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl.meta b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl.meta new file mode 100644 index 00000000000..dbc46634e4a --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Components/Tonemapping.cs.hlsl.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 3716aaa9cab64e5419f00d48a69fab04 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs index 24985b52709..dd7ce91f082 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs @@ -34,11 +34,15 @@ private enum SMAAStage // Exposure data const int k_ExposureCurvePrecision = 128; + const int k_HistogramBins = 128; // Important! If this changes, need to change HistogramExposure.compute readonly Color[] m_ExposureCurveColorArray = new Color[k_ExposureCurvePrecision]; readonly int[] m_ExposureVariants = new int[4]; Texture2D m_ExposureCurveTexture; RTHandle m_EmptyExposureTexture; // RGHalf + RTHandle m_DebugExposureData; + ComputeBuffer m_HistogramBuffer; + readonly int[] m_EmptyHistogram = new int[k_HistogramBins]; // Depth of field data ComputeBuffer m_BokehNearKernel; @@ -195,6 +199,11 @@ public PostProcessSystem(HDRenderPipelineAsset hdAsset, RenderPipelineResources m_EmptyExposureTexture = RTHandles.Alloc(1, 1, colorFormat: k_ExposureFormat, enableRandomWrite: true, name: "Empty EV100 Exposure"); + m_DebugExposureData = RTHandles.Alloc(1, 1, colorFormat: k_ExposureFormat, + enableRandomWrite: true, name: "Debug Exposure Info" + ); + + FillEmptyExposureTexture(); // Call after initializing m_LutSize and m_KeepAlpha as it's needed for render target allocation. @@ -220,6 +229,8 @@ public void Cleanup() CoreUtils.SafeRelease(m_NearBokehTileList); CoreUtils.SafeRelease(m_FarBokehTileList); CoreUtils.SafeRelease(m_ContrastAdaptiveSharpen); + CoreUtils.SafeRelease(m_HistogramBuffer); + RTHandles.Release(m_DebugExposureData); m_ExposureCurveTexture = null; m_InternalSpectralLut = null; @@ -232,6 +243,9 @@ public void Cleanup() m_BokehIndirectCmd = null; m_NearBokehTileList = null; m_FarBokehTileList = null; + m_HistogramBuffer = null; + m_DebugExposureData = null; + } public void InitializeNonRenderGraphResources(HDRenderPipelineAsset hdAsset) @@ -308,6 +322,7 @@ void CheckRenderTexturesValidity() if (!m_NonRenderGraphResourcesAvailable) return; + HDUtils.CheckRTCreated(m_DebugExposureData.rt); HDUtils.CheckRTCreated(m_InternalLogLut.rt); HDUtils.CheckRTCreated(m_TempTexture1024.rt); HDUtils.CheckRTCreated(m_TempTexture32.rt); @@ -480,7 +495,14 @@ void PoolSource(ref RTHandle src, RTHandle dst) { using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.DynamicExposure))) { - DoDynamicExposure(cmd, camera, source); + if (m_Exposure.mode.value == ExposureMode.AutomaticHistogram) + { + DoHistogramBasedExposure(cmd, camera, source); + } + else + { + DoDynamicExposure(cmd, camera, source); + } // On reset history we need to apply dynamic exposure immediately to avoid // white or black screen flashes when the current exposure isn't anywhere @@ -797,6 +819,26 @@ public RTHandle GetPreviousExposureTexture(HDCamera camera) return rt ?? m_EmptyExposureTexture; } + internal RTHandle GetExposureDebugData() + { + return m_DebugExposureData; + } + + internal HableCurve GetCustomToneMapCurve() + { + return m_HableCurve; + } + + internal int GetLutSize() + { + return m_LutSize; + } + + internal ComputeBuffer GetHistogramBuffer() + { + return m_HistogramBuffer; + } + void DoFixedExposure(CommandBuffer cmd, HDCamera camera) { var cs = m_Resources.shaders.exposureCS; @@ -875,12 +917,9 @@ void PrepareExposureCurveData(AnimationCurve curve, out float min, out float max m_ExposureCurveTexture.Apply(); } - void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer) + void DynamicExposureSetup(CommandBuffer cmd, HDCamera camera, out RTHandle prevExposure, out RTHandle nextExposure) { - var cs = m_Resources.shaders.exposureCS; - int kernel; - - GrabExposureHistoryTextures(camera, out var prevExposure, out var nextExposure); + GrabExposureHistoryTextures(camera, out prevExposure, out nextExposure); // Setup variants var adaptationMode = m_Exposure.adaptationMode.value; @@ -899,6 +938,14 @@ void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer) m_ExposureVariants[1] = (int)m_Exposure.meteringMode.value; m_ExposureVariants[2] = (int)adaptationMode; m_ExposureVariants[3] = 0; + } + + void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer) + { + var cs = m_Resources.shaders.exposureCS; + int kernel; + + DynamicExposureSetup(cmd, camera, out var prevExposure, out var nextExposure); var sourceTex = colorBuffer; @@ -936,6 +983,8 @@ 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)); + m_ExposureVariants[3] = 2; } @@ -947,6 +996,75 @@ void DoDynamicExposure(CommandBuffer cmd, HDCamera camera, RTHandle colorBuffer) cmd.DispatchCompute(cs, kernel, 1, 1, 1); } + void DoHistogramBasedExposure(CommandBuffer cmd, HDCamera camera, RTHandle sourceTexture) + { + var cs = m_Resources.shaders.histogramExposureCS; + cs.shaderKeywords = null; + int kernel; + + DynamicExposureSetup(cmd, camera, out var prevExposure, out var nextExposure); + // Parameters + Vector2 histogramFraction = m_Exposure.histogramPercentages.value / 100.0f; + float evRange = m_Exposure.limitMax.value - m_Exposure.limitMin.value; + float histScale = 1.0f / Mathf.Max(1e-5f, evRange); + float histBias = -m_Exposure.limitMin.value * histScale; + Vector4 histogramParams = new Vector4(histScale, histBias, histogramFraction.x, histogramFraction.y); + + ValidateComputeBuffer(ref m_HistogramBuffer, k_HistogramBins, sizeof(uint)); + m_HistogramBuffer.SetData(m_EmptyHistogram); // Clear the histogram + cmd.SetComputeVectorParam(cs, HDShaderIDs._HistogramExposureParams, histogramParams); + + // Generate histogram. + kernel = cs.FindKernel("KHistogramGen"); + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._PreviousExposureTexture, prevExposure); + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._SourceTexture, sourceTexture); + if (m_Exposure.meteringMode == MeteringMode.MaskWeighted && m_Exposure.weightTextureMask.value != null) + { + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureWeightMask, m_Exposure.weightTextureMask.value); + } + else + { + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureWeightMask, Texture2D.whiteTexture); + } + + cmd.SetComputeIntParams(cs, HDShaderIDs._Variants, m_ExposureVariants); + + cmd.SetComputeBufferParam(cs, kernel, HDShaderIDs._HistogramBuffer, m_HistogramBuffer); + + int threadGroupSizeX = 16; + int threadGroupSizeY = 8; + int dispatchSizeX = HDUtils.DivRoundUp(camera.actualWidth / 2, threadGroupSizeX); + int dispatchSizeY = HDUtils.DivRoundUp(camera.actualHeight / 2, threadGroupSizeY); + int totalPixels = camera.actualWidth * camera.actualHeight; + cmd.DispatchCompute(cs, kernel, dispatchSizeX, dispatchSizeY, 1); + + // 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._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); + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._OutputTexture, nextExposure); + + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureCurveTexture, m_ExposureCurveTexture); + m_ExposureVariants[3] = 0; + 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)); + m_ExposureVariants[3] = 2; + } + cmd.SetComputeIntParams(cs, HDShaderIDs._Variants, m_ExposureVariants); + + if (m_HDInstance.m_CurrentDebugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.HistogramView) + { + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._ExposureDebugTexture, m_DebugExposureData); + cs.EnableKeyword("OUTPUT_DEBUG_DATA"); + } + + cmd.DispatchCompute(cs, kernel, 1, 1, 1); + } + #endregion #region Temporal Anti-aliasing diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/Exposure.compute b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/Exposure.compute index 24bb51c8249..4e53910f142 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/Exposure.compute +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/Exposure.compute @@ -1,7 +1,4 @@ -#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" -#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" -#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/PhysicalCamera.hlsl" -#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" +#include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl" #pragma only_renderers d3d11 playstation xboxone vulkan metal switch @@ -11,99 +8,11 @@ #pragma kernel KReduction #pragma kernel KReset -TEXTURE2D(_ExposureCurveTexture); -TEXTURE2D(_PreviousExposureTexture); TEXTURE2D(_InputTexture); -TEXTURE2D(_ExposureWeightMask); - -TEXTURE2D_X(_SourceTexture); - -RW_TEXTURE2D(float2, _OutputTexture); - -SAMPLER(sampler_LinearClamp); - -CBUFFER_START(cb) - float4 _ExposureParams; - float4 _AdaptationParams; - uint4 _Variants; -CBUFFER_END - -#define ParamEV100 _ExposureParams.y -#define ParamExposureCompensation _ExposureParams.x -#define ParamAperture _ExposureParams.y -#define ParamShutterSpeed _ExposureParams.z -#define ParamISO _ExposureParams.w -#define ParamSpeedLightToDark _AdaptationParams.x -#define ParamSpeedDarkToLight _AdaptationParams.y -#define ParamExposureLimitMin _ExposureParams.y -#define ParamExposureLimitMax _ExposureParams.z -#define ParamCurveMin _ExposureParams.y -#define ParamCurveMax _ExposureParams.z -#define ParamSourceBuffer _Variants.x -#define ParamMeteringMode _Variants.y -#define ParamAdaptationMode _Variants.z -#define ParamEvaluateMode _Variants.w - #define PREPASS_TEX_SIZE 1024.0 #define PREPASS_TEX_HALF_SIZE 512.0 -float WeightSample(uint2 pixel) -{ - UNITY_BRANCH - switch (ParamMeteringMode) - { - case 1u: - { - // Spot metering - const float kRadius = 0.075 * PREPASS_TEX_SIZE; - const float2 kCenter = (PREPASS_TEX_HALF_SIZE).xx; - float d = length(kCenter - pixel) - kRadius; - return 1.0 - saturate(d); - } - case 2u: - { - // Center-weighted - const float2 kCenter = (PREPASS_TEX_HALF_SIZE).xx; - return 1.0 - saturate(pow(length(kCenter - pixel) / PREPASS_TEX_HALF_SIZE, 1.0)); - } - case 3u: - { - // Mask weigthing - return SAMPLE_TEXTURE2D_LOD(_ExposureWeightMask, sampler_LinearClamp, pixel * rcp(PREPASS_TEX_SIZE), 0.0).x; - } - - default: - { - // Global average - return 1.0; - } - } -} - -float GetPreviousExposureEV100() -{ - return _PreviousExposureTexture[uint2(0u, 0u)].y; -} - -float AdaptExposure(float exposure) -{ - UNITY_BRANCH - switch (ParamAdaptationMode) - { - case 1u: - { - // Progressive - return ComputeLuminanceAdaptation(GetPreviousExposureEV100(), exposure, ParamSpeedDarkToLight, ParamSpeedLightToDark, unity_DeltaTime.x); - } - default: - { - // Fixed - return exposure; - } - } -} - // // Fixed exposure // Doesn't do anything fancy, simply copies the exposure & clamp values set in the volume system @@ -140,28 +49,10 @@ void KPrePass(uint2 dispatchThreadId : SV_DispatchThreadID) PositionInputs posInputs = GetPositionInput(float2(dispatchThreadId), rcp(PREPASS_TEX_SIZE), uint2(8u, 8u)); float2 uv = ClampAndScaleUVForBilinear(posInputs.positionNDC); - float luma; + float luma = SampleLuminance(uv); - UNITY_BRANCH - switch (ParamSourceBuffer) - { - case 1u: - { - // Color buffer - float prevExposure = ConvertEV100ToExposure(GetPreviousExposureEV100()); - float3 color = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, sampler_LinearClamp, uv, 0.0).xyz; - luma = Luminance(color / prevExposure); - break; - } - default: - { - // Lighting buffer - luma = 1.0; - break; - } - } + float weight = WeightSample(dispatchThreadId, PREPASS_TEX_SIZE.xx); - float weight = WeightSample(dispatchThreadId); float logLuma = ComputeEV100FromAvgLuminance(max(luma, 1e-4)); _OutputTexture[posInputs.positionSS] = float2(logLuma, weight); } @@ -235,8 +126,7 @@ void KReduction(uint2 groupId : SV_GroupID, uint2 groupThreadId : SV_GroupThread case 2u: { // Curve remapping - float remap = saturate((avgLuminance - ParamCurveMin) / (ParamCurveMax - ParamCurveMin)); - float exposure = SAMPLE_TEXTURE2D_LOD(_ExposureCurveTexture, sampler_LinearClamp, float2(remap, 0.0), 0.0).x; + float exposure = CurveRemap(avgLuminance); exposure = AdaptExposure(exposure - ParamExposureCompensation); _OutputTexture[groupId.xy] = float2(ConvertEV100ToExposure(exposure), exposure); break; diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl new file mode 100644 index 00000000000..f12efaea4f9 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl @@ -0,0 +1,108 @@ +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/PhysicalCamera.hlsl" +#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" + + +TEXTURE2D(_ExposureWeightMask); +TEXTURE2D_X(_SourceTexture); +TEXTURE2D(_PreviousExposureTexture); +RW_TEXTURE2D(float2, _OutputTexture); +TEXTURE2D(_ExposureCurveTexture); + +CBUFFER_START(cb) +float4 _ExposureParams; +float4 _ExposureParams2; +float4 _HistogramExposureParams; +float4 _AdaptationParams; +uint4 _Variants; +CBUFFER_END + +#define ParamEV100 _ExposureParams.y +#define ParamExposureCompensation _ExposureParams.x +#define ParamAperture _ExposureParams.y +#define ParamShutterSpeed _ExposureParams.z +#define ParamISO _ExposureParams.w +#define ParamSpeedLightToDark _AdaptationParams.x +#define ParamSpeedDarkToLight _AdaptationParams.y +#define ParamExposureLimitMin _ExposureParams.y +#define ParamExposureLimitMax _ExposureParams.z +#define ParamCurveMin _ExposureParams2.x +#define ParamCurveMax _ExposureParams2.y +#define ParamSourceBuffer _Variants.x +#define ParamMeteringMode _Variants.y +#define ParamAdaptationMode _Variants.z +#define ParamEvaluateMode _Variants.w + +float GetPreviousExposureEV100() +{ + return _PreviousExposureTexture[uint2(0u, 0u)].y; +} + +float WeightSample(uint2 pixel, float2 sourceSize) +{ + UNITY_BRANCH + switch (ParamMeteringMode) + { + case 1u: + { + // Spot metering + float screenDiagonal = 0.5f * (sourceSize.x + sourceSize.y); + const float kRadius = 0.075 * screenDiagonal; + const float2 kCenter = sourceSize * 0.5f; + float d = length(kCenter - pixel) - kRadius; + return 1.0 - saturate(d); + } + case 2u: + { + // Center-weighted + float screenDiagonal = 0.5f * (sourceSize.x + sourceSize.y); + const float2 kCenter = sourceSize * 0.5f; + return 1.0 - saturate(pow(length(kCenter - pixel) / screenDiagonal, 1.0)); + } + case 3u: + { + // Mask weigthing + return SAMPLE_TEXTURE2D_LOD(_ExposureWeightMask, s_linear_clamp_sampler, pixel * rcp(sourceSize), 0.0).x; + } + + default: + { + // Global average + return 1.0; + } + } +} + +float SampleLuminance(float2 uv) +{ + if (ParamSourceBuffer == 1) + { + // Color buffer + float prevExposure = ConvertEV100ToExposure(GetPreviousExposureEV100()); + float3 color = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, s_linear_clamp_sampler, uv, 0.0).xyz; + return Luminance(color / prevExposure); + } + else + { + return 1.0f; + } +} + +float AdaptExposure(float exposure) +{ + if (ParamAdaptationMode == 1) + { + return ComputeLuminanceAdaptation(GetPreviousExposureEV100(), exposure, ParamSpeedDarkToLight, ParamSpeedLightToDark, unity_DeltaTime.x); + } + else + { + return exposure; + } +} + +float CurveRemap(float inEV) +{ + float remap = saturate((inEV - ParamCurveMin) / (ParamCurveMax - ParamCurveMin)); + return SAMPLE_TEXTURE2D_LOD(_ExposureCurveTexture, s_linear_clamp_sampler, float2(remap, 0.0), 0.0).x; +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl.meta b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl.meta new file mode 100644 index 00000000000..fd95688bcc3 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/ExposureCommon.hlsl.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: cb6a39236cce6824cbf70aa0d69f39f6 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute new file mode 100644 index 00000000000..43e5ccbdc54 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute @@ -0,0 +1,183 @@ +#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" + + +// TODO List to investigate +// - Worth considering multiple histograms per lane in the thread. (i.e. sharedHisto[BINS][NUMB_HIST] ) +// - At the moment the dispatch is at half res, but the buffer sampled is full res, +// causing fairly bad cache behaviour. Can we use the mip chain realistically without issues? [The one we have is blurred and might be incomplete?] + +#pragma kernel KHistogramGen GEN_PASS +#pragma kernel KHistogramReduce REDUCE_PASS +#define GROUP_SIZE_X 16 +#define GROUP_SIZE_Y 8 + +#pragma multi_compile _ OUTPUT_DEBUG_DATA + +// Because atomics are only on uint and we need a weighted value, we need to convert. +// If we multiply the weight by 2048, we get somewhat ok precision and we support up to +// the equivalent of 1920x1080 image in one bin. (Note, we run this at half res, so equivalent of 4k image) +uint PackWeight(float weight) +{ + return uint(weight * 2048); +} + +groupshared uint gs_localHistogram[HISTOGRAM_BINS]; + + +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)] +void KHistogramGen(uint groupIndex : SV_GroupIndex, + uint3 dispatchThreadId : SV_DispatchThreadID) +{ + // Groupshared memory is not guaranteed to be 0 initialized. + // Note that currently the branch is always true (GROUP_SIZE_X * GROUP_SIZE_Y == HISTOGRAM_BINS). Here as safeguard if changing group size or bins. + if (groupIndex < HISTOGRAM_BINS) + { + gs_localHistogram[groupIndex] = 0u; + } + + GroupMemoryBarrierWithGroupSync(); + + // TODO: This leads to poor cache behaviour, verify if we can use lower mip of the color pyramid. + uint2 fullResCoords = dispatchThreadId.xy << 1u; + + if (all(fullResCoords < uint2(_ScreenSize.xy))) + { + float2 uv = ClampAndScaleUVForBilinear((fullResCoords + 0.5) * _ScreenSize.zw); + float luminance = SampleLuminance(uv); + float weight = WeightSample(fullResCoords, _ScreenSize.xy); + + uint bin = GetHistogramBinLocation(luminance); + InterlockedAdd(gs_localHistogram[bin], PackWeight(weight)); + } + + GroupMemoryBarrierWithGroupSync(); + + // Note that currently the branch is always true (GROUP_SIZE_X * GROUP_SIZE_Y == HISTOGRAM_BINS). Here as safeguard if changing group size or bins. + if (groupIndex < HISTOGRAM_BINS) + { + InterlockedAdd(_HistogramBuffer[groupIndex], gs_localHistogram[groupIndex]); + } +} + +#define USE_WAVE_INTRINSICS defined(PLATFORM_LANE_COUNT) && defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) + + +#if USE_WAVE_INTRINSICS + +#define WAVE_SIZE PLATFORM_LANE_COUNT +#define SUM_SCRATCH_SIZE HISTOGRAM_BINS / WAVE_SIZE + +#else + +#define SUM_SCRATCH_SIZE HISTOGRAM_BINS + +#endif + +groupshared float gs_partialSums[SUM_SCRATCH_SIZE]; +groupshared float gs_values[HISTOGRAM_BINS]; + +float ComputeTotalSum(uint threadID, float threadVal) +{ + float sum = 0; + +#if USE_WAVE_INTRINSICS + + uint waveCount = (HISTOGRAM_BINS / WAVE_SIZE); + float waveSum = WaveActiveSum(threadVal); + + uint waveIDInGroup = threadID / WAVE_SIZE; + if (WaveIsFirstLane()) + { + gs_partialSums[waveIDInGroup] = waveSum; + } + + // We have values for all the waves, let's sync. + GroupMemoryBarrierWithGroupSync(); + + sum = gs_partialSums[0]; + for (uint i = 1u; i < waveCount; ++i) + { + sum += gs_partialSums[i]; + } + +#else // !USE_WAVE_INTRINSICS + + gs_partialSums[threadID] = threadVal; + + GroupMemoryBarrierWithGroupSync(); + + // Sum all values + for (uint i = HISTOGRAM_BINS >> 1u; i > 0u; i >>= 1u) + { + if (threadID < i) + gs_partialSums[threadID] = (gs_partialSums[threadID] + gs_partialSums[threadID + i]); + + GroupMemoryBarrierWithGroupSync(); + } + + sum = gs_partialSums[0]; + +#endif + + return sum; +} + +void ProcessBin(uint binIndex, inout float2 extremesSums, inout float evSum, inout float totalWeight) +{ + float histVal = gs_values[binIndex]; + float binEV = BinLocationToEV(binIndex); + + // Shadows + float off = min(extremesSums.x, histVal); + extremesSums -= off; + histVal -= off; + // Highlights + histVal = min(extremesSums.y, histVal); + extremesSums.y -= histVal; + + evSum += histVal * binEV; + totalWeight += histVal; +} + +[numthreads(HISTOGRAM_BINS, 1, 1)] +void KHistogramReduce(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + uint threadID = dispatchThreadId.x; + float histogramVal = UnpackWeight(_HistogramBuffer[threadID]); + + gs_values[threadID] = histogramVal; + + float sum = ComputeTotalSum(threadID, histogramVal); + + float2 extremesSums = float2(_HistogramMinPercentile, _HistogramMaxPercentile) * sum; + + // TODO: Can we be a bit more parallel here? + if (threadID == 0) + { + float evProcessedSum = 0; + float w = 0; + + for (int i = 0; i < HISTOGRAM_BINS; ++i) + { + ProcessBin(i, extremesSums, evProcessedSum, w); + } + + w = max(w, 1e-4f); + float avgEV = evProcessedSum * rcp(w); + + if (ParamEvaluateMode == 2) + { + avgEV = CurveRemap(avgEV); + } + + float exposure = AdaptExposure(avgEV - ParamExposureCompensation); + exposure = clamp(exposure, ParamExposureLimitMin, ParamExposureLimitMax); + _OutputTexture[uint2(0, 0)] = float2(ConvertEV100ToExposure(exposure), exposure); +#ifdef OUTPUT_DEBUG_DATA + _ExposureDebugTexture[uint2(0, 0)] = float2(avgEV - ParamExposureCompensation, 0.0f); +#endif + } + + +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute.meta b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute.meta new file mode 100644 index 00000000000..ec4511dfb0c --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposure.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 222da48299136f34b8e3fb75ae9f8ac7 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl new file mode 100644 index 00000000000..7620df3ec91 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl @@ -0,0 +1,39 @@ + +#define HISTOGRAM_BINS 128 + +#define _HistogramRangeScale _HistogramExposureParams.x +#define _HistogramRangeBias _HistogramExposureParams.y +#define _HistogramMinPercentile _HistogramExposureParams.z +#define _HistogramMaxPercentile _HistogramExposureParams.w + +#ifdef GEN_PASS +RWStructuredBuffer _HistogramBuffer; +#else +StructuredBuffer _HistogramBuffer; +#endif + +#ifdef OUTPUT_DEBUG_DATA +RW_TEXTURE2D(float2, _ExposureDebugTexture); +#else +TEXTURE2D(_ExposureDebugTexture); +#endif + +float UnpackWeight(uint val) +{ + return val * rcp(2048.0f); +} + +float GetFractionWithinHistogram(float value) +{ + return ComputeEV100FromAvgLuminance(value) * _HistogramRangeScale + _HistogramRangeBias; +} + +uint GetHistogramBinLocation(float value) +{ + return uint(saturate(GetFractionWithinHistogram(value)) * (HISTOGRAM_BINS - 1)); +} + +float BinLocationToEV(uint binIdx) +{ + return (binIdx * rcp(float(HISTOGRAM_BINS - 1)) - _HistogramRangeBias) / _HistogramRangeScale; +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl.meta b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl.meta new file mode 100644 index 00000000000..11a0b6748c6 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/HistogramExposureCommon.hlsl.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 8b66a6c34796d04498345e5530eb228c +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.Debug.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.Debug.cs index fe353879da5..e134caf8d8c 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.Debug.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.Debug.cs @@ -194,6 +194,14 @@ TextureHandle RenderDebug( RenderGraph renderGraph, m_FullScreenDebugPushed = false; } + // TODO RENDERGRAPH (Needs post processing in Rendergraph to properly be implemented) + if(debugParameters.exposureDebugEnabled) + { + // For reference the following is what is called in the non-render-graph version. + // RenderExposureDebug(debugParams, m_CameraColorBuffer, m_DebugFullScreenTempBuffer,m_PostProcessSystem.GetPreviousExposureTexture(hdCamera), m_PostProcessSystem.GetExposureTexture(hdCamera), + // m_PostProcessSystem.GetExposureDebugData(),m_IntermediateAfterPostProcessBuffer, m_PostProcessSystem.GetCustomToneMapCurve(), m_PostProcessSystem.GetLutSize(), m_PostProcessSystem.GetHistogramBuffer(), cmd); + } + if (debugParameters.colorPickerEnabled) output = ResolveColorPickerDebug(renderGraph, debugParameters, colorPickerDebugTexture); diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs index 82f45408e04..41ec5bebcbf 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs @@ -154,6 +154,7 @@ internal static Volume GetOrCreateDefaultVolume() Material m_DebugFullScreen; MaterialPropertyBlock m_DebugFullScreenPropertyBlock = new MaterialPropertyBlock(); Material m_DebugColorPicker; + Material m_DebugExposure; Material m_ErrorMaterial; Material m_Blit; @@ -947,6 +948,7 @@ void InitializeDebugMaterials() m_DebugDisplayLatlong = CoreUtils.CreateEngineMaterial(defaultResources.shaders.debugDisplayLatlongPS); m_DebugFullScreen = CoreUtils.CreateEngineMaterial(defaultResources.shaders.debugFullScreenPS); m_DebugColorPicker = CoreUtils.CreateEngineMaterial(defaultResources.shaders.debugColorPickerPS); + m_DebugExposure = CoreUtils.CreateEngineMaterial(defaultResources.shaders.debugExposurePS); m_Blit = CoreUtils.CreateEngineMaterial(defaultResources.shaders.blitPS); m_ErrorMaterial = CoreUtils.CreateEngineMaterial("Hidden/InternalErrorShader"); @@ -1032,6 +1034,7 @@ protected override void Dispose(bool disposing) CoreUtils.Destroy(m_DebugDisplayLatlong); CoreUtils.Destroy(m_DebugFullScreen); CoreUtils.Destroy(m_DebugColorPicker); + CoreUtils.Destroy(m_DebugExposure); CoreUtils.Destroy(m_Blit); CoreUtils.Destroy(m_BlitTexArray); CoreUtils.Destroy(m_BlitTexArraySingleSlice); @@ -2712,6 +2715,8 @@ void Callback(CommandBuffer c, HDCamera cam) RenderTargetIdentifier postProcessDest = HDUtils.PostProcessIsFinalPass(hdCamera) ? target.id : m_IntermediateAfterPostProcessBuffer; RenderPostProcess(cullingResults, hdCamera, postProcessDest, renderContext, cmd); + PushFullScreenExposureDebugTexture(cmd, m_IntermediateAfterPostProcessBuffer); + RenderCustomPass(renderContext, cmd, hdCamera, customPassCullingResults, CustomPassInjectionPoint.AfterPostProcess, aovRequest, aovCustomPassBuffers); // Copy and rescale depth buffer for XR devices @@ -4442,7 +4447,8 @@ unsafe void ApplyDebugDisplaySettings(HDCamera hdCamera, CommandBuffer cmd) cmd.SetGlobalTexture(HDShaderIDs._DebugMatCapTexture, defaultResources.textures.matcapTex); if (debugDisplayEnabledOrSceneLightingDisabled || - m_CurrentDebugDisplaySettings.data.colorPickerDebugSettings.colorPickerMode != ColorPickerDebugMode.None) + m_CurrentDebugDisplaySettings.data.colorPickerDebugSettings.colorPickerMode != ColorPickerDebugMode.None || + m_CurrentDebugDisplaySettings.IsDebugExposureModeEnabled()) { // This is for texture streaming m_CurrentDebugDisplaySettings.UpdateMaterials(); @@ -4531,6 +4537,11 @@ void PushColorPickerDebugTexture(CommandBuffer cmd, HDCamera hdCamera, RTHandle } } + bool NeedExposureDebugMode(DebugDisplaySettings debugSettings) + { + return debugSettings.data.lightingDebugSettings.exposureDebugMode != ExposureDebugMode.None; + } + bool NeedsFullScreenDebugMode() { bool fullScreenDebugEnabled = m_CurrentDebugDisplaySettings.data.fullScreenDebugMode != FullScreenDebugMode.None; @@ -4550,6 +4561,14 @@ void PushFullScreenLightingDebugTexture(HDCamera hdCamera, CommandBuffer cmd, RT } } + void PushFullScreenExposureDebugTexture(CommandBuffer cmd, RTHandle textureID) + { + if (m_CurrentDebugDisplaySettings.data.lightingDebugSettings.exposureDebugMode != ExposureDebugMode.None) + { + HDUtils.BlitCameraTexture(cmd, textureID, m_DebugFullScreenTempBuffer); + } + } + internal void PushFullScreenDebugTexture(HDCamera hdCamera, CommandBuffer cmd, RTHandle textureID, FullScreenDebugMode debugMode) { if (debugMode == m_CurrentDebugDisplaySettings.data.fullScreenDebugMode) @@ -4594,6 +4613,10 @@ struct DebugParameters // Color picker public bool colorPickerEnabled; public Material colorPickerMaterial; + + // Exposure + public bool exposureDebugEnabled; + public Material debugExposureMaterial; } DebugParameters PrepareDebugParameters(HDCamera hdCamera, HDUtils.PackedMipChainInfo depthMipInfo) @@ -4618,6 +4641,9 @@ DebugParameters PrepareDebugParameters(HDCamera hdCamera, HDUtils.PackedMipChain parameters.colorPickerEnabled = NeedColorPickerDebug(parameters.debugDisplaySettings); parameters.colorPickerMaterial = m_DebugColorPicker; + parameters.exposureDebugEnabled = NeedExposureDebugMode(parameters.debugDisplaySettings); + parameters.debugExposureMaterial = m_DebugExposure; + return parameters; } @@ -4665,6 +4691,82 @@ static void ResolveColorPickerDebug(in DebugParameters parameters, HDUtils.DrawFullScreen(cmd, parameters.colorPickerMaterial, output); } + static void RenderExposureDebug(in DebugParameters parameters, + RTHandle inputColorBuffer, + RTHandle postprocessedColorBuffer, + RTHandle currentExposure, + RTHandle prevExposure, + RTHandle debugExposureData, + RTHandle output, + HableCurve hableCurve, + int lutSize, + ComputeBuffer histogramBuffer, + CommandBuffer cmd) + { + // Grab exposure parameters + var exposureSettings = parameters.hdCamera.volumeStack.GetComponent(); + + Vector4 exposureParams = new Vector4(exposureSettings.compensation.value + parameters.debugDisplaySettings.data.lightingDebugSettings.debugExposure, exposureSettings.limitMin.value, + exposureSettings.limitMax.value, 0f); + + Vector4 exposureVariants = new Vector4(1.0f, (int)exposureSettings.meteringMode.value, (int)exposureSettings.adaptationMode.value, 0.0f); + Vector2 histogramFraction = exposureSettings.histogramPercentages.value / 100.0f; + float evRange = exposureSettings.limitMax.value - exposureSettings.limitMin.value; + float histScale = 1.0f / Mathf.Max(1e-5f, evRange); + float histBias = -exposureSettings.limitMin.value * histScale; + Vector4 histogramParams = new Vector4(histScale, histBias, histogramFraction.x, histogramFraction.y); + + parameters.debugExposureMaterial.SetVector(HDShaderIDs._HistogramExposureParams, histogramParams); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._Variants, exposureVariants); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ExposureParams, exposureParams); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._MousePixelCoord, HDUtils.GetMouseCoordinates(parameters.hdCamera)); + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._SourceTexture, inputColorBuffer); + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._DebugFullScreenTexture, postprocessedColorBuffer); + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._PreviousExposureTexture, prevExposure); + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._ExposureTexture, currentExposure); + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._ExposureWeightMask, exposureSettings.weightTextureMask.value); + parameters.debugExposureMaterial.SetBuffer(HDShaderIDs._HistogramBuffer, histogramBuffer); + + + int passIndex = 0; + if (parameters.debugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.MeteringWeighted) + passIndex = 1; + if (parameters.debugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.HistogramView) + { + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._ExposureDebugTexture, debugExposureData); + var tonemappingSettings = parameters.hdCamera.volumeStack.GetComponent(); + + bool toneMapIsEnabled = parameters.hdCamera.frameSettings.IsEnabled(FrameSettingsField.Tonemapping); + var tonemappingMode = toneMapIsEnabled ? tonemappingSettings.mode.value : TonemappingMode.None; + + bool drawTonemapCurve = tonemappingMode != TonemappingMode.None && + parameters.debugDisplaySettings.data.lightingDebugSettings.showTonemapCurveAlongHistogramView; + + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ExposureDebugParams, new Vector4(drawTonemapCurve ? 1.0f : 0.0f, (int)tonemappingMode, 0, 0)); + if (drawTonemapCurve) + { + if (tonemappingMode == TonemappingMode.Custom) + { + parameters.debugExposureMaterial.SetVector(HDShaderIDs._CustomToneCurve, hableCurve.uniforms.curve); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ToeSegmentA, hableCurve.uniforms.toeSegmentA); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ToeSegmentB, hableCurve.uniforms.toeSegmentB); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._MidSegmentA, hableCurve.uniforms.midSegmentA); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._MidSegmentB, hableCurve.uniforms.midSegmentB); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ShoSegmentA, hableCurve.uniforms.shoSegmentA); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ShoSegmentB, hableCurve.uniforms.shoSegmentB); + } + } + else if (tonemappingMode == TonemappingMode.External) + { + parameters.debugExposureMaterial.SetTexture(HDShaderIDs._LogLut3D, tonemappingSettings.lutTexture.value); + parameters.debugExposureMaterial.SetVector(HDShaderIDs._LogLut3D_Params, new Vector4(1f / lutSize, lutSize - 1f, tonemappingSettings.lutContribution.value, 0f)); + } + passIndex = 2; + } + + HDUtils.DrawFullScreen(cmd, parameters.debugExposureMaterial, output, null, passIndex); + } + static void RenderSkyReflectionOverlay(in DebugParameters debugParameters, CommandBuffer cmd, MaterialPropertyBlock mpb, ref float x, ref float y, float overlaySize) { var lightingDebug = debugParameters.debugDisplaySettings.data.lightingDebugSettings; @@ -4707,6 +4809,12 @@ void RenderDebug(HDCamera hdCamera, CommandBuffer cmd, CullingResults cullResult PushColorPickerDebugTexture(cmd, hdCamera, m_IntermediateAfterPostProcessBuffer); } + if (debugParams.exposureDebugEnabled) + { + RenderExposureDebug(debugParams, m_CameraColorBuffer, m_DebugFullScreenTempBuffer,m_PostProcessSystem.GetPreviousExposureTexture(hdCamera), m_PostProcessSystem.GetExposureTexture(hdCamera), + m_PostProcessSystem.GetExposureDebugData(),m_IntermediateAfterPostProcessBuffer, m_PostProcessSystem.GetCustomToneMapCurve(), m_PostProcessSystem.GetLutSize(), m_PostProcessSystem.GetHistogramBuffer(), cmd); + } + // First resolve color picker if (debugParams.colorPickerEnabled) ResolveColorPickerDebug(debugParams, m_DebugColorPickerBuffer, m_IntermediateAfterPostProcessBuffer, cmd); diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs index 0f491337b7a..af01a856dec 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs @@ -597,7 +597,12 @@ static class HDShaderIDs public static readonly int _ExposureTexture = Shader.PropertyToID("_ExposureTexture"); public static readonly int _PrevExposureTexture = Shader.PropertyToID("_PrevExposureTexture"); public static readonly int _PreviousExposureTexture = Shader.PropertyToID("_PreviousExposureTexture"); + public static readonly int _ExposureDebugTexture = Shader.PropertyToID("_ExposureDebugTexture"); public static readonly int _ExposureParams = Shader.PropertyToID("_ExposureParams"); + public static readonly int _ExposureParams2 = Shader.PropertyToID("_ExposureParams2"); + public static readonly int _ExposureDebugParams = Shader.PropertyToID("_ExposureDebugParams"); + public static readonly int _HistogramExposureParams = Shader.PropertyToID("_HistogramExposureParams"); + public static readonly int _HistogramBuffer = Shader.PropertyToID("_HistogramBuffer"); public static readonly int _AdaptationParams = Shader.PropertyToID("_AdaptationParams"); public static readonly int _ExposureCurveTexture = Shader.PropertyToID("_ExposureCurveTexture"); public static readonly int _ExposureWeightMask = Shader.PropertyToID("_ExposureWeightMask"); diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs index 391ee0d57e1..3a98956a933 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs @@ -25,6 +25,8 @@ public sealed class ShaderResources public Shader debugFullScreenPS; [Reload("Runtime/Debug/DebugColorPicker.Shader")] public Shader debugColorPickerPS; + [Reload("Runtime/Debug/DebugExposure.Shader")] + public Shader debugExposurePS; [Reload("Runtime/Debug/DebugLightVolumes.Shader")] public Shader debugLightVolumePS; [Reload("Runtime/Debug/DebugLightVolumes.compute")] @@ -220,6 +222,8 @@ public sealed class ShaderResources public ComputeShader nanKillerCS; [Reload("Runtime/PostProcessing/Shaders/Exposure.compute")] public ComputeShader exposureCS; + [Reload("Runtime/PostProcessing/Shaders/HistogramExposure.compute")] + public ComputeShader histogramExposureCS; [Reload("Runtime/PostProcessing/Shaders/ApplyExposure.compute")] public ComputeShader applyExposureCS; [Reload("Runtime/PostProcessing/Shaders/UberPost.compute")] diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipelineResources/HDRenderPipelineResources.asset b/com.unity.render-pipelines.high-definition/Runtime/RenderPipelineResources/HDRenderPipelineResources.asset index dcafb599476..add31419125 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipelineResources/HDRenderPipelineResources.asset +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipelineResources/HDRenderPipelineResources.asset @@ -21,12 +21,10 @@ MonoBehaviour: type: 3} debugViewTilesPS: {fileID: 4800000, guid: c7c2bd17b06ceb4468e14081aaf1b96f, type: 3} debugFullScreenPS: {fileID: 4800000, guid: e874aca2df8300a488258738c31f85cf, type: 3} - debugColorPickerPS: {fileID: 4800000, guid: 8137b807709e178498f22ed710864bb0, - type: 3} - debugLightVolumePS: {fileID: 4800000, guid: 8e706c0e71fcec34a8f5c9713e5e2943, - type: 3} - debugLightVolumeCS: {fileID: 7200000, guid: f5d5d21faef5cf445ac2c5d8ff9c4184, - type: 3} + debugColorPickerPS: {fileID: 4800000, guid: 8137b807709e178498f22ed710864bb0, type: 3} + debugExposurePS: {fileID: 4800000, guid: 0ef322534f047a34c96d29419d56d17a, type: 3} + debugLightVolumePS: {fileID: 4800000, guid: 8e706c0e71fcec34a8f5c9713e5e2943, type: 3} + debugLightVolumeCS: {fileID: 7200000, guid: f5d5d21faef5cf445ac2c5d8ff9c4184, type: 3} debugBlitQuad: {fileID: 4800000, guid: cf5ca5b6ef18b3f429ed707ee9ceac9f, type: 3} deferredPS: {fileID: 4800000, guid: 00dd221e34a6ab349a1196b0f2fab693, type: 3} colorPyramidPS: {fileID: 4800000, guid: 2fcfb8d92f45e4549b3f0bad5d0654bf, type: 3} @@ -152,6 +150,7 @@ MonoBehaviour: copyAlphaCS: {fileID: 7200000, guid: c2c7eb6611725264187721ef9df0354b, type: 3} nanKillerCS: {fileID: 7200000, guid: 83982f199acf927499576a99abc9bea9, type: 3} exposureCS: {fileID: 7200000, guid: 976d7bce54fae534fb9ec67e9c18570c, type: 3} + histogramExposureCS: {fileID: 7200000, guid: 222da48299136f34b8e3fb75ae9f8ac7, type: 3} applyExposureCS: {fileID: 7200000, guid: 1a6fea1dc099b984d8f2b27d504dc096, type: 3} uberPostCS: {fileID: 7200000, guid: f1bf52f7c71bffd4f91e6cd90d12a4f7, type: 3} lutBuilder3DCS: {fileID: 7200000, guid: 37f2b1b0ecd6f1c439e4c1b4f2fdb524, type: 3}