diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 462218537cb..9400c5148c3 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -154,6 +154,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added scene view exposure override. - Added support for exposure curve remapping for min/max limits. - Added presets for ray traced reflections. +- Added final image histogram debug view (both luminance and RGB). ### Fixed - Fix when rescale probe all direction below zero (1219246) 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 1d4f00de3b2..efdbf756423 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs @@ -981,8 +981,20 @@ void RegisterLightingDebug() setter = value => data.lightingDebugSettings.centerHistogramAroundMiddleGrey = value }); } + if (data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.FinalImageHistogramView) + { + exposureFoldout.children.Add( + new DebugUI.BoolField() + { + displayName = "Display RGB Histogram", + getter = () => data.lightingDebugSettings.displayFinalImageHistogramAsRGB, + setter = value => data.lightingDebugSettings.displayFinalImageHistogramAsRGB = value + }); - exposureFoldout.children.Add( + } + + + exposureFoldout.children.Add( new DebugUI.FloatField { displayName = "Debug Lens Attenuation", diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader index 7dab784c729..15822f60cf1 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugExposure.shader @@ -37,6 +37,7 @@ Shader "Hidden/HDRP/DebugExposure" #define _DrawTonemapCurve _ExposureDebugParams.x #define _TonemapType _ExposureDebugParams.y #define _CenterAroundTargetExposure _ExposureDebugParams.z + #define _FinalImageHistogramRGB _ExposureDebugParams.w struct Attributes @@ -683,6 +684,71 @@ Shader "Hidden/HDRP/DebugExposure" return outputColor; } + StructuredBuffer _FullImageHistogram; + + float3 FragImageHistogram(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; + float3 outputColor = color; + float heightLabelBar = (DEBUG_FONT_TEXT_WIDTH * 1.25) * _ScreenSize.w * _RTHandleScale.y; + uint maxValue = 0; + uint maxLuma = 0; + for (int i = 0; i < 256; ++i) + { + uint histogramVal = Max3(_FullImageHistogram[i].x, _FullImageHistogram[i].y, _FullImageHistogram[i].z); + maxValue = max(histogramVal, maxValue); + maxLuma = max(_FullImageHistogram[i].w, maxLuma); + } + float histFrameHeight = 0.2 * _RTHandleScale.y; + float safeBand = 1.0f / 255.0f; + float binLocMin = safeBand; + float binLocMax = 1.0f - safeBand; + if (DrawEmptyFrame(uv, float3(0.125, 0.125, 0.125), 0.4, histFrameHeight, heightLabelBar, outputColor)) + { + // Draw labels + const int labelCount = 12; + int minLabelLocationX = DEBUG_FONT_TEXT_WIDTH * 0.25; + int maxLabelLocationX = _ScreenSize.x - (DEBUG_FONT_TEXT_WIDTH * 3); + int labelLocationY = 0.0f; + uint2 unormCoord = input.positionCS.xy; + [unroll] + for (int i = 0; i <= labelCount; ++i) + { + float t = rcp(labelCount) * i; + uint2 labelLoc = uint2((uint)lerp(minLabelLocationX, maxLabelLocationX, t), labelLocationY); + float labelValue = lerp(0.0, 255.0, t); + labelLoc.x += 2; + DrawInteger(labelValue, float3(1.0f, 1.0f, 1.0f), unormCoord, labelLoc, outputColor.rgb); + } + float remappedX = (uv.x - binLocMin) / (binLocMax - binLocMin); + // Draw bins + uint bin = remappedX * 255; + float4 val = _FullImageHistogram[bin]; + val /= float4(maxValue, maxValue, maxValue, maxLuma); + val *= 0.95*(histFrameHeight - heightLabelBar); + val += heightLabelBar; + if (_FinalImageHistogramRGB > 0) + { + float3 alphas = 0; + if (uv.y < val.x && uv.y > heightLabelBar) + alphas.x = 0.3333f; + if (uv.y < val.y && uv.y > heightLabelBar) + alphas.y = 0.3333f; + if (uv.y < val.z && uv.y > heightLabelBar) + alphas.z = 0.3333f; + outputColor = outputColor * (1.0f - (alphas.x + alphas.y + alphas.z)) + alphas; + } + else + { + if (uv.y < val.w && uv.y > heightLabelBar) + outputColor = 0.3333f; + } + } + return outputColor; + } + ENDHLSL SubShader @@ -724,6 +790,17 @@ Shader "Hidden/HDRP/DebugExposure" ENDHLSL } + Pass + { + ZWrite Off + ZTest Always + Blend Off + Cull Off + HLSLPROGRAM + #pragma fragment FragImageHistogram + ENDHLSL + } + } Fallback Off } 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 89d8812e501..f42f81e0b4f 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/LightingDebug.cs @@ -184,6 +184,8 @@ public enum ExposureDebugMode SceneEV100Values, /// Display the Histogram used for exposure. HistogramView, + /// Display an RGB histogram of the final image (after post-processing). + FinalImageHistogramView, /// Visualize the scene color weighted as the metering mode selected. MeteringWeighted, @@ -323,6 +325,8 @@ public bool IsDebugDisplayEnabled() public bool showTonemapCurveAlongHistogramView = true; /// Whether to center the histogram debug view around the middle-grey point or not. public bool centerHistogramAroundMiddleGrey = false; + /// Whether to show tonemap curve in the histogram debug view or not. + public bool displayFinalImageHistogramAsRGB = false; /// Display the light cookies atlas. public bool displayCookieAtlas = false; 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 487069c6a43..681c169c7f7 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/PostProcessSystem.cs @@ -35,6 +35,7 @@ private enum SMAAStage // Exposure data const int k_ExposureCurvePrecision = 128; const int k_HistogramBins = 128; // Important! If this changes, need to change HistogramExposure.compute + const int k_DebugImageHistogramBins = 256; // Important! If this changes, need to change HistogramExposure.compute readonly Color[] m_ExposureCurveColorArray = new Color[k_ExposureCurvePrecision]; readonly int[] m_ExposureVariants = new int[4]; @@ -42,7 +43,9 @@ private enum SMAAStage RTHandle m_EmptyExposureTexture; // RGHalf RTHandle m_DebugExposureData; ComputeBuffer m_HistogramBuffer; + ComputeBuffer m_DebugImageHistogramBuffer; readonly int[] m_EmptyHistogram = new int[k_HistogramBins]; + readonly int[] m_EmptyDebugImageHistogram = new int[k_DebugImageHistogramBins * 4]; // Depth of field data ComputeBuffer m_BokehNearKernel; @@ -234,6 +237,7 @@ public void Cleanup() CoreUtils.SafeRelease(m_FarBokehTileList); CoreUtils.SafeRelease(m_ContrastAdaptiveSharpen); CoreUtils.SafeRelease(m_HistogramBuffer); + CoreUtils.SafeRelease(m_DebugImageHistogramBuffer); RTHandles.Release(m_DebugExposureData); m_ExposureCurveTexture = null; @@ -248,7 +252,8 @@ public void Cleanup() m_NearBokehTileList = null; m_FarBokehTileList = null; m_HistogramBuffer = null; - m_DebugExposureData = null; + m_DebugImageHistogramBuffer = null; + m_DebugExposureData = null; } @@ -969,6 +974,12 @@ internal void ComputeProceduralMeteringParams(HDCamera camera, out Vector4 proce proceduralParams2 = new Vector4(1.0f / m_Exposure.proceduralSoftness.value, LightUtils.ConvertEvToLuminance(m_Exposure.maskMinIntensity.value), LightUtils.ConvertEvToLuminance(m_Exposure.maskMaxIntensity.value), 0.0f); } + + internal ComputeBuffer GetDebugImageHistogramBuffer() + { + return m_DebugImageHistogramBuffer; + } + void DoFixedExposure(CommandBuffer cmd, HDCamera camera) { var cs = m_Resources.shaders.exposureCS; @@ -1229,6 +1240,24 @@ void DoHistogramBasedExposure(CommandBuffer cmd, HDCamera camera, RTHandle sourc cmd.DispatchCompute(cs, kernel, 1, 1, 1); } + internal void GenerateDebugImageHistogram(CommandBuffer cmd, HDCamera camera, RTHandle sourceTexture) + { + var cs = m_Resources.shaders.debugImageHistogramCS; + int kernel = cs.FindKernel("KHistogramGen"); + + ValidateComputeBuffer(ref m_DebugImageHistogramBuffer, k_DebugImageHistogramBins * 4, sizeof(uint)); + m_DebugImageHistogramBuffer.SetData(m_EmptyDebugImageHistogram); // Clear the histogram + cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._SourceTexture, sourceTexture); + cmd.SetComputeBufferParam(cs, kernel, HDShaderIDs._HistogramBuffer, m_DebugImageHistogramBuffer); + + int threadGroupSizeX = 16; + int threadGroupSizeY = 16; + 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); + } + #endregion #region Temporal Anti-aliasing diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute new file mode 100644 index 00000000000..04571d57ac0 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute @@ -0,0 +1,62 @@ +#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" + + +#pragma kernel KHistogramGen +#define GROUP_SIZE_X 16 +#define GROUP_SIZE_Y 16 + +#define HISTOGRAM_BINS 256 + +// RGB: Histogram for RGB separately A: Histogram for luminance +RWStructuredBuffer _HistogramBuffer; +TEXTURE2D_X(_SourceTexture); + + +uint4 GetBinsLoc(float3 rgbValue) +{ + float3 srgbVal = LinearToSRGB(rgbValue); + + return uint4(saturate(float4(srgbVal, Luminance(srgbVal))) * (HISTOGRAM_BINS - 1)); +} + +groupshared uint4 gs_localHistogram[HISTOGRAM_BINS]; + +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)] +void KHistogramGen(uint groupIndex : SV_GroupIndex, + uint3 dispatchThreadId : SV_DispatchThreadID) +{ + if (groupIndex < HISTOGRAM_BINS) + { + gs_localHistogram[groupIndex] = uint4(0u, 0u, 0u, 0u); + } + + GroupMemoryBarrierWithGroupSync(); + + uint2 fullResCoords = dispatchThreadId.xy << 1u; + + if (all(fullResCoords < uint2(_ScreenSize.xy))) + { + float2 uv = ClampAndScaleUVForBilinear((fullResCoords + 0.5) * _ScreenSize.zw); + float3 rgbVal = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, s_linear_clamp_sampler, uv, 0.0).xyz; + + uint4 bins = GetBinsLoc(rgbVal); + + InterlockedAdd(gs_localHistogram[bins.x].x, 1u); + InterlockedAdd(gs_localHistogram[bins.y].y, 1u); + InterlockedAdd(gs_localHistogram[bins.z].z, 1u); + InterlockedAdd(gs_localHistogram[bins.w].w, 1u); + } + + GroupMemoryBarrierWithGroupSync(); + + if (groupIndex < HISTOGRAM_BINS) + { + InterlockedAdd(_HistogramBuffer[groupIndex].x, gs_localHistogram[groupIndex].x); + InterlockedAdd(_HistogramBuffer[groupIndex].y, gs_localHistogram[groupIndex].y); + InterlockedAdd(_HistogramBuffer[groupIndex].z, gs_localHistogram[groupIndex].z); + InterlockedAdd(_HistogramBuffer[groupIndex].w, gs_localHistogram[groupIndex].w); + } +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute.meta b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute.meta new file mode 100644 index 00000000000..2eeb9bcd6dd --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/DebugHistogramImage.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 52cc17ef5a5ffc443a5c142f9b745a85 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: 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 70e25fb9772..f7e0df0198e 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs @@ -2816,6 +2816,12 @@ void Callback(CommandBuffer c, HDCamera cam) RenderTargetIdentifier postProcessDest = HDUtils.PostProcessIsFinalPass(hdCamera) ? target.id : m_IntermediateAfterPostProcessBuffer; RenderPostProcess(cullingResults, hdCamera, postProcessDest, renderContext, cmd); + // If requested, compute histogram of the very final image + if (m_CurrentDebugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.FinalImageHistogramView) + { + m_PostProcessSystem.GenerateDebugImageHistogram(cmd, hdCamera, m_IntermediateAfterPostProcessBuffer); + } + PushFullScreenExposureDebugTexture(cmd, m_IntermediateAfterPostProcessBuffer); RenderCustomPass(renderContext, cmd, hdCamera, customPassCullingResults, CustomPassInjectionPoint.AfterPostProcess, aovRequest, aovCustomPassBuffers); @@ -4909,7 +4915,8 @@ static void RenderExposureDebug(in DebugParameters parameters, 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)); + bool centerAroundMiddleGrey = parameters.debugDisplaySettings.data.lightingDebugSettings.centerHistogramAroundMiddleGrey; + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ExposureDebugParams, new Vector4(drawTonemapCurve ? 1.0f : 0.0f, (int)tonemappingMode, centerAroundMiddleGrey ? 1 : 0, 0)); if (drawTonemapCurve) { if (tonemappingMode == TonemappingMode.Custom) @@ -4930,6 +4937,16 @@ static void RenderExposureDebug(in DebugParameters parameters, } passIndex = 2; } + if (parameters.debugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.FinalImageHistogramView) + { + bool finalImageRGBHisto = parameters.debugDisplaySettings.data.lightingDebugSettings.displayFinalImageHistogramAsRGB; + + parameters.debugExposureMaterial.SetVector(HDShaderIDs._ExposureDebugParams, new Vector4(0, 0, 0, finalImageRGBHisto ? 1 : 0)); + + parameters.debugExposureMaterial.SetBuffer(HDShaderIDs._FullImageHistogram, histogramBuffer); + passIndex = 3; + } + HDUtils.DrawFullScreen(cmd, parameters.debugExposureMaterial, output, null, passIndex); } @@ -4989,7 +5006,7 @@ void RenderDebug(HDCamera hdCamera, CommandBuffer cmd, CullingResults cullResult m_PostProcessSystem.GetLutSize(), proceduralParams1, proceduralParams2, - m_PostProcessSystem.GetHistogramBuffer(), cmd); + debugParams.debugDisplaySettings.data.lightingDebugSettings.exposureDebugMode == ExposureDebugMode.FinalImageHistogramView ? m_PostProcessSystem.GetDebugImageHistogramBuffer() : m_PostProcessSystem.GetHistogramBuffer(), cmd); } // First resolve color picker 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 0931b75f459..c1c5d161694 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs @@ -645,6 +645,7 @@ static class HDShaderIDs 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 _FullImageHistogram = Shader.PropertyToID("_FullImageHistogram"); 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 06f3e24d1ed..9f6dbd1ac7a 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPipelineResources.cs @@ -228,6 +228,8 @@ public sealed class ShaderResources public ComputeShader histogramExposureCS; [Reload("Runtime/PostProcessing/Shaders/ApplyExposure.compute")] public ComputeShader applyExposureCS; + [Reload("Runtime/PostProcessing/Shaders/DebugHistogramImage.compute")] + public ComputeShader debugImageHistogramCS; [Reload("Runtime/PostProcessing/Shaders/UberPost.compute")] public ComputeShader uberPostCS; [Reload("Runtime/PostProcessing/Shaders/LutBuilder3D.compute")]