update AutoExposure
This commit is contained in:
parent
d694bdc43b
commit
2d834cf06a
@ -4707,6 +4707,10 @@ PrefabInstance:
|
|||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: NestedParentArmature_Unpack
|
value: NestedParentArmature_Unpack
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8508310942573128499, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3}
|
||||||
|
propertyPath: m_RenderPostProcessing
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents:
|
m_RemovedComponents:
|
||||||
- {fileID: 3543048675319490572, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3}
|
- {fileID: 3543048675319490572, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3}
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,41 @@ MonoBehaviour:
|
|||||||
- {fileID: 5935297997615179469}
|
- {fileID: 5935297997615179469}
|
||||||
- {fileID: -4720686990112659349}
|
- {fileID: -4720686990112659349}
|
||||||
- {fileID: 9109530758031880643}
|
- {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
|
--- !u!114 &5935297997615179469
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 3
|
m_ObjectHideFlags: 3
|
||||||
|
|||||||
@ -75,7 +75,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 5a00a63fdd6bd2a45ab1f2d869305ffd, type: 3}
|
m_Script: {fileID: 11500000, guid: 5a00a63fdd6bd2a45ab1f2d869305ffd, type: 3}
|
||||||
m_Name: OasisFogVolumeComponent
|
m_Name: OasisFogVolumeComponent
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
active: 1
|
active: 0
|
||||||
Density:
|
Density:
|
||||||
m_OverrideState: 1
|
m_OverrideState: 1
|
||||||
m_Value: 0.002
|
m_Value: 0.002
|
||||||
@ -103,7 +103,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3}
|
m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3}
|
||||||
m_Name: ColorAdjustments
|
m_Name: ColorAdjustments
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
active: 1
|
active: 0
|
||||||
postExposure:
|
postExposure:
|
||||||
m_OverrideState: 1
|
m_OverrideState: 1
|
||||||
m_Value: 2
|
m_Value: 2
|
||||||
|
|||||||
@ -108,6 +108,25 @@ MonoBehaviour:
|
|||||||
cutoffThreshold: 0.2
|
cutoffThreshold: 0.2
|
||||||
binaryValue: 0.9
|
binaryValue: 0.9
|
||||||
flags: 13
|
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
|
--- !u!114 &-5418649131825517062
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@ -337,7 +356,8 @@ MonoBehaviour:
|
|||||||
- {fileID: 7541218312462517771}
|
- {fileID: 7541218312462517771}
|
||||||
- {fileID: 5808157236138506604}
|
- {fileID: 5808157236138506604}
|
||||||
- {fileID: -5418649131825517062}
|
- {fileID: -5418649131825517062}
|
||||||
m_RendererFeatureMap: bc3f630842f2e70dd6a559c442a94bfd4529d15534f2d3de228858dca8d12222716523fbf3439fdb7a327b7bff4bdd446ac59dfa966ffa88ca6373cd5da9013d6cff55ca297e5e908a7b3653203b82383b2141bb05fbe69aec5704e48e2763e90bc6ff9f19caa7686c79a6bb3bb89a50faad0fe75217cdb4
|
- {fileID: -7143664486661302651}
|
||||||
|
m_RendererFeatureMap: bc3f630842f2e70dd6a559c442a94bfd4529d15534f2d3de228858dca8d12222716523fbf3439fdb7a327b7bff4bdd446ac59dfa966ffa88ca6373cd5da9013d6cff55ca297e5e908a7b3653203b82383b2141bb05fbe69aec5704e48e2763e90bc6ff9f19caa7686c79a6bb3bb89a50faad0fe75217cdb485d6fa85ff9adc9c
|
||||||
m_UseNativeRenderPass: 0
|
m_UseNativeRenderPass: 0
|
||||||
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
|
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
|
||||||
shaders:
|
shaders:
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Experimental.Rendering;
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
using UnityEngine.Rendering.Universal;
|
using UnityEngine.Rendering.Universal;
|
||||||
|
|
||||||
@ -10,57 +12,131 @@ namespace X.Rendering.Feature
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class Settings
|
public class Settings
|
||||||
{
|
{
|
||||||
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
public RenderPassEvent HistogramRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
||||||
|
public RenderPassEvent AutoExposureRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
||||||
public Material BlitMat;
|
public Material BlitMat;
|
||||||
public ComputeShader Compute;
|
public ComputeShader ComputeHistogramComputeShader;
|
||||||
|
public ComputeShader AutoExposureComputeShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
Settings settings;
|
Settings settings;
|
||||||
AutoExposurePass autoExposurePass;
|
AutoExposurePass autoExposurePass;
|
||||||
|
ComputeLuminanceHistogramPass computeLuminanceHistogramPass;
|
||||||
|
|
||||||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
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);
|
renderer.EnqueuePass(autoExposurePass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Create()
|
public override void Create()
|
||||||
{
|
{
|
||||||
|
computeLuminanceHistogramPass = new(settings);
|
||||||
autoExposurePass = 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<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 Settings settings;
|
||||||
private int computeCounter;
|
private int computeCounter;
|
||||||
private ProfilingSampler profiler;
|
private ProfilingSampler profiler;
|
||||||
private ComputeBuffer outputBuffer;
|
|
||||||
|
|
||||||
private static readonly int Dims = Shader.PropertyToID("_Dims");
|
const int k_NumAutoExposureTextures = 2;
|
||||||
private static readonly int Source = Shader.PropertyToID("_Source");
|
private RTHandle[] autoExposureHandles;
|
||||||
private static readonly int TempOut = Shader.PropertyToID("_OutBuffer");
|
private int autoExposurePingPong;
|
||||||
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");
|
|
||||||
|
|
||||||
|
|
||||||
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 AutoExposureVolumeProfile autoExposureVolume = null;
|
||||||
private static float lastTime = 0.0f;
|
private bool reset = false;
|
||||||
private static bool autoExposureing = false;
|
|
||||||
|
|
||||||
public AutoExposurePass(Settings settings)
|
public AutoExposurePass(Settings settings)
|
||||||
{
|
{
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
renderPassEvent = settings.renderPassEvent;
|
renderPassEvent = settings.AutoExposureRenderPassEvent;
|
||||||
profiler = new(nameof(AutoExposurePass));
|
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)
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||||
@ -69,97 +145,89 @@ namespace X.Rendering.Feature
|
|||||||
autoExposureVolume = stack.GetComponent<AutoExposureVolumeProfile>();
|
autoExposureVolume = stack.GetComponent<AutoExposureVolumeProfile>();
|
||||||
if (!autoExposureVolume || !autoExposureVolume.IsActive())
|
if (!autoExposureVolume || !autoExposureVolume.IsActive())
|
||||||
{
|
{
|
||||||
|
reset = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = renderingData.commandBuffer;
|
var cmd = renderingData.commandBuffer;
|
||||||
using var scp = new ProfilingScope(cmd, profiler);
|
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;
|
||||||
computeCounter = 0;
|
float highPercent = filteringMinMax.y;
|
||||||
var textureDesc = renderingData.cameraData.cameraTargetDescriptor;
|
const float kMinDelta = 1e-2f;
|
||||||
|
highPercent = Mathf.Clamp(highPercent, 1f + kMinDelta, 99f);
|
||||||
|
lowPercent = Mathf.Clamp(lowPercent, 1f, highPercent - kMinDelta);
|
||||||
|
|
||||||
Vector2Int size = new(textureDesc.width, textureDesc.height);
|
// Clamp min/max adaptation values as well
|
||||||
if(outputBuffer == null || lastSize != size)
|
float minLum = autoExposureVolume.minLuminance.value;
|
||||||
{
|
float maxLum = autoExposureVolume.maxLuminance.value;
|
||||||
lastSize = size;
|
minLum = Mathf.Min(minLum, maxLum);
|
||||||
outputBuffer?.Dispose();
|
maxLum = Mathf.Max(minLum, maxLum);
|
||||||
outputBuffer = new ComputeBuffer(Mathf.CeilToInt((float)size.y / ThreadGroupSize * 2), 4, ComputeBufferType.Structured)
|
var speedDown = Mathf.Max(0, autoExposureVolume.speedDown.value);
|
||||||
{
|
var speedUp = Mathf.Max(0, autoExposureVolume.speedUp.value);
|
||||||
name = "Auto Exposure Output Buffer"
|
var exposureCompensation = autoExposureVolume.exposureCompensation.value;
|
||||||
};
|
string adaptation = null;
|
||||||
}
|
|
||||||
cmd.SetComputeIntParams(settings.Compute, Dims, size.x, size.y);
|
|
||||||
|
|
||||||
cmd.SetComputeTextureParam(settings.Compute, 0, Source, renderingData.cameraData.renderer.cameraColorTargetHandle);
|
if (autoExposureVolume.eyeAdaptation.value == AutoExposureVolumeProfile.EyeAdaptation.Fixed)
|
||||||
cmd.SetComputeBufferParam(settings.Compute, 0, TempOut, outputBuffer);
|
adaptation = "KAutoExposureAvgLuminance_fixed";
|
||||||
cmd.DispatchCompute(settings.Compute, 0, size.y, 1, 1);
|
else
|
||||||
AsyncGPUReadback.Request(outputBuffer, OnCompleteReadBack);
|
adaptation = "KAutoExposureAvgLuminance_progressive";
|
||||||
}
|
|
||||||
++computeCounter;
|
var compute = settings.AutoExposureComputeShader;
|
||||||
if (!autoExposureing)
|
int kernel = compute.FindKernel(adaptation);
|
||||||
|
int pp = autoExposurePingPong;
|
||||||
|
var src = autoExposureHandles[++pp % 2];
|
||||||
|
var dst = autoExposureHandles[++pp % 2];
|
||||||
|
|
||||||
|
if (reset)
|
||||||
{
|
{
|
||||||
//return;
|
cmd.SetRenderTarget(src, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store);
|
||||||
|
cmd.ClearRenderTarget(false, true, Color.clear);
|
||||||
}
|
}
|
||||||
UpdateExposure();
|
|
||||||
settings.BlitMat.SetFloat(ScreenExposureProp, currentExposure);
|
cmd.SetComputeBufferParam(compute, kernel, "_HistogramBuffer", ComputeLuminanceHistogramPass.HistogramBufferHandle);
|
||||||
settings.BlitMat.SetVector(ExposureRangeProp, autoExposureVolume.exposureRange.value);
|
cmd.SetComputeVectorParam(compute, "_Params1", new Vector4(lowPercent * 0.01f, highPercent * 0.01f, Mathf.Pow(2, minLum), Mathf.Pow(2, maxLum)));
|
||||||
settings.BlitMat.SetFloat(WhitePointProp, autoExposureVolume.whitePoint.value);
|
cmd.SetComputeVectorParam(compute, "_Params2", new Vector4(speedDown, speedUp, exposureCompensation, Time.smoothDeltaTime));
|
||||||
settings.BlitMat.SetFloat(MaxBrightness, autoExposureVolume.brightnessLimit.value);
|
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;
|
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);
|
var destination = renderer.GetCameraColorFrontBuffer(cmd);
|
||||||
cmd.SetRenderTarget(destination, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store);
|
cmd.SetRenderTarget(destination, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store);
|
||||||
cmd.DrawProcedural(Matrix4x4.identity, settings.BlitMat, 0, MeshTopology.Triangles, 3);
|
cmd.DrawProcedural(Matrix4x4.identity, settings.BlitMat, 0, MeshTopology.Triangles, 3);
|
||||||
renderer.SwapColorBuffer(cmd);
|
renderer.SwapColorBuffer(cmd);
|
||||||
|
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]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnCompleteReadBack(AsyncGPUReadbackRequest request)
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (request.hasError)
|
if (autoExposureHandles != null)
|
||||||
{
|
{
|
||||||
return;
|
foreach (var autoExposureHandle in autoExposureHandles)
|
||||||
}
|
|
||||||
|
|
||||||
// Get Compute result
|
|
||||||
float[] groupValues = request.GetData<float>().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];
|
autoExposureHandle?.Release();
|
||||||
pixels += (uint)Mathf.CeilToInt(groupValues[i * 2 + 1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Average the results and set as the target exposure
|
autoExposureHandles = null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,82 @@
|
|||||||
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
using UnityEngine.Rendering.Universal;
|
using UnityEngine.Rendering.Universal;
|
||||||
|
|
||||||
namespace X.Rendering.Feature
|
namespace X.Rendering.Feature
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public class AutoExposureVolumeProfile : VolumeComponent, IPostProcessComponent
|
public class AutoExposureVolumeProfile : VolumeComponent, IPostProcessComponent
|
||||||
{
|
{
|
||||||
[Tooltip("How many frames it takes to recalculate the average brightness. Decreasing this value can affect performance.")]
|
public const int rangeMin = -9; // ev
|
||||||
public ClampedIntParameter framesPerCompute = new(5, 1, 20);
|
public const int rangeMax = 9; // ev
|
||||||
|
|
||||||
[Space]
|
public static Vector4 GetHistogramScaleOffsetRes()
|
||||||
[Tooltip("Limits the maximum brightness for very bright objects to work properly with bloom and other effects")]
|
{
|
||||||
public FloatParameter brightnessLimit = new(15f);
|
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 enum EyeAdaptation
|
||||||
public FloatParameter whitePoint = new(3f);
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Progressive (smooth) eye adaptation.
|
||||||
|
/// </summary>
|
||||||
|
Progressive,
|
||||||
|
|
||||||
[Tooltip("How much an individual pixel's exposure can vary from the original value.")]
|
/// <summary>
|
||||||
public FloatRangeParameter exposureRange = new(new Vector2(-2.5f, 0.6f), -6.0f, 6.0f);
|
/// Fixed (instant) eye adaptation.
|
||||||
|
/// </summary>
|
||||||
|
Fixed
|
||||||
|
}
|
||||||
|
|
||||||
[Space]
|
[Serializable]
|
||||||
[Tooltip("How quickly the exposure increases")]
|
public sealed class EyeAdaptationParameter : VolumeParameter<EyeAdaptation> { }
|
||||||
public FloatParameter increaseSpeed = new(5f);
|
|
||||||
|
|
||||||
[Tooltip("How quickly the exposure decreases")]
|
[Range(1f, 99f)]
|
||||||
public FloatParameter decreaseSpeed = new(5f);
|
public Vector2Parameter filtering = new Vector2Parameter(new Vector2(50f, 95f));
|
||||||
|
|
||||||
public bool IsActive() => framesPerCompute.overrideState;
|
/// <summary>
|
||||||
|
/// Minimum average luminance to consider for auto exposure (in EV).
|
||||||
|
/// </summary>
|
||||||
|
[Range(rangeMin, rangeMax), InspectorName("Minimum (EV)")]
|
||||||
|
public FloatParameter minLuminance = new FloatParameter (0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum average luminance to consider for auto exposure (in EV).
|
||||||
|
/// </summary>
|
||||||
|
[Range(rangeMin, rangeMax), InspectorName("Maximum (EV)")]
|
||||||
|
public FloatParameter maxLuminance = new FloatParameter (0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Middle-grey value. Use this to compensate the global exposure of the scene.
|
||||||
|
/// </summary>
|
||||||
|
[Min(0f), InspectorName("Exposure Compensation"), Tooltip("Use this to scale the global exposure of the scene.")]
|
||||||
|
public FloatParameter exposureCompensation = new FloatParameter (1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of eye adaptation to use.
|
||||||
|
/// </summary>
|
||||||
|
[InspectorName("Type"), Tooltip("Use \"Progressive\" if you want auto exposure to be animated. Use \"Fixed\" otherwise.")]
|
||||||
|
public EyeAdaptationParameter eyeAdaptation = new EyeAdaptationParameter() { value = EyeAdaptation.Progressive };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The adaptation speed from a dark to a light environment.
|
||||||
|
/// </summary>
|
||||||
|
[Min(0f), Tooltip("Adaptation speed from a dark to a light environment.")]
|
||||||
|
public FloatParameter speedUp = new FloatParameter (2f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The adaptation speed from a light to a dark environment.
|
||||||
|
/// </summary>
|
||||||
|
[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()
|
public bool IsTileCompatible()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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<uint> buffer, uint index, float maxHistogramValue)
|
||||||
|
{
|
||||||
|
return float(buffer[index]) * maxHistogramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float FindMaxHistogramValue(StructuredBuffer<uint> 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<uint> 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<uint> 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<uint> _HistogramBuffer;
|
||||||
|
Texture2D<float> _Source;
|
||||||
|
RWTexture2D<float> _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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,66 +21,14 @@ Shader "XRP/AutoExposure"
|
|||||||
#pragma vertex vert
|
#pragma vertex vert
|
||||||
#pragma fragment frag
|
#pragma fragment frag
|
||||||
|
|
||||||
SamplerState sampler_BlitTexture; // Screen texture
|
TEXTURE2D_X(_SourceTexture);
|
||||||
|
TEXTURE2D_X(_AutoExposureTexture);
|
||||||
|
|
||||||
float _MaxBrightness; // Limit the final brightness to this value
|
half GammaCorrect_half(half linearColor)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
float3 xyz;
|
return pow(linearColor,1/2.2);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 vert(uint vertexID: SV_VertexID)
|
||||||
{
|
{
|
||||||
Varyings o;
|
Varyings o;
|
||||||
@ -89,27 +37,15 @@ Shader "XRP/AutoExposure"
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fragment shader, adaptation of: https://bruop.github.io/tonemapping/
|
|
||||||
float4 frag(Varyings input) : SV_Target
|
float4 frag(Varyings input) : SV_Target
|
||||||
{
|
{
|
||||||
float3 rgb;
|
float2 uv = input.texcoord.xy;
|
||||||
float alpha;
|
half4 color = SAMPLE_TEXTURE2D_X_LOD(_SourceTexture, sampler_LinearClamp, uv, _BlitMipLevel);
|
||||||
sample_split(input.texcoord, rgb, alpha);
|
float luminance = GammaCorrect_half(SAMPLE_TEXTURE2D_X_LOD(_AutoExposureTexture, sampler_LinearRepeat, float2(0,0), _BlitMipLevel).x) ;
|
||||||
|
|
||||||
// Yxy.x is Y, the luminance
|
return color * 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ENDHLSL
|
ENDHLSL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<float> _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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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<uint> buffer, uint index, float maxHistogramValue)
|
||||||
|
{
|
||||||
|
return float(buffer[index]) * maxHistogramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RWStructuredBuffer<uint> _HistogramBuffer;
|
||||||
|
Texture2D<float4> _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;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b140e27dc74a1fb4d9ae30f8566b8919
|
||||||
|
ComputeShaderImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Loading…
x
Reference in New Issue
Block a user