2025-07-21 12:13:06 +08:00
|
|
|
using System;
|
2025-07-21 18:10:06 +08:00
|
|
|
using System.Drawing.Drawing2D;
|
2025-07-21 12:13:06 +08:00
|
|
|
using UnityEngine;
|
2025-07-21 18:10:06 +08:00
|
|
|
using UnityEngine.Experimental.Rendering;
|
2025-07-21 12:13:06 +08:00
|
|
|
using UnityEngine.Rendering;
|
|
|
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
|
|
|
|
|
|
namespace X.Rendering.Feature
|
|
|
|
|
{
|
|
|
|
|
public class AutoExposure : ScriptableRendererFeature
|
|
|
|
|
{
|
|
|
|
|
[Serializable]
|
|
|
|
|
public class Settings
|
|
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
public RenderPassEvent HistogramRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
|
|
|
public RenderPassEvent AutoExposureRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
2025-07-21 12:13:06 +08:00
|
|
|
public Material BlitMat;
|
2025-07-21 18:10:06 +08:00
|
|
|
public ComputeShader ComputeHistogramComputeShader;
|
|
|
|
|
public ComputeShader AutoExposureComputeShader;
|
2025-07-21 12:13:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[SerializeField]
|
|
|
|
|
Settings settings;
|
|
|
|
|
AutoExposurePass autoExposurePass;
|
2025-07-21 18:10:06 +08:00
|
|
|
ComputeLuminanceHistogramPass computeLuminanceHistogramPass;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
if(renderingData.cameraData.isSceneViewCamera || renderingData.cameraData.isPreviewCamera)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
renderer.EnqueuePass(computeLuminanceHistogramPass);
|
2025-07-21 12:13:06 +08:00
|
|
|
renderer.EnqueuePass(autoExposurePass);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Create()
|
|
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
computeLuminanceHistogramPass = new(settings);
|
2025-07-21 12:13:06 +08:00
|
|
|
autoExposurePass = new(settings);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
autoExposurePass?.Dispose();
|
|
|
|
|
computeLuminanceHistogramPass?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ComputeLuminanceHistogramPass : ScriptableRenderPass, IDisposable
|
2025-07-21 12:13:06 +08:00
|
|
|
{
|
|
|
|
|
private ProfilingSampler profiler;
|
2025-07-21 18:10:06 +08:00
|
|
|
private Settings settings;
|
|
|
|
|
private GraphicsBuffer histogramBuffer;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
public const int k_Bins = 128;
|
|
|
|
|
public static GraphicsBufferHandle HistogramBufferHandle;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
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<AutoExposureVolumeProfile>();
|
|
|
|
|
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;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
const int k_NumAutoExposureTextures = 2;
|
|
|
|
|
private RTHandle[] autoExposureHandles;
|
|
|
|
|
private int autoExposurePingPong;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
|
|
|
|
private static AutoExposureVolumeProfile autoExposureVolume = null;
|
2025-07-21 18:10:06 +08:00
|
|
|
private bool reset = false;
|
2025-07-21 12:13:06 +08:00
|
|
|
|
|
|
|
|
public AutoExposurePass(Settings settings)
|
|
|
|
|
{
|
|
|
|
|
this.settings = settings;
|
2025-07-21 18:10:06 +08:00
|
|
|
renderPassEvent = settings.AutoExposureRenderPassEvent;
|
2025-07-21 12:13:06 +08:00
|
|
|
profiler = new(nameof(AutoExposurePass));
|
2025-07-21 18:10:06 +08:00
|
|
|
|
|
|
|
|
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}");
|
|
|
|
|
}
|
2025-07-21 12:13:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
|
|
|
|
{
|
|
|
|
|
var stack = VolumeManager.instance.stack;
|
|
|
|
|
autoExposureVolume = stack.GetComponent<AutoExposureVolumeProfile>();
|
|
|
|
|
if (!autoExposureVolume || !autoExposureVolume.IsActive())
|
|
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
reset = true;
|
2025-07-21 12:13:06 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cmd = renderingData.commandBuffer;
|
|
|
|
|
using var scp = new ProfilingScope(cmd, profiler);
|
2025-07-21 18:10:06 +08:00
|
|
|
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)
|
2025-07-21 12:13:06 +08:00
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
cmd.SetRenderTarget(src, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store);
|
|
|
|
|
cmd.ClearRenderTarget(false, true, Color.clear);
|
2025-07-21 12:13:06 +08:00
|
|
|
}
|
2025-07-21 18:10:06 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
2025-07-21 12:13:06 +08:00
|
|
|
var renderer = renderingData.cameraData.renderer;
|
2025-07-21 18:10:06 +08:00
|
|
|
settings.BlitMat.SetTexture("_SourceTexture", renderer.cameraColorTargetHandle);
|
|
|
|
|
settings.BlitMat.SetTexture("_AutoExposureTexture", dst);
|
2025-07-21 12:13:06 +08:00
|
|
|
var destination = renderer.GetCameraColorFrontBuffer(cmd);
|
2025-07-22 15:39:27 +08:00
|
|
|
|
2025-07-21 12:13:06 +08:00
|
|
|
cmd.SetRenderTarget(destination, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store);
|
|
|
|
|
cmd.DrawProcedural(Matrix4x4.identity, settings.BlitMat, 0, MeshTopology.Triangles, 3);
|
|
|
|
|
renderer.SwapColorBuffer(cmd);
|
2025-07-21 18:10:06 +08:00
|
|
|
reset = false;
|
|
|
|
|
//cmd.RequestAsyncReadback(dst, (a) =>
|
|
|
|
|
//{
|
|
|
|
|
// if (!a.hasError)
|
|
|
|
|
// {
|
|
|
|
|
// var data = a.GetData<float>();
|
|
|
|
|
// for (var i = 0; i < data.Length; i++)
|
|
|
|
|
// {
|
|
|
|
|
// Debug.Log(data[i]);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//});
|
2025-07-21 12:13:06 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
|
|
|
|
|
public void Dispose()
|
2025-07-21 12:13:06 +08:00
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
if (autoExposureHandles != null)
|
2025-07-21 12:13:06 +08:00
|
|
|
{
|
2025-07-21 18:10:06 +08:00
|
|
|
foreach (var autoExposureHandle in autoExposureHandles)
|
|
|
|
|
{
|
|
|
|
|
autoExposureHandle?.Release();
|
|
|
|
|
}
|
2025-07-21 12:13:06 +08:00
|
|
|
|
2025-07-21 18:10:06 +08:00
|
|
|
autoExposureHandles = null;
|
2025-07-21 12:13:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|