FiE-Game/Assets/PostProcessing/Runtime/Components/EyeAdaptationComponent.cs
2023-07-25 00:52:50 +05:00

192 lines
8.4 KiB
C#

namespace UnityEngine.PostProcessing
{
public sealed class EyeAdaptationComponent : PostProcessingComponentRenderTexture<EyeAdaptationModel>
{
static class Uniforms
{
internal static readonly int _Params = Shader.PropertyToID("_Params");
internal static readonly int _Speed = Shader.PropertyToID("_Speed");
internal static readonly int _ExposureCompensation = Shader.PropertyToID("_ExposureCompensation");
internal static readonly int _AutoExposure = Shader.PropertyToID("_AutoExposure");
internal static readonly int _DebugWidth = Shader.PropertyToID("_DebugWidth");
//The overloads for the PropertyID werent completely added until 5.5.1, so use int where possible but fallback to strings if required.
#if UNITY_5_6_OR_NEWER
internal static readonly int _Histogram = Shader.PropertyToID("_Histogram");
internal static readonly int _Source = Shader.PropertyToID("_Source");
internal static readonly int _ScaleOffsetRes = Shader.PropertyToID("_ScaleOffsetRes");
#else
internal static readonly string _Histogram = "_Histogram";
internal static readonly string _Source = "_Source";
internal static readonly string _ScaleOffsetRes = "_ScaleOffsetRes";
#endif
}
ComputeShader m_EyeCompute;
ComputeBuffer m_HistogramBuffer;
readonly RenderTexture[] m_AutoExposurePool = new RenderTexture[2];
int m_AutoExposurePingPing;
RenderTexture m_CurrentAutoExposure;
RenderTexture m_DebugHistogram;
int m_FrameCount = 0;
// Don't forget to update 'EyeAdaptation.cginc' if you change these values !
const int k_HistogramBins = 64;
const int k_HistogramThreadX = 16;
const int k_HistogramThreadY = 16;
public override bool active
{
get
{
return model.enabled
&& SystemInfo.supportsComputeShaders
&& !context.interrupted;
}
}
public void ResetHistory()
{
m_FrameCount = 0;
}
public override void OnEnable()
{
m_FrameCount = 0;
}
public override void OnDisable()
{
foreach (var rt in m_AutoExposurePool)
GraphicsUtils.Destroy(rt);
if (m_HistogramBuffer != null)
m_HistogramBuffer.Release();
m_HistogramBuffer = null;
if (m_DebugHistogram != null)
m_DebugHistogram.Release();
m_DebugHistogram = null;
}
Vector4 GetHistogramScaleOffsetRes()
{
var settings = model.settings;
float diff = settings.logMax - settings.logMin;
float scale = 1f / diff;
float offset = -settings.logMin * scale;
return new Vector4(scale, offset, Mathf.Floor(context.width / 2f), Mathf.Floor(context.height / 2f));
}
public Texture Prepare(RenderTexture source, Material uberMaterial)
{
var settings = model.settings;
// Setup compute
if (m_EyeCompute == null)
m_EyeCompute = Resources.Load<ComputeShader>("Shaders/EyeHistogram");
var material = context.materialFactory.Get("Hidden/Post FX/Eye Adaptation");
material.shaderKeywords = null;
if (m_HistogramBuffer == null)
m_HistogramBuffer = new ComputeBuffer(k_HistogramBins, sizeof(uint));
// Downscale the framebuffer, we don't need an absolute precision for auto exposure and it
// helps making it more stable
var scaleOffsetRes = GetHistogramScaleOffsetRes();
var rt = context.renderTextureFactory.Get((int)scaleOffsetRes.z, (int)scaleOffsetRes.w, 0, source.format);
Graphics.Blit(source, rt);
if (m_AutoExposurePool[0] == null || !m_AutoExposurePool[0].IsCreated())
m_AutoExposurePool[0] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
if (m_AutoExposurePool[1] == null || !m_AutoExposurePool[1].IsCreated())
m_AutoExposurePool[1] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
// Clear the buffer on every frame as we use it to accumulate luminance values on each frame
int kernel = m_EyeCompute.FindKernel("KEyeHistogramClear");
m_EyeCompute.SetBuffer(kernel, Uniforms._Histogram, m_HistogramBuffer);
m_EyeCompute.Dispatch(kernel, Mathf.CeilToInt(k_HistogramBins / (float)k_HistogramThreadX), 1, 1);
// Gets a log histogram
kernel = m_EyeCompute.FindKernel("KEyeHistogram");
m_EyeCompute.SetBuffer(kernel, Uniforms._Histogram, m_HistogramBuffer);
m_EyeCompute.SetTexture(kernel, Uniforms._Source, rt);
m_EyeCompute.SetVector(Uniforms._ScaleOffsetRes, scaleOffsetRes);
m_EyeCompute.Dispatch(kernel, Mathf.CeilToInt(rt.width / (float)k_HistogramThreadX), Mathf.CeilToInt(rt.height / (float)k_HistogramThreadY), 1);
// Cleanup
context.renderTextureFactory.Release(rt);
// Make sure filtering values are correct to avoid apocalyptic consequences
const float minDelta = 1e-2f;
settings.highPercent = Mathf.Clamp(settings.highPercent, 1f + minDelta, 99f);
settings.lowPercent = Mathf.Clamp(settings.lowPercent, 1f, settings.highPercent - minDelta);
// Compute auto exposure
material.SetBuffer(Uniforms._Histogram, m_HistogramBuffer);
material.SetVector(Uniforms._Params, new Vector4(settings.lowPercent * 0.01f, settings.highPercent * 0.01f, Mathf.Exp(settings.minLuminance * 0.69314718055994530941723212145818f), Mathf.Exp(settings.maxLuminance * 0.69314718055994530941723212145818f)));
material.SetVector(Uniforms._Speed, new Vector2(settings.speedDown, settings.speedUp));
material.SetVector(Uniforms._ScaleOffsetRes, scaleOffsetRes);
material.SetFloat(Uniforms._ExposureCompensation, settings.keyValue);
if (settings.dynamicKeyValue)
material.EnableKeyword("AUTO_KEY_VALUE");
if (m_FrameCount < 2 || !Application.isPlaying)
{
// We don't want eye adaptation when not in play mode because the GameView isn't
// animated, thus making it harder to tweak. Just use the final audo exposure value.
m_CurrentAutoExposure = m_AutoExposurePool[0];
Graphics.Blit(null, m_CurrentAutoExposure, material, (int)EyeAdaptationModel.EyeAdaptationType.Fixed);
// Copy current exposure to the other pingpong target to avoid adapting from black
Graphics.Blit(m_AutoExposurePool[0], m_AutoExposurePool[1]);
}
else
{
int pp = m_AutoExposurePingPing;
var src = m_AutoExposurePool[++pp % 2];
var dst = m_AutoExposurePool[++pp % 2];
Graphics.Blit(src, dst, material, (int)settings.adaptationType);
m_AutoExposurePingPing = ++pp % 2;
m_CurrentAutoExposure = dst;
}
// Generate debug histogram
if (context.profile.debugViews.IsModeActive(BuiltinDebugViewsModel.Mode.EyeAdaptation))
{
if (m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
{
m_DebugHistogram = new RenderTexture(256, 128, 0, RenderTextureFormat.ARGB32)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
}
material.SetFloat(Uniforms._DebugWidth, m_DebugHistogram.width);
Graphics.Blit(null, m_DebugHistogram, material, 2);
}
m_FrameCount++;
return m_CurrentAutoExposure;
}
public void OnGUI()
{
if (m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
return;
var rect = new Rect(context.viewport.x * Screen.width + 8f, 8f, m_DebugHistogram.width, m_DebugHistogram.height);
GUI.DrawTexture(rect, m_DebugHistogram);
}
}
}