236 lines
10 KiB
C#
Raw Normal View History

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);
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
}
}
}
}
}