AutoExposure.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. using System;
  2. namespace UnityEngine.Rendering.PostProcessing
  3. {
  4. /// <summary>
  5. /// Eye adaptation modes.
  6. /// </summary>
  7. public enum EyeAdaptation
  8. {
  9. /// <summary>
  10. /// Progressive (smooth) eye adaptation.
  11. /// </summary>
  12. Progressive,
  13. /// <summary>
  14. /// Fixed (instant) eye adaptation.
  15. /// </summary>
  16. Fixed
  17. }
  18. /// <summary>
  19. /// A volume parameter holding a <see cref="EyeAdaptation"/> value.
  20. /// </summary>
  21. [Serializable]
  22. public sealed class EyeAdaptationParameter : ParameterOverride<EyeAdaptation> {}
  23. /// <summary>
  24. /// This class holds settings for the Auto Exposure effect.
  25. /// </summary>
  26. [Serializable]
  27. [PostProcess(typeof(AutoExposureRenderer), "Unity/Auto Exposure")]
  28. public sealed class AutoExposure : PostProcessEffectSettings
  29. {
  30. /// <summary>
  31. /// These values are the lower and upper percentages of the histogram that will be used to
  32. /// find a stable average luminance. Values outside of this range will be discarded and wont
  33. /// contribute to the average luminance.
  34. /// </summary>
  35. [MinMax(1f, 99f), DisplayName("Filtering (%)"), Tooltip("Filters the bright and dark parts of the histogram when computing the average luminance. This is to avoid very dark pixels and very bright pixels from contributing to the auto exposure. Unit is in percent.")]
  36. public Vector2Parameter filtering = new Vector2Parameter { value = new Vector2(50f, 95f) };
  37. /// <summary>
  38. /// Minimum average luminance to consider for auto exposure (in EV).
  39. /// </summary>
  40. [Range(LogHistogram.rangeMin, LogHistogram.rangeMax), DisplayName("Minimum (EV)"), Tooltip("Minimum average luminance to consider for auto exposure. Unit is EV.")]
  41. public FloatParameter minLuminance = new FloatParameter { value = 0f };
  42. /// <summary>
  43. /// Maximum average luminance to consider for auto exposure (in EV).
  44. /// </summary>
  45. [Range(LogHistogram.rangeMin, LogHistogram.rangeMax), DisplayName("Maximum (EV)"), Tooltip("Maximum average luminance to consider for auto exposure. Unit is EV.")]
  46. public FloatParameter maxLuminance = new FloatParameter { value = 0f };
  47. /// <summary>
  48. /// Middle-grey value. Use this to compensate the global exposure of the scene.
  49. /// </summary>
  50. [Min(0f), DisplayName("Exposure Compensation"), Tooltip("Use this to scale the global exposure of the scene.")]
  51. public FloatParameter keyValue = new FloatParameter { value = 1f };
  52. /// <summary>
  53. /// The type of eye adaptation to use.
  54. /// </summary>
  55. [DisplayName("Type"), Tooltip("Use \"Progressive\" if you want auto exposure to be animated. Use \"Fixed\" otherwise.")]
  56. public EyeAdaptationParameter eyeAdaptation = new EyeAdaptationParameter { value = EyeAdaptation.Progressive };
  57. /// <summary>
  58. /// The adaptation speed from a dark to a light environment.
  59. /// </summary>
  60. [Min(0f), Tooltip("Adaptation speed from a dark to a light environment.")]
  61. public FloatParameter speedUp = new FloatParameter { value = 2f };
  62. /// <summary>
  63. /// The adaptation speed from a light to a dark environment.
  64. /// </summary>
  65. [Min(0f), Tooltip("Adaptation speed from a light to a dark environment.")]
  66. public FloatParameter speedDown = new FloatParameter { value = 1f };
  67. /// <inheritdoc />
  68. public override bool IsEnabledAndSupported(PostProcessRenderContext context)
  69. {
  70. return enabled.value
  71. && SystemInfo.supportsComputeShaders
  72. && !RuntimeUtilities.isAndroidOpenGL
  73. && RenderTextureFormat.RFloat.IsSupported()
  74. && context.resources.computeShaders.autoExposure
  75. && context.resources.computeShaders.exposureHistogram;
  76. }
  77. }
  78. internal sealed class AutoExposureRenderer : PostProcessEffectRenderer<AutoExposure>
  79. {
  80. const int k_NumEyes = 2;
  81. const int k_NumAutoExposureTextures = 2;
  82. readonly RenderTexture[][] m_AutoExposurePool = new RenderTexture[k_NumEyes][];
  83. int[] m_AutoExposurePingPong = new int[k_NumEyes];
  84. RenderTexture m_CurrentAutoExposure;
  85. public AutoExposureRenderer()
  86. {
  87. for (int eye = 0; eye < k_NumEyes; eye++)
  88. {
  89. m_AutoExposurePool[eye] = new RenderTexture[k_NumAutoExposureTextures];
  90. m_AutoExposurePingPong[eye] = 0;
  91. }
  92. }
  93. void CheckTexture(int eye, int id)
  94. {
  95. if (m_AutoExposurePool[eye][id] == null || !m_AutoExposurePool[eye][id].IsCreated())
  96. {
  97. m_AutoExposurePool[eye][id] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat) { enableRandomWrite = true };
  98. m_AutoExposurePool[eye][id].Create();
  99. }
  100. }
  101. public override void Render(PostProcessRenderContext context)
  102. {
  103. var cmd = context.command;
  104. cmd.BeginSample("AutoExposureLookup");
  105. // Prepare autoExpo texture pool
  106. CheckTexture(context.xrActiveEye, 0);
  107. CheckTexture(context.xrActiveEye, 1);
  108. // Make sure filtering values are correct to avoid apocalyptic consequences
  109. float lowPercent = settings.filtering.value.x;
  110. float highPercent = settings.filtering.value.y;
  111. const float kMinDelta = 1e-2f;
  112. highPercent = Mathf.Clamp(highPercent, 1f + kMinDelta, 99f);
  113. lowPercent = Mathf.Clamp(lowPercent, 1f, highPercent - kMinDelta);
  114. // Clamp min/max adaptation values as well
  115. float minLum = settings.minLuminance.value;
  116. float maxLum = settings.maxLuminance.value;
  117. settings.minLuminance.value = Mathf.Min(minLum, maxLum);
  118. settings.maxLuminance.value = Mathf.Max(minLum, maxLum);
  119. // Compute average luminance & auto exposure
  120. bool firstFrame = m_ResetHistory || !Application.isPlaying;
  121. string adaptation = null;
  122. if (firstFrame || settings.eyeAdaptation.value == EyeAdaptation.Fixed)
  123. adaptation = "KAutoExposureAvgLuminance_fixed";
  124. else
  125. adaptation = "KAutoExposureAvgLuminance_progressive";
  126. var compute = context.resources.computeShaders.autoExposure;
  127. int kernel = compute.FindKernel(adaptation);
  128. cmd.SetComputeBufferParam(compute, kernel, "_HistogramBuffer", context.logHistogram.data);
  129. cmd.SetComputeVectorParam(compute, "_Params1", new Vector4(lowPercent * 0.01f, highPercent * 0.01f, RuntimeUtilities.Exp2(settings.minLuminance.value), RuntimeUtilities.Exp2(settings.maxLuminance.value)));
  130. cmd.SetComputeVectorParam(compute, "_Params2", new Vector4(settings.speedDown.value, settings.speedUp.value, settings.keyValue.value, Time.deltaTime));
  131. cmd.SetComputeVectorParam(compute, "_ScaleOffsetRes", context.logHistogram.GetHistogramScaleOffsetRes(context));
  132. if (firstFrame)
  133. {
  134. // We don't want eye adaptation when not in play mode because the GameView isn't
  135. // animated, thus making it harder to tweak. Just use the final audo exposure value.
  136. m_CurrentAutoExposure = m_AutoExposurePool[context.xrActiveEye][0];
  137. cmd.SetComputeTextureParam(compute, kernel, "_Destination", m_CurrentAutoExposure);
  138. cmd.DispatchCompute(compute, kernel, 1, 1, 1);
  139. // Copy current exposure to the other pingpong target to avoid adapting from black
  140. RuntimeUtilities.CopyTexture(cmd, m_AutoExposurePool[context.xrActiveEye][0], m_AutoExposurePool[context.xrActiveEye][1]);
  141. m_ResetHistory = false;
  142. }
  143. else
  144. {
  145. int pp = m_AutoExposurePingPong[context.xrActiveEye];
  146. var src = m_AutoExposurePool[context.xrActiveEye][++pp % 2];
  147. var dst = m_AutoExposurePool[context.xrActiveEye][++pp % 2];
  148. cmd.SetComputeTextureParam(compute, kernel, "_Source", src);
  149. cmd.SetComputeTextureParam(compute, kernel, "_Destination", dst);
  150. cmd.DispatchCompute(compute, kernel, 1, 1, 1);
  151. m_AutoExposurePingPong[context.xrActiveEye] = ++pp % 2;
  152. m_CurrentAutoExposure = dst;
  153. }
  154. cmd.EndSample("AutoExposureLookup");
  155. context.autoExposureTexture = m_CurrentAutoExposure;
  156. context.autoExposure = settings;
  157. }
  158. public override void Release()
  159. {
  160. foreach (var rtEyeSet in m_AutoExposurePool)
  161. {
  162. foreach (var rt in rtEyeSet)
  163. RuntimeUtilities.Destroy(rt);
  164. }
  165. }
  166. }
  167. }