diff --git a/Assets/Scenes/Oasis/OasisScene.unity b/Assets/Scenes/Oasis/OasisScene.unity index 38caa06..4f28064 100644 --- a/Assets/Scenes/Oasis/OasisScene.unity +++ b/Assets/Scenes/Oasis/OasisScene.unity @@ -4707,6 +4707,10 @@ PrefabInstance: propertyPath: m_Name value: NestedParentArmature_Unpack objectReference: {fileID: 0} + - target: {fileID: 8508310942573128499, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3} + propertyPath: m_RenderPostProcessing + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: - {fileID: 3543048675319490572, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3} m_RemovedGameObjects: [] diff --git a/Assets/Scenes/Oasis/Scripts/Runtime/OasisFogVolumeComponent.cs b/Assets/Scenes/Oasis/Scripts/Runtime/OasisFogVolumeComponent.cs index beb8765..579a611 100644 --- a/Assets/Scenes/Oasis/Scripts/Runtime/OasisFogVolumeComponent.cs +++ b/Assets/Scenes/Oasis/Scripts/Runtime/OasisFogVolumeComponent.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections; -using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; diff --git a/Assets/Scenes/Oasis/Settings/Oasis_Outdoor_Volume_Profile.asset b/Assets/Scenes/Oasis/Settings/Oasis_Outdoor_Volume_Profile.asset index b77313a..aad5ef0 100644 --- a/Assets/Scenes/Oasis/Settings/Oasis_Outdoor_Volume_Profile.asset +++ b/Assets/Scenes/Oasis/Settings/Oasis_Outdoor_Volume_Profile.asset @@ -63,6 +63,41 @@ MonoBehaviour: - {fileID: 5935297997615179469} - {fileID: -4720686990112659349} - {fileID: 9109530758031880643} + - {fileID: 4702212335856883116} +--- !u!114 &4702212335856883116 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: af08f034e95c2d246b0cbaf4305dfbc4, type: 3} + m_Name: AutoExposureVolumeProfile + m_EditorClassIdentifier: + active: 1 + filtering: + m_OverrideState: 0 + m_Value: {x: 50, y: 95} + minLuminance: + m_OverrideState: 0 + m_Value: 0 + maxLuminance: + m_OverrideState: 0 + m_Value: 0 + exposureCompensation: + m_OverrideState: 0 + m_Value: 1 + eyeAdaptation: + m_OverrideState: 0 + m_Value: 0 + speedUp: + m_OverrideState: 0 + m_Value: 2 + speedDown: + m_OverrideState: 0 + m_Value: 1 --- !u!114 &5935297997615179469 MonoBehaviour: m_ObjectHideFlags: 3 diff --git a/Assets/Scenes/Oasis/Settings/Oasis_Tent_Volume_Profile.asset b/Assets/Scenes/Oasis/Settings/Oasis_Tent_Volume_Profile.asset index 355c0a9..75024a3 100644 --- a/Assets/Scenes/Oasis/Settings/Oasis_Tent_Volume_Profile.asset +++ b/Assets/Scenes/Oasis/Settings/Oasis_Tent_Volume_Profile.asset @@ -75,7 +75,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 5a00a63fdd6bd2a45ab1f2d869305ffd, type: 3} m_Name: OasisFogVolumeComponent m_EditorClassIdentifier: - active: 1 + active: 0 Density: m_OverrideState: 1 m_Value: 0.002 @@ -103,7 +103,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3} m_Name: ColorAdjustments m_EditorClassIdentifier: - active: 1 + active: 0 postExposure: m_OverrideState: 1 m_Value: 2 diff --git a/Assets/Settings/Mobile/Mobile_High_Renderer.asset b/Assets/Settings/Mobile/Mobile_High_Renderer.asset index 0855119..4e964be 100644 --- a/Assets/Settings/Mobile/Mobile_High_Renderer.asset +++ b/Assets/Settings/Mobile/Mobile_High_Renderer.asset @@ -108,6 +108,25 @@ MonoBehaviour: cutoffThreshold: 0.2 binaryValue: 0.9 flags: 13 +--- !u!114 &-7143664486661302651 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d34dacd53c1ace84a8c6ac5ff4afb555, type: 3} + m_Name: AutoExposure + m_EditorClassIdentifier: + m_Active: 1 + settings: + HistogramRenderPassEvent: 550 + AutoExposureRenderPassEvent: 550 + BlitMat: {fileID: 2100000, guid: b04591ed716b35e41857f554d491ef4b, type: 2} + ComputeHistogramComputeShader: {fileID: 7200000, guid: b140e27dc74a1fb4d9ae30f8566b8919, type: 3} + AutoExposureComputeShader: {fileID: 7200000, guid: 6febb5a945f3510429c58ed4a45c1846, type: 3} --- !u!114 &-5418649131825517062 MonoBehaviour: m_ObjectHideFlags: 0 @@ -337,7 +356,8 @@ MonoBehaviour: - {fileID: 7541218312462517771} - {fileID: 5808157236138506604} - {fileID: -5418649131825517062} - m_RendererFeatureMap: bc3f630842f2e70dd6a559c442a94bfd4529d15534f2d3de228858dca8d12222716523fbf3439fdb7a327b7bff4bdd446ac59dfa966ffa88ca6373cd5da9013d6cff55ca297e5e908a7b3653203b82383b2141bb05fbe69aec5704e48e2763e90bc6ff9f19caa7686c79a6bb3bb89a50faad0fe75217cdb4 + - {fileID: -7143664486661302651} + m_RendererFeatureMap: bc3f630842f2e70dd6a559c442a94bfd4529d15534f2d3de228858dca8d12222716523fbf3439fdb7a327b7bff4bdd446ac59dfa966ffa88ca6373cd5da9013d6cff55ca297e5e908a7b3653203b82383b2141bb05fbe69aec5704e48e2763e90bc6ff9f19caa7686c79a6bb3bb89a50faad0fe75217cdb485d6fa85ff9adc9c m_UseNativeRenderPass: 0 postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} shaders: diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposure.cs b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposure.cs index 6485860..1a1c9b5 100644 --- a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposure.cs +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposure.cs @@ -1,5 +1,7 @@ using System; +using System.Drawing.Drawing2D; using UnityEngine; +using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; @@ -10,57 +12,131 @@ namespace X.Rendering.Feature [Serializable] public class Settings { - public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; + public RenderPassEvent HistogramRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; + public RenderPassEvent AutoExposureRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; public Material BlitMat; - public ComputeShader Compute; + public ComputeShader ComputeHistogramComputeShader; + public ComputeShader AutoExposureComputeShader; } [SerializeField] Settings settings; AutoExposurePass autoExposurePass; + ComputeLuminanceHistogramPass computeLuminanceHistogramPass; public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { +#if UNITY_EDITOR + if(renderingData.cameraData.isSceneViewCamera || renderingData.cameraData.isPreviewCamera) + { + return; + } +#endif + renderer.EnqueuePass(computeLuminanceHistogramPass); renderer.EnqueuePass(autoExposurePass); } public override void Create() { + computeLuminanceHistogramPass = new(settings); autoExposurePass = new(settings); } - class AutoExposurePass : ScriptableRenderPass + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + autoExposurePass?.Dispose(); + computeLuminanceHistogramPass?.Dispose(); + } + + class ComputeLuminanceHistogramPass : ScriptableRenderPass, IDisposable + { + private ProfilingSampler profiler; + private Settings settings; + private GraphicsBuffer histogramBuffer; + + public const int k_Bins = 128; + public static GraphicsBufferHandle HistogramBufferHandle; + + public ComputeLuminanceHistogramPass(Settings settings) + { + this.settings = settings; + profiler = new(nameof(ComputeLuminanceHistogramPass)); + renderPassEvent = settings.HistogramRenderPassEvent; + histogramBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, k_Bins, sizeof(uint)); + } + + public void Dispose() + { + histogramBuffer?.Release(); + histogramBuffer = null; + } + + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + var stack = VolumeManager.instance.stack; + var autoExposureVolume = stack.GetComponent(); + if (!autoExposureVolume || !autoExposureVolume.IsActive()) + { + return; + } + var cmd = renderingData.commandBuffer; + using var scp = new ProfilingScope(cmd, profiler); + var scaleOffsetRes = AutoExposureVolumeProfile.GetHistogramScaleOffsetRes(); + var computeShader = settings.ComputeHistogramComputeShader; + int kernel = computeShader.FindKernel("KEyeHistogramClear"); + cmd.SetComputeBufferParam(computeShader, kernel, "_HistogramBuffer", histogramBuffer); + computeShader.GetKernelThreadGroupSizes(kernel, out var threadX, out var threadY, out var threadZ); + cmd.DispatchCompute(computeShader, kernel, Mathf.CeilToInt(k_Bins / (float)threadX), 1, 1); + + // Get a log histogram + kernel = 1; + HistogramBufferHandle = histogramBuffer.bufferHandle; + cmd.SetComputeBufferParam(computeShader, kernel, "_HistogramBuffer", histogramBuffer); + cmd.SetComputeTextureParam(computeShader, kernel, "_Source", renderingData.cameraData.renderer.cameraColorTargetHandle); + cmd.SetComputeVectorParam(computeShader, "_ScaleOffsetRes", scaleOffsetRes); + + computeShader.GetKernelThreadGroupSizes(kernel, out threadX, out threadY, out threadZ); + + cmd.DispatchCompute(computeShader, kernel, + Mathf.CeilToInt(scaleOffsetRes.z / 2f / threadX), + Mathf.CeilToInt(scaleOffsetRes.w / 2f / threadY), + 1); + } + } + + class AutoExposurePass : ScriptableRenderPass, IDisposable { private Settings settings; private int computeCounter; private ProfilingSampler profiler; - private ComputeBuffer outputBuffer; - private static readonly int Dims = Shader.PropertyToID("_Dims"); - private static readonly int Source = Shader.PropertyToID("_Source"); - private static readonly int TempOut = Shader.PropertyToID("_OutBuffer"); - private static readonly int ScreenExposureProp = Shader.PropertyToID("_Exposure"); - private static readonly int ExposureRangeProp = Shader.PropertyToID("_Range"); - private static readonly int WhitePointProp = Shader.PropertyToID("_WhitePoint"); - private static readonly int MaxBrightness = Shader.PropertyToID("_MaxBrightness"); - private static readonly int BlitTextureId = Shader.PropertyToID("_BlitTexture"); - + const int k_NumAutoExposureTextures = 2; + private RTHandle[] autoExposureHandles; + private int autoExposurePingPong; - private const int ThreadGroupSize = 32; - - private Vector2Int lastSize = Vector2Int.zero; - - private static float targetExposure = 0.1f; - private static float currentExposure = 0.1f; private static AutoExposureVolumeProfile autoExposureVolume = null; - private static float lastTime = 0.0f; - private static bool autoExposureing = false; + private bool reset = false; public AutoExposurePass(Settings settings) { this.settings = settings; - renderPassEvent = settings.renderPassEvent; + renderPassEvent = settings.AutoExposureRenderPassEvent; profiler = new(nameof(AutoExposurePass)); + + var desc = new RenderTextureDescriptor(1,1,GraphicsFormat.R32_SFloat,0,1) + { + enableRandomWrite = true, + msaaSamples = 1, + useMipMap = false + }; + + autoExposureHandles = new RTHandle[k_NumAutoExposureTextures]; + + for (var i = 0; i < autoExposureHandles.Length; i++) + { + RenderingUtils.ReAllocateIfNeeded(ref autoExposureHandles[i], desc, name: $"AutoExposure_{i}"); + } } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) @@ -69,97 +145,89 @@ namespace X.Rendering.Feature autoExposureVolume = stack.GetComponent(); if (!autoExposureVolume || !autoExposureVolume.IsActive()) { + reset = true; return; } var cmd = renderingData.commandBuffer; using var scp = new ProfilingScope(cmd, profiler); - if (computeCounter % Mathf.Max(autoExposureVolume.framesPerCompute.value, 1.0f) == 0) + var filteringMinMax = autoExposureVolume.filtering.value; + float lowPercent = filteringMinMax.x; + float highPercent = filteringMinMax.y; + const float kMinDelta = 1e-2f; + highPercent = Mathf.Clamp(highPercent, 1f + kMinDelta, 99f); + lowPercent = Mathf.Clamp(lowPercent, 1f, highPercent - kMinDelta); + + // Clamp min/max adaptation values as well + float minLum = autoExposureVolume.minLuminance.value; + float maxLum = autoExposureVolume.maxLuminance.value; + minLum = Mathf.Min(minLum, maxLum); + maxLum = Mathf.Max(minLum, maxLum); + var speedDown = Mathf.Max(0, autoExposureVolume.speedDown.value); + var speedUp = Mathf.Max(0, autoExposureVolume.speedUp.value); + var exposureCompensation = autoExposureVolume.exposureCompensation.value; + string adaptation = null; + + if (autoExposureVolume.eyeAdaptation.value == AutoExposureVolumeProfile.EyeAdaptation.Fixed) + adaptation = "KAutoExposureAvgLuminance_fixed"; + else + adaptation = "KAutoExposureAvgLuminance_progressive"; + + var compute = settings.AutoExposureComputeShader; + int kernel = compute.FindKernel(adaptation); + int pp = autoExposurePingPong; + var src = autoExposureHandles[++pp % 2]; + var dst = autoExposureHandles[++pp % 2]; + + if (reset) { - computeCounter = 0; - var textureDesc = renderingData.cameraData.cameraTargetDescriptor; - - Vector2Int size = new(textureDesc.width, textureDesc.height); - if(outputBuffer == null || lastSize != size) - { - lastSize = size; - outputBuffer?.Dispose(); - outputBuffer = new ComputeBuffer(Mathf.CeilToInt((float)size.y / ThreadGroupSize * 2), 4, ComputeBufferType.Structured) - { - name = "Auto Exposure Output Buffer" - }; - } - cmd.SetComputeIntParams(settings.Compute, Dims, size.x, size.y); - - cmd.SetComputeTextureParam(settings.Compute, 0, Source, renderingData.cameraData.renderer.cameraColorTargetHandle); - cmd.SetComputeBufferParam(settings.Compute, 0, TempOut, outputBuffer); - cmd.DispatchCompute(settings.Compute, 0, size.y, 1, 1); - AsyncGPUReadback.Request(outputBuffer, OnCompleteReadBack); + cmd.SetRenderTarget(src, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); + cmd.ClearRenderTarget(false, true, Color.clear); } - ++computeCounter; - if (!autoExposureing) - { - //return; - } - UpdateExposure(); - settings.BlitMat.SetFloat(ScreenExposureProp, currentExposure); - settings.BlitMat.SetVector(ExposureRangeProp, autoExposureVolume.exposureRange.value); - settings.BlitMat.SetFloat(WhitePointProp, autoExposureVolume.whitePoint.value); - settings.BlitMat.SetFloat(MaxBrightness, autoExposureVolume.brightnessLimit.value); + + cmd.SetComputeBufferParam(compute, kernel, "_HistogramBuffer", ComputeLuminanceHistogramPass.HistogramBufferHandle); + cmd.SetComputeVectorParam(compute, "_Params1", new Vector4(lowPercent * 0.01f, highPercent * 0.01f, Mathf.Pow(2, minLum), Mathf.Pow(2, maxLum))); + cmd.SetComputeVectorParam(compute, "_Params2", new Vector4(speedDown, speedUp, exposureCompensation, Time.smoothDeltaTime)); + cmd.SetComputeVectorParam(compute, "_ScaleOffsetRes", AutoExposureVolumeProfile.GetHistogramScaleOffsetRes()); + + cmd.SetComputeTextureParam(compute, kernel, "_Source", src); + cmd.SetComputeTextureParam(compute, kernel, "_Destination", dst); + cmd.DispatchCompute(compute, kernel, 1, 1, 1); + autoExposurePingPong = ++pp % 2; + + var renderer = renderingData.cameraData.renderer; - settings.BlitMat.SetTexture(BlitTextureId, renderer.cameraColorTargetHandle); + settings.BlitMat.SetTexture("_SourceTexture", renderer.cameraColorTargetHandle); + settings.BlitMat.SetTexture("_AutoExposureTexture", dst); var destination = renderer.GetCameraColorFrontBuffer(cmd); cmd.SetRenderTarget(destination, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); cmd.DrawProcedural(Matrix4x4.identity, settings.BlitMat, 0, MeshTopology.Triangles, 3); renderer.SwapColorBuffer(cmd); + reset = false; + //cmd.RequestAsyncReadback(dst, (a) => + //{ + // if (!a.hasError) + // { + // var data = a.GetData(); + // for (var i = 0; i < data.Length; i++) + // { + // Debug.Log(data[i]); + // } + // } + //}); } - private static void OnCompleteReadBack(AsyncGPUReadbackRequest request) + + public void Dispose() { - if (request.hasError) + if (autoExposureHandles != null) { - return; - } + foreach (var autoExposureHandle in autoExposureHandles) + { + autoExposureHandle?.Release(); + } - // Get Compute result - float[] groupValues = request.GetData().ToArray(); - - // Add up all the workgroup's results - double pixelLumTotal = 0; - uint pixels = 0; - for (int i = 0; i < groupValues.Length / 2; i++) - { - pixelLumTotal += groupValues[i * 2]; - pixels += (uint)Mathf.CeilToInt(groupValues[i * 2 + 1]); - } - - // Average the results and set as the target exposure - targetExposure = (float)(pixelLumTotal / pixels); - autoExposureing = true; - } - - public static float ExpDecay(float a, float b, float decay, float deltaTime) => b + (a - b) * Mathf.Exp(-decay * deltaTime); - - private static void UpdateExposure() - { - // Delta time, using the built-in one seems to flicker a lot, weird - float deltaTime = Time.time - lastTime; - lastTime = Time.time; - - // Exposure difference, skip if zero - float diff = targetExposure - currentExposure; - if (Mathf.Approximately(diff,0)) - { - autoExposureing = false; - return; - } - - float decay = diff > 0 ? autoExposureVolume.increaseSpeed.value : autoExposureVolume.decreaseSpeed.value; - currentExposure = ExpDecay(currentExposure, targetExposure, decay, deltaTime); - - if (float.IsNaN(currentExposure)) - { - currentExposure = 0.1f; + autoExposureHandles = null; } } } diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposureVolumeProfile.cs b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposureVolumeProfile.cs index 2506783..c5aece1 100644 --- a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposureVolumeProfile.cs +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/AutoExposureVolumeProfile.cs @@ -1,32 +1,82 @@ +using System; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace X.Rendering.Feature { + + public class AutoExposureVolumeProfile : VolumeComponent, IPostProcessComponent { - [Tooltip("How many frames it takes to recalculate the average brightness. Decreasing this value can affect performance.")] - public ClampedIntParameter framesPerCompute = new(5, 1, 20); + public const int rangeMin = -9; // ev + public const int rangeMax = 9; // ev - [Space] - [Tooltip("Limits the maximum brightness for very bright objects to work properly with bloom and other effects")] - public FloatParameter brightnessLimit = new(15f); + public static Vector4 GetHistogramScaleOffsetRes() + { + float diff = rangeMax - rangeMin; + float scale = 1f / diff; + float offset = -rangeMin * scale; + return new Vector4(scale, offset, Screen.width, Screen.height); + } - [Tooltip("For reinhard tonemapping")] - public FloatParameter whitePoint = new(3f); + public enum EyeAdaptation + { + /// + /// Progressive (smooth) eye adaptation. + /// + Progressive, - [Tooltip("How much an individual pixel's exposure can vary from the original value.")] - public FloatRangeParameter exposureRange = new(new Vector2(-2.5f, 0.6f), -6.0f, 6.0f); + /// + /// Fixed (instant) eye adaptation. + /// + Fixed + } - [Space] - [Tooltip("How quickly the exposure increases")] - public FloatParameter increaseSpeed = new(5f); + [Serializable] + public sealed class EyeAdaptationParameter : VolumeParameter { } - [Tooltip("How quickly the exposure decreases")] - public FloatParameter decreaseSpeed = new(5f); + [Range(1f, 99f)] + public Vector2Parameter filtering = new Vector2Parameter(new Vector2(50f, 95f)); - public bool IsActive() => framesPerCompute.overrideState; + /// + /// Minimum average luminance to consider for auto exposure (in EV). + /// + [Range(rangeMin, rangeMax), InspectorName("Minimum (EV)")] + public FloatParameter minLuminance = new FloatParameter (0f); + + /// + /// Maximum average luminance to consider for auto exposure (in EV). + /// + [Range(rangeMin, rangeMax), InspectorName("Maximum (EV)")] + public FloatParameter maxLuminance = new FloatParameter (0f); + + /// + /// Middle-grey value. Use this to compensate the global exposure of the scene. + /// + [Min(0f), InspectorName("Exposure Compensation"), Tooltip("Use this to scale the global exposure of the scene.")] + public FloatParameter exposureCompensation = new FloatParameter (1f); + + /// + /// The type of eye adaptation to use. + /// + [InspectorName("Type"), Tooltip("Use \"Progressive\" if you want auto exposure to be animated. Use \"Fixed\" otherwise.")] + public EyeAdaptationParameter eyeAdaptation = new EyeAdaptationParameter() { value = EyeAdaptation.Progressive }; + + /// + /// The adaptation speed from a dark to a light environment. + /// + [Min(0f), Tooltip("Adaptation speed from a dark to a light environment.")] + public FloatParameter speedUp = new FloatParameter (2f); + + /// + /// The adaptation speed from a light to a dark environment. + /// + [Min(0f), Tooltip("Adaptation speed from a light to a dark environment.")] + public FloatParameter speedDown = new FloatParameter (1f); + + + public bool IsActive() => filtering.overrideState; public bool IsTileCompatible() { diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.compute b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.compute new file mode 100644 index 0000000..226753e --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.compute @@ -0,0 +1,184 @@ +#pragma warning(disable : 3568) + +#pragma kernel KAutoExposureAvgLuminance_fixed MAIN=KAutoExposureAvgLuminance_fixed +#pragma kernel KAutoExposureAvgLuminance_progressive MAIN=KAutoExposureAvgLuminance_progressive PROGRESSIVE + +#pragma enable_d3d11_debug_symbols +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" +#define EPSILON 1.0e-4 + +#define HISTOGRAM_BINS 128 +#define HISTOGRAM_TEXELS HISTOGRAM_BINS / 4 + +#if SHADER_API_GLES3 || SHADER_API_METAL +#define HISTOGRAM_THREAD_X 8 +#define HISTOGRAM_THREAD_Y 8 +#define HISTOGRAM_REDUCTION_THREAD_X 8 +#define HISTOGRAM_REDUCTION_THREAD_Y 8 +#define HISTOGRAM_REDUCTION_ALT_PATH 1 +#else +#define HISTOGRAM_THREAD_X 16 +#define HISTOGRAM_THREAD_Y 16 +#define HISTOGRAM_REDUCTION_THREAD_X HISTOGRAM_THREAD_X +#define HISTOGRAM_REDUCTION_THREAD_Y HISTOGRAM_BINS / HISTOGRAM_THREAD_Y +#define HISTOGRAM_REDUCTION_ALT_PATH 0 +#endif + +#define HISTOGRAM_REDUCTION_BINS HISTOGRAM_REDUCTION_THREAD_X * HISTOGRAM_REDUCTION_THREAD_Y + +half Luminance(half3 linearRgb) +{ + return dot(linearRgb, float3(0.2126729, 0.7151522, 0.0721750)); +} + +half Luminance(half4 linearRgba) +{ + return Luminance(linearRgba.rgb); +} + +float GetHistogramBinFromLuminance(float value, float2 scaleOffset) +{ + return saturate(log2(value) * scaleOffset.x + scaleOffset.y); +} + +float GetLuminanceFromHistogramBin(float bin, float2 scaleOffset) +{ + return exp2((bin - scaleOffset.y) / scaleOffset.x); +} + +float GetBinValue(StructuredBuffer buffer, uint index, float maxHistogramValue) +{ + return float(buffer[index]) * maxHistogramValue; +} + +float FindMaxHistogramValue(StructuredBuffer buffer) +{ + uint maxValue = 0u; + + for (uint i = 0; i < HISTOGRAM_BINS; i++) + { + uint h = buffer[i]; + maxValue = max(maxValue, h); + } + + return float(maxValue); +} + +void FilterLuminance(StructuredBuffer buffer, uint i, float maxHistogramValue, float2 scaleOffset, inout float4 filter) +{ + float binValue = GetBinValue(buffer, i, maxHistogramValue); + + // Filter dark areas + float offset = min(filter.z, binValue); + binValue -= offset; + filter.zw -= offset.xx; + + // Filter highlights + binValue = min(filter.w, binValue); + filter.w -= binValue; + + // Luminance at the bin + float luminance = GetLuminanceFromHistogramBin(float(i) / float(HISTOGRAM_BINS), scaleOffset); + + filter.xy += float2(luminance * binValue, binValue); +} + +float GetAverageLuminance(StructuredBuffer buffer, float4 params, float maxHistogramValue, float2 scaleOffset) +{ + // Sum of all bins + uint i; + float totalSum = 0.0; + + UNITY_UNROLL + + for (i = 0; i < HISTOGRAM_BINS; i++) + totalSum += GetBinValue(buffer, i, maxHistogramValue); + + // Skip darker and lighter parts of the histogram to stabilize the auto exposure + // x: filtered sum + // y: accumulator + // zw: fractions + float4 filter = float4(0.0, 0.0, totalSum * params.xy); + + UNITY_UNROLL + + for (i = 0; i < HISTOGRAM_BINS; i++) + FilterLuminance(buffer, i, maxHistogramValue, scaleOffset, filter); + + // Clamp to user brightness range + return clamp(filter.x / max(filter.y, EPSILON), params.z, params.w); +} + + +StructuredBuffer _HistogramBuffer; +Texture2D _Source; +RWTexture2D _Destination; + +float4 _Params1; // x: lowPercent, y: highPercent, z: minBrightness, w: maxBrightness +float4 _Params2; // x: speed down, y: speed up, z: exposure compensation, w: delta time +float4 _ScaleOffsetRes; // x: scale, y: offset, w: histogram pass width, h: histogram pass height + +groupshared uint gs_pyramid[HISTOGRAM_REDUCTION_BINS]; + +float GetExposureMultiplier(float avgLuminance) +{ + avgLuminance = max(EPSILON, avgLuminance); + //float keyValue = 1.03 - (2.0 / (2.0 + log2(avgLuminance + 1.0))); + float keyValue = _Params2.z; + float exposure = keyValue / avgLuminance; + return exposure; +} + +float InterpolateExposure(float newExposure, float oldExposure) +{ + float delta = newExposure - oldExposure; + float speed = delta > 0.0 ? _Params2.x : _Params2.y; + float exposure = oldExposure + delta * (1.0 - exp2(-_Params2.w * speed)); + return exposure; +} + + + +[numthreads(HISTOGRAM_REDUCTION_THREAD_X, HISTOGRAM_REDUCTION_THREAD_Y, 1)] +void MAIN(uint2 groupThreadId : SV_GroupThreadID) +{ +#if HISTOGRAM_REDUCTION_ALT_PATH + const uint thread_id = groupThreadId.y * HISTOGRAM_REDUCTION_THREAD_X + groupThreadId.x; + gs_pyramid[thread_id] = max(_HistogramBuffer[thread_id], _HistogramBuffer[thread_id + HISTOGRAM_REDUCTION_BINS]); +#else + const uint thread_id = groupThreadId.y * HISTOGRAM_REDUCTION_THREAD_X + groupThreadId.x; + gs_pyramid[thread_id] = _HistogramBuffer[thread_id]; +#endif + + GroupMemoryBarrierWithGroupSync(); + + // Parallel reduction to find the max value + UNITY_UNROLL + + for (uint i = HISTOGRAM_REDUCTION_BINS >> 1u; i > 0u; i >>= 1u) + { + if (thread_id < i) + gs_pyramid[thread_id] = max(gs_pyramid[thread_id], gs_pyramid[thread_id + i]); + + GroupMemoryBarrierWithGroupSync(); + } + + GroupMemoryBarrierWithGroupSync(); + + if (thread_id == 0u) + { + float maxValue = 1.0 / float(gs_pyramid[0]); + +#if PROGRESSIVE + float avgLuminance = GetAverageLuminance(_HistogramBuffer, _Params1, maxValue, _ScaleOffsetRes.xy); + float exposure = GetExposureMultiplier(avgLuminance); + float prevExposure = _Source[uint2(0u, 0u)].x; + exposure = InterpolateExposure(exposure, prevExposure); + _Destination[uint2(0u, 0u)].x = exposure.x; +#else + float avgLuminance = GetAverageLuminance(_HistogramBuffer, _Params1, maxValue, _ScaleOffsetRes.xy); + float exposure = GetExposureMultiplier(avgLuminance); + _Destination[uint2(0u, 0u)].x = exposure.x; +#endif + } +} diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AverageExposure.compute.meta b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.compute.meta similarity index 100% rename from Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AverageExposure.compute.meta rename to Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.compute.meta diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.shader b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.shader index a5ec507..b6b2a98 100644 --- a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.shader +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AutoExposure.shader @@ -21,66 +21,14 @@ Shader "XRP/AutoExposure" #pragma vertex vert #pragma fragment frag - SamplerState sampler_BlitTexture; // Screen texture - - float _MaxBrightness; // Limit the final brightness to this value - float _Exposure; // The average exposure to work with - float _WhitePoint; // White point for reinhard tonemapping - float2 _Range; - // Min and Max luminosity change, how much can each pixel be offset from it's original luminosity - - // Converts from RGB space to Yxy (float3.x is Luminance) - float3 convert_rgb_to_Yxy(float3 rgb) + TEXTURE2D_X(_SourceTexture); + TEXTURE2D_X(_AutoExposureTexture); + + half GammaCorrect_half(half linearColor) { - float3 xyz; - xyz.x = dot(rgb, float3(0.4124f, 0.3576f, 0.1805f)); - xyz.y = dot(rgb, float3(0.2126f, 0.7152f, 0.0722f)); - xyz.z = dot(rgb, float3(0.0193f, 0.1192f, 0.9505f)); - - float sum = xyz.x + xyz.y + xyz.z; - - float x = (sum > 0.0) ? (xyz.x / sum) : 0.0; - float y = (sum > 0.0) ? (xyz.y / sum) : 0.0; - - return float3(xyz.y, x, y); // Y, x, y + return pow(linearColor,1/2.2); } - - // Converts from Yxy (float3.x is Luminance) space to RGB - float3 convert_Yxy_to_rgb(float3 Yxy) - { - float Y = Yxy.x; - float x = Yxy.y; - float y = Yxy.z; - - float X = y > 0.0 ? x * Y / y : 0.0; - float Z = y > 0.0 ? (1.0 - x - y) * Y / y : 0.0; - - float3 XYZ = float3(X, Y, Z); - - float3x3 M_XYZ2RGB = float3x3( - 3.2406f, -1.5372f, -0.4986f, - -0.9689f, 1.8758f, 0.0415f, - 0.0557f, -0.2040f, 1.0570f - ); - - return mul(M_XYZ2RGB, XYZ); - } - - // Tone Mapping function - float reinhard2(float lp, float wp) - { - return lp * (1.0f + lp / (wp * wp)) / (1.0f + lp); - } - - // Sample texture and return RGB and A in separate variables - void sample_split(float2 uv, out float3 rgb, out float alpha) - { - float4 color = _BlitTexture.SampleBias(sampler_BlitTexture, uv, _GlobalMipBias.x);; - rgb = color.rgb; - alpha = color.a; - } - - + Varyings vert(uint vertexID: SV_VertexID) { Varyings o; @@ -88,28 +36,16 @@ Shader "XRP/AutoExposure" o.texcoord = GetFullScreenTriangleTexCoord(vertexID); return o; } - - // Fragment shader, adaptation of: https://bruop.github.io/tonemapping/ + float4 frag(Varyings input) : SV_Target { - float3 rgb; - float alpha; - sample_split(input.texcoord, rgb, alpha); - - // Yxy.x is Y, the luminance - float3 Yxy = convert_rgb_to_Yxy(rgb); - Yxy.x = min(Yxy.x, _MaxBrightness); - - // Tone mapping - float lp = Yxy.x / (9.6f * _Exposure + 0.0001f); - float new_lum = reinhard2(lp, _WhitePoint); - - // Clamp the added luminosity and convert back to RGB - Yxy.x += clamp(new_lum - Yxy.x, _Range.x, _Range.y); - rgb = convert_Yxy_to_rgb(Yxy); - - return float4(rgb, alpha); + float2 uv = input.texcoord.xy; + half4 color = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, sampler_LinearClamp, uv, _BlitMipLevel); + float luminance = GammaCorrect_half(SAMPLE_TEXTURE2D_X_LOD(_AutoExposureTexture, sampler_LinearRepeat, float2(0,0), _BlitMipLevel).x) ; + + return color * luminance; } + ENDHLSL } } diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AverageExposure.compute b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AverageExposure.compute deleted file mode 100644 index f82fb14..0000000 --- a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/AverageExposure.compute +++ /dev/null @@ -1,68 +0,0 @@ -#pragma kernel Main KERNEL_SCAN - -// 32 or 64 should be ok -#define THREAD_COUNT 32 - -// I/O -uint2 _Dims; // Screen Dimensions -Texture2D _Source; // Screen Texture -RWStructuredBuffer _OutBuffer; // Compute Group Output - -// Shared row values -groupshared float rows_lum[THREAD_COUNT]; -groupshared uint rows_pixels[THREAD_COUNT]; - -// Weird ass function for doing things with the luminosity, looks great on desmos -float weird_ass_function(float x) -{ - x += 0.1f; - float w = 32.0f; - return x * (x + w) / (w * x + 0.154f) - 0.137f; -} - -// Convert screen coordinates to the pixel's luminosity -float get_screen_lum(uint x, uint y) -{ - return dot(_Source[uint2(x, y)].rgb, float3(0.21267291f, 0.7151522f, 0.0721750f)); -} - -// Each thread corresponds to a Y coordinate on the screen and is in charge of the whole row -[numthreads(THREAD_COUNT, 1, 1)] -void Main(uint group : SV_GroupID, uint thread : SV_GroupThreadID, uint y : SV_DispatchThreadID) -{ - float lum_row = 0; - uint pixels = 0; - - // Get the total pixels and luminosity of each row - if (y < _Dims.y) - { - for (uint x = 0; x < _Dims.x; x++) - { - pixels++; - lum_row += get_screen_lum(x, y); - } - } - - // Store the results in the shared arrays - rows_lum[thread] = lum_row; - rows_pixels[thread] = pixels; - GroupMemoryBarrierWithGroupSync(); - - // The thread id:0 of each workgroup is in charge of adding upp all the values of the threads in its group - if (thread == 0) - { - float groupLumAvg = 0; - uint groupPixels = 0; - - // Add upp everything - for (int row = 0; row < THREAD_COUNT; row++) - { - groupLumAvg += rows_lum[row]; - groupPixels += rows_pixels[row]; - } - - // Store the group's luminosity and pixel count in the output buffer - _OutBuffer[group * 2] = groupLumAvg; - _OutBuffer[group * 2 + 1] = groupPixels; - } -} \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute new file mode 100644 index 0000000..ed0ee28 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute @@ -0,0 +1,134 @@ +#pragma warning(disable : 3568) + +#pragma kernel KEyeHistogram +#pragma kernel KEyeHistogram USE_VIGNETTE_WEIGHTING +#pragma kernel KEyeHistogramClear +#pragma enable_d3d11_debug_symbols +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" +#define EPSILON 1.0e-4 + +#define HISTOGRAM_BINS 128 +#define HISTOGRAM_TEXELS HISTOGRAM_BINS / 4 + +#if SHADER_API_GLES3 || SHADER_API_METAL +#define HISTOGRAM_THREAD_X 8 +#define HISTOGRAM_THREAD_Y 8 +#define HISTOGRAM_REDUCTION_THREAD_X 8 +#define HISTOGRAM_REDUCTION_THREAD_Y 8 +#define HISTOGRAM_REDUCTION_ALT_PATH 1 +#else +#define HISTOGRAM_THREAD_X 16 +#define HISTOGRAM_THREAD_Y 16 +#define HISTOGRAM_REDUCTION_THREAD_X HISTOGRAM_THREAD_X +#define HISTOGRAM_REDUCTION_THREAD_Y HISTOGRAM_BINS / HISTOGRAM_THREAD_Y +#define HISTOGRAM_REDUCTION_ALT_PATH 0 +#endif + +#define HISTOGRAM_REDUCTION_BINS HISTOGRAM_REDUCTION_THREAD_X * HISTOGRAM_REDUCTION_THREAD_Y + +half Luminance(half3 linearRgb) +{ + return dot(linearRgb, float3(0.2126729, 0.7151522, 0.0721750)); +} + +half Luminance(half4 linearRgba) +{ + return Luminance(linearRgba.rgb); +} +float GetHistogramBinFromLuminance(float value, float2 scaleOffset) +{ + return saturate(log2(value) * scaleOffset.x + scaleOffset.y); +} + +float GetLuminanceFromHistogramBin(float bin, float2 scaleOffset) +{ + return exp2((bin - scaleOffset.y) / scaleOffset.x); +} + +float GetBinValue(StructuredBuffer buffer, uint index, float maxHistogramValue) +{ + return float(buffer[index]) * maxHistogramValue; +} + +RWStructuredBuffer _HistogramBuffer; +Texture2D _Source; +SamplerState sampler_LinearClamp; + +cbuffer Params +{ + float4 _ScaleOffsetRes; // x: scale, y: offset, z: width, w: height +}; + +groupshared uint gs_histogram[HISTOGRAM_BINS]; + + +[numthreads(HISTOGRAM_THREAD_X, HISTOGRAM_THREAD_Y, 1)] +void KEyeHistogram(uint2 dispatchThreadId : SV_DispatchThreadID, uint2 groupThreadId : SV_GroupThreadID) +{ + const uint localThreadId = groupThreadId.y * HISTOGRAM_THREAD_X + groupThreadId.x; + + // Clears the shared memory +#if HISTOGRAM_REDUCTION_ALT_PATH + uint localThreadIdOff = localThreadId << 1u; + if (localThreadIdOff < HISTOGRAM_BINS) + { + gs_histogram[localThreadIdOff ] = 0u; + gs_histogram[localThreadIdOff + 1] = 0u; + } +#else + if (localThreadId < HISTOGRAM_BINS) + { + gs_histogram[localThreadId] = 0u; + } +#endif + + float2 ipos = float2(dispatchThreadId) * 2.0; + + GroupMemoryBarrierWithGroupSync(); + + // Gather local group histogram + if (ipos.x < _ScaleOffsetRes.z && ipos.y < _ScaleOffsetRes.w) + { + uint weight = 1u; + float2 sspos = ipos / _ScaleOffsetRes.zw; + + // Vignette weighting to put more focus on what's in the center of the screen +#if USE_VIGNETTE_WEIGHTING + { + float2 d = abs(sspos - (0.5).xx); + float vfactor = saturate(1.0 - dot(d, d)); + vfactor *= vfactor; + weight = (uint)(64.0 * vfactor); + } +#endif + + float3 color = _Source.SampleLevel(sampler_LinearClamp, sspos, 0.0).xyz; // Bilinear downsample 2x + float luminance = Luminance(color); + float logLuminance = GetHistogramBinFromLuminance(luminance, _ScaleOffsetRes.xy); + uint idx = (uint) (logLuminance * (HISTOGRAM_BINS - 1u)); + InterlockedAdd(gs_histogram[idx], weight); + } + + GroupMemoryBarrierWithGroupSync(); + + // Merge everything +#if HISTOGRAM_REDUCTION_ALT_PATH + if (localThreadIdOff < HISTOGRAM_BINS) + { + InterlockedAdd(_HistogramBuffer[localThreadIdOff ], gs_histogram[localThreadIdOff ]); + InterlockedAdd(_HistogramBuffer[localThreadIdOff + 1], gs_histogram[localThreadIdOff + 1]); + } +#else + if (localThreadId < HISTOGRAM_BINS) + { + InterlockedAdd(_HistogramBuffer[localThreadId], gs_histogram[localThreadId]); + } +#endif +} + +[numthreads(HISTOGRAM_THREAD_X, 1, 1)] +void KEyeHistogramClear(uint dispatchThreadId : SV_DispatchThreadID) +{ + if (dispatchThreadId < HISTOGRAM_BINS) + _HistogramBuffer[dispatchThreadId] = 0u; +} diff --git a/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute.meta b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute.meta new file mode 100644 index 0000000..ab725bb --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal@14.0.11/Runtime/XRenderFeatures/AutoExposure/Shader/ExposureHistogram.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b140e27dc74a1fb4d9ae30f8566b8919 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: