| | |
| | | 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 _ScaleOffsetRes = Shader.PropertyToID("_ScaleOffsetRes"); |
| | | internal static readonly int _ExposureCompensation = Shader.PropertyToID("_ExposureCompensation"); |
| | | internal static readonly int _AutoExposure = Shader.PropertyToID("_AutoExposure"); |
| | | internal static readonly int _DebugWidth = Shader.PropertyToID("_DebugWidth"); |
| | | } |
| | | |
| | | ComputeShader m_EyeCompute; |
| | | ComputeBuffer m_HistogramBuffer; |
| | | |
| | | readonly RenderTexture[] m_AutoExposurePool = new RenderTexture[2]; |
| | | int m_AutoExposurePingPing; |
| | | RenderTexture m_CurrentAutoExposure; |
| | | |
| | | RenderTexture m_DebugHistogram; |
| | | |
| | | static uint[] s_EmptyHistogramBuffer; |
| | | |
| | | bool m_FirstFrame = true; |
| | | |
| | | // 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 != null && model.enabled |
| | | && SystemInfo.supportsComputeShaders |
| | | && !context.interrupted; |
| | | } |
| | | } |
| | | |
| | | public void ResetHistory() |
| | | { |
| | | m_FirstFrame = true; |
| | | } |
| | | |
| | | public override void OnEnable() |
| | | { |
| | | m_FirstFrame = true; |
| | | } |
| | | |
| | | 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)); |
| | | |
| | | if (s_EmptyHistogramBuffer == null) |
| | | s_EmptyHistogramBuffer = new uint[k_HistogramBins]; |
| | | |
| | | // 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); |
| | | |
| | | // Clears the buffer on every frame as we use it to accumulate luminance values on each frame |
| | | m_HistogramBuffer.SetData(s_EmptyHistogramBuffer); |
| | | |
| | | // Gets a log histogram |
| | | int kernel = m_EyeCompute.FindKernel("KEyeHistogram"); |
| | | m_EyeCompute.SetBuffer(kernel, "_Histogram", m_HistogramBuffer); |
| | | m_EyeCompute.SetTexture(kernel, "_Source", rt); |
| | | m_EyeCompute.SetVector("_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("_Histogram", m_HistogramBuffer); // No (int, buffer) overload for SetBuffer ? |
| | | 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_FirstFrame || !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_FirstFrame = false; |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | 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 _ScaleOffsetRes = Shader.PropertyToID("_ScaleOffsetRes");
|
| | | internal static readonly int _ExposureCompensation = Shader.PropertyToID("_ExposureCompensation");
|
| | | internal static readonly int _AutoExposure = Shader.PropertyToID("_AutoExposure");
|
| | | internal static readonly int _DebugWidth = Shader.PropertyToID("_DebugWidth");
|
| | | }
|
| | |
|
| | | ComputeShader m_EyeCompute;
|
| | | ComputeBuffer m_HistogramBuffer;
|
| | |
|
| | | readonly RenderTexture[] m_AutoExposurePool = new RenderTexture[2];
|
| | | int m_AutoExposurePingPing;
|
| | | RenderTexture m_CurrentAutoExposure;
|
| | |
|
| | | RenderTexture m_DebugHistogram;
|
| | |
|
| | | static uint[] s_EmptyHistogramBuffer;
|
| | |
|
| | | bool m_FirstFrame = true;
|
| | |
|
| | | // 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 != null && model.enabled
|
| | | && SystemInfo.supportsComputeShaders
|
| | | && !context.interrupted;
|
| | | }
|
| | | }
|
| | |
|
| | | public void ResetHistory()
|
| | | {
|
| | | m_FirstFrame = true;
|
| | | }
|
| | |
|
| | | public override void OnEnable()
|
| | | {
|
| | | m_FirstFrame = true;
|
| | | }
|
| | |
|
| | | 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));
|
| | |
|
| | | if (s_EmptyHistogramBuffer == null)
|
| | | s_EmptyHistogramBuffer = new uint[k_HistogramBins];
|
| | |
|
| | | // 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);
|
| | |
|
| | | // Clears the buffer on every frame as we use it to accumulate luminance values on each frame
|
| | | m_HistogramBuffer.SetData(s_EmptyHistogramBuffer);
|
| | |
|
| | | // Gets a log histogram
|
| | | int kernel = m_EyeCompute.FindKernel("KEyeHistogram");
|
| | | m_EyeCompute.SetBuffer(kernel, "_Histogram", m_HistogramBuffer);
|
| | | m_EyeCompute.SetTexture(kernel, "_Source", rt);
|
| | | m_EyeCompute.SetVector("_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("_Histogram", m_HistogramBuffer); // No (int, buffer) overload for SetBuffer ?
|
| | | 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_FirstFrame || !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_FirstFrame = false;
|
| | | 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);
|
| | | }
|
| | | }
|
| | | }
|