EyeAdaptationComponent.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. namespace UnityEngine.PostProcessing
  2. {
  3. public sealed class EyeAdaptationComponent : PostProcessingComponentRenderTexture<EyeAdaptationModel>
  4. {
  5. static class Uniforms
  6. {
  7. internal static readonly int _Params = Shader.PropertyToID("_Params");
  8. internal static readonly int _Speed = Shader.PropertyToID("_Speed");
  9. internal static readonly int _ScaleOffsetRes = Shader.PropertyToID("_ScaleOffsetRes");
  10. internal static readonly int _ExposureCompensation = Shader.PropertyToID("_ExposureCompensation");
  11. internal static readonly int _AutoExposure = Shader.PropertyToID("_AutoExposure");
  12. internal static readonly int _DebugWidth = Shader.PropertyToID("_DebugWidth");
  13. }
  14. ComputeShader m_EyeCompute;
  15. ComputeBuffer m_HistogramBuffer;
  16. readonly RenderTexture[] m_AutoExposurePool = new RenderTexture[2];
  17. int m_AutoExposurePingPing;
  18. RenderTexture m_CurrentAutoExposure;
  19. RenderTexture m_DebugHistogram;
  20. static uint[] s_EmptyHistogramBuffer;
  21. bool m_FirstFrame = true;
  22. // Don't forget to update 'EyeAdaptation.cginc' if you change these values !
  23. const int k_HistogramBins = 64;
  24. const int k_HistogramThreadX = 16;
  25. const int k_HistogramThreadY = 16;
  26. public override bool active
  27. {
  28. get
  29. {
  30. return model.enabled
  31. && SystemInfo.supportsComputeShaders
  32. && !context.interrupted;
  33. }
  34. }
  35. public void ResetHistory()
  36. {
  37. m_FirstFrame = true;
  38. }
  39. public override void OnEnable()
  40. {
  41. m_FirstFrame = true;
  42. }
  43. public override void OnDisable()
  44. {
  45. foreach (var rt in m_AutoExposurePool)
  46. GraphicsUtils.Destroy(rt);
  47. if (m_HistogramBuffer != null)
  48. m_HistogramBuffer.Release();
  49. m_HistogramBuffer = null;
  50. if (m_DebugHistogram != null)
  51. m_DebugHistogram.Release();
  52. m_DebugHistogram = null;
  53. }
  54. Vector4 GetHistogramScaleOffsetRes()
  55. {
  56. var settings = model.settings;
  57. float diff = settings.logMax - settings.logMin;
  58. float scale = 1f / diff;
  59. float offset = -settings.logMin * scale;
  60. return new Vector4(scale, offset, Mathf.Floor(context.width / 2f), Mathf.Floor(context.height / 2f));
  61. }
  62. public Texture Prepare(RenderTexture source, Material uberMaterial)
  63. {
  64. var settings = model.settings;
  65. // Setup compute
  66. if (m_EyeCompute == null)
  67. m_EyeCompute = Resources.Load<ComputeShader>("Shaders/EyeHistogram");
  68. var material = context.materialFactory.Get("Hidden/Post FX/Eye Adaptation");
  69. material.shaderKeywords = null;
  70. if (m_HistogramBuffer == null)
  71. m_HistogramBuffer = new ComputeBuffer(k_HistogramBins, sizeof(uint));
  72. if (s_EmptyHistogramBuffer == null)
  73. s_EmptyHistogramBuffer = new uint[k_HistogramBins];
  74. // Downscale the framebuffer, we don't need an absolute precision for auto exposure and it
  75. // helps making it more stable
  76. var scaleOffsetRes = GetHistogramScaleOffsetRes();
  77. var rt = context.renderTextureFactory.Get((int)scaleOffsetRes.z, (int)scaleOffsetRes.w, 0, source.format);
  78. Graphics.Blit(source, rt);
  79. if (m_AutoExposurePool[0] == null || !m_AutoExposurePool[0].IsCreated())
  80. m_AutoExposurePool[0] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
  81. if (m_AutoExposurePool[1] == null || !m_AutoExposurePool[1].IsCreated())
  82. m_AutoExposurePool[1] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
  83. // Clears the buffer on every frame as we use it to accumulate luminance values on each frame
  84. m_HistogramBuffer.SetData(s_EmptyHistogramBuffer);
  85. // Gets a log histogram
  86. int kernel = m_EyeCompute.FindKernel("KEyeHistogram");
  87. m_EyeCompute.SetBuffer(kernel, "_Histogram", m_HistogramBuffer);
  88. m_EyeCompute.SetTexture(kernel, "_Source", rt);
  89. m_EyeCompute.SetVector("_ScaleOffsetRes", scaleOffsetRes);
  90. m_EyeCompute.Dispatch(kernel, Mathf.CeilToInt(rt.width / (float)k_HistogramThreadX), Mathf.CeilToInt(rt.height / (float)k_HistogramThreadY), 1);
  91. // Cleanup
  92. context.renderTextureFactory.Release(rt);
  93. // Make sure filtering values are correct to avoid apocalyptic consequences
  94. const float minDelta = 1e-2f;
  95. settings.highPercent = Mathf.Clamp(settings.highPercent, 1f + minDelta, 99f);
  96. settings.lowPercent = Mathf.Clamp(settings.lowPercent, 1f, settings.highPercent - minDelta);
  97. // Compute auto exposure
  98. material.SetBuffer("_Histogram", m_HistogramBuffer); // No (int, buffer) overload for SetBuffer ?
  99. 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)));
  100. material.SetVector(Uniforms._Speed, new Vector2(settings.speedDown, settings.speedUp));
  101. material.SetVector(Uniforms._ScaleOffsetRes, scaleOffsetRes);
  102. material.SetFloat(Uniforms._ExposureCompensation, settings.keyValue);
  103. if (settings.dynamicKeyValue)
  104. material.EnableKeyword("AUTO_KEY_VALUE");
  105. if (m_FirstFrame || !Application.isPlaying)
  106. {
  107. // We don't want eye adaptation when not in play mode because the GameView isn't
  108. // animated, thus making it harder to tweak. Just use the final audo exposure value.
  109. m_CurrentAutoExposure = m_AutoExposurePool[0];
  110. Graphics.Blit(null, m_CurrentAutoExposure, material, (int)EyeAdaptationModel.EyeAdaptationType.Fixed);
  111. // Copy current exposure to the other pingpong target to avoid adapting from black
  112. Graphics.Blit(m_AutoExposurePool[0], m_AutoExposurePool[1]);
  113. }
  114. else
  115. {
  116. int pp = m_AutoExposurePingPing;
  117. var src = m_AutoExposurePool[++pp % 2];
  118. var dst = m_AutoExposurePool[++pp % 2];
  119. Graphics.Blit(src, dst, material, (int)settings.adaptationType);
  120. m_AutoExposurePingPing = ++pp % 2;
  121. m_CurrentAutoExposure = dst;
  122. }
  123. // Generate debug histogram
  124. if (context.profile.debugViews.IsModeActive(BuiltinDebugViewsModel.Mode.EyeAdaptation))
  125. {
  126. if (m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
  127. {
  128. m_DebugHistogram = new RenderTexture(256, 128, 0, RenderTextureFormat.ARGB32)
  129. {
  130. filterMode = FilterMode.Point,
  131. wrapMode = TextureWrapMode.Clamp
  132. };
  133. }
  134. material.SetFloat(Uniforms._DebugWidth, m_DebugHistogram.width);
  135. Graphics.Blit(null, m_DebugHistogram, material, 2);
  136. }
  137. m_FirstFrame = false;
  138. return m_CurrentAutoExposure;
  139. }
  140. public void OnGUI()
  141. {
  142. if (m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
  143. return;
  144. var rect = new Rect(context.viewport.x * Screen.width + 8f, 8f, m_DebugHistogram.width, m_DebugHistogram.height);
  145. GUI.DrawTexture(rect, m_DebugHistogram);
  146. }
  147. }
  148. }