Bloom.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. using System;
  2. using UnityEngine.Serialization;
  3. namespace UnityEngine.Rendering.PostProcessing
  4. {
  5. // For now and by popular request, this bloom effect is geared toward artists so they have full
  6. // control over how it looks at the expense of physical correctness.
  7. // Eventually we will need a "true" natural bloom effect with proper energy conservation.
  8. /// <summary>
  9. /// This class holds settings for the Bloom effect.
  10. /// </summary>
  11. [Serializable]
  12. [PostProcess(typeof(BloomRenderer), "Unity/Bloom")]
  13. public sealed class Bloom : PostProcessEffectSettings
  14. {
  15. /// <summary>
  16. /// The strength of the bloom filter.
  17. /// </summary>
  18. [Min(0f), Tooltip("Strength of the bloom filter. Values higher than 1 will make bloom contribute more energy to the final render.")]
  19. public FloatParameter intensity = new FloatParameter { value = 0f };
  20. /// <summary>
  21. /// Filters out pixels under this level of brightness. This value is expressed in
  22. /// gamma-space.
  23. /// </summary>
  24. [Min(0f), Tooltip("Filters out pixels under this level of brightness. Value is in gamma-space.")]
  25. public FloatParameter threshold = new FloatParameter { value = 1f };
  26. /// <summary>
  27. /// Makes transition between under/over-threshold gradual (0 = hard threshold, 1 = soft
  28. /// threshold).
  29. /// </summary>
  30. [Range(0f, 1f), Tooltip("Makes transitions between under/over-threshold gradual. 0 for a hard threshold, 1 for a soft threshold).")]
  31. public FloatParameter softKnee = new FloatParameter { value = 0.5f };
  32. /// <summary>
  33. /// Clamps pixels to control the bloom amount. This value is expressed in gamma-space.
  34. /// </summary>
  35. [Tooltip("Clamps pixels to control the bloom amount. Value is in gamma-space.")]
  36. public FloatParameter clamp = new FloatParameter { value = 65472f };
  37. /// <summary>
  38. /// Changes extent of veiling effects in a screen resolution-independent fashion. For
  39. /// maximum quality stick to integer values. Because this value changes the internal
  40. /// iteration count, animating it isn't recommended as it may introduce small hiccups in
  41. /// the perceived radius.
  42. /// </summary>
  43. [Range(1f, 10f), Tooltip("Changes the extent of veiling effects. For maximum quality, use integer values. Because this value changes the internal iteration count, You should not animating it as it may introduce issues with the perceived radius.")]
  44. public FloatParameter diffusion = new FloatParameter { value = 7f };
  45. /// <summary>
  46. /// Distorts the bloom to give an anamorphic look. Negative values distort vertically,
  47. /// positive values distort horizontally.
  48. /// </summary>
  49. [Range(-1f, 1f), Tooltip("Distorts the bloom to give an anamorphic look. Negative values distort vertically, positive values distort horizontally.")]
  50. public FloatParameter anamorphicRatio = new FloatParameter { value = 0f };
  51. /// <summary>
  52. /// The tint of the Bloom filter.
  53. /// </summary>
  54. #if UNITY_2018_1_OR_NEWER
  55. [ColorUsage(false, true), Tooltip("Global tint of the bloom filter.")]
  56. #else
  57. [ColorUsage(false, true, 0f, 8f, 0.125f, 3f), Tooltip("Global tint of the bloom filter.")]
  58. #endif
  59. public ColorParameter color = new ColorParameter { value = Color.white };
  60. /// <summary>
  61. /// Boost performances by lowering the effect quality.
  62. /// </summary>
  63. [FormerlySerializedAs("mobileOptimized")]
  64. [Tooltip("Boost performance by lowering the effect quality. This settings is meant to be used on mobile and other low-end platforms but can also provide a nice performance boost on desktops and consoles.")]
  65. public BoolParameter fastMode = new BoolParameter { value = false };
  66. /// <summary>
  67. /// The dirtiness texture to add smudges or dust to the lens.
  68. /// </summary>
  69. [Tooltip("The lens dirt texture used to add smudges or dust to the bloom effect."), DisplayName("Texture")]
  70. public TextureParameter dirtTexture = new TextureParameter { value = null };
  71. /// <summary>
  72. /// The amount of lens dirtiness.
  73. /// </summary>
  74. [Min(0f), Tooltip("The intensity of the lens dirtiness."), DisplayName("Intensity")]
  75. public FloatParameter dirtIntensity = new FloatParameter { value = 0f };
  76. /// <inheritdoc />
  77. public override bool IsEnabledAndSupported(PostProcessRenderContext context)
  78. {
  79. return enabled.value
  80. && intensity.value > 0f;
  81. }
  82. }
  83. internal sealed class BloomRenderer : PostProcessEffectRenderer<Bloom>
  84. {
  85. enum Pass
  86. {
  87. Prefilter13,
  88. Prefilter4,
  89. Downsample13,
  90. Downsample4,
  91. UpsampleTent,
  92. UpsampleBox,
  93. DebugOverlayThreshold,
  94. DebugOverlayTent,
  95. DebugOverlayBox
  96. }
  97. // [down,up]
  98. Level[] m_Pyramid;
  99. const int k_MaxPyramidSize = 16; // Just to make sure we handle 64k screens... Future-proof!
  100. struct Level
  101. {
  102. internal int down;
  103. internal int up;
  104. }
  105. public override void Init()
  106. {
  107. m_Pyramid = new Level[k_MaxPyramidSize];
  108. for (int i = 0; i < k_MaxPyramidSize; i++)
  109. {
  110. m_Pyramid[i] = new Level
  111. {
  112. down = Shader.PropertyToID("_BloomMipDown" + i),
  113. up = Shader.PropertyToID("_BloomMipUp" + i)
  114. };
  115. }
  116. }
  117. public override void Render(PostProcessRenderContext context)
  118. {
  119. var cmd = context.command;
  120. cmd.BeginSample("BloomPyramid");
  121. var sheet = context.propertySheets.Get(context.resources.shaders.bloom);
  122. // Apply auto exposure adjustment in the prefiltering pass
  123. sheet.properties.SetTexture(ShaderIDs.AutoExposureTex, context.autoExposureTexture);
  124. // Negative anamorphic ratio values distort vertically - positive is horizontal
  125. float ratio = Mathf.Clamp(settings.anamorphicRatio, -1, 1);
  126. float rw = ratio < 0 ? -ratio : 0f;
  127. float rh = ratio > 0 ? ratio : 0f;
  128. // Do bloom on a half-res buffer, full-res doesn't bring much and kills performances on
  129. // fillrate limited platforms
  130. int tw = Mathf.FloorToInt(context.screenWidth / (2f - rw));
  131. int th = Mathf.FloorToInt(context.screenHeight / (2f - rh));
  132. bool singlePassDoubleWide = (context.stereoActive && (context.stereoRenderingMode == PostProcessRenderContext.StereoRenderingMode.SinglePass) && (context.camera.stereoTargetEye == StereoTargetEyeMask.Both));
  133. int tw_stereo = singlePassDoubleWide ? tw * 2 : tw;
  134. // Determine the iteration count
  135. int s = Mathf.Max(tw, th);
  136. float logs = Mathf.Log(s, 2f) + Mathf.Min(settings.diffusion.value, 10f) - 10f;
  137. int logs_i = Mathf.FloorToInt(logs);
  138. int iterations = Mathf.Clamp(logs_i, 1, k_MaxPyramidSize);
  139. float sampleScale = 0.5f + logs - logs_i;
  140. sheet.properties.SetFloat(ShaderIDs.SampleScale, sampleScale);
  141. // Prefiltering parameters
  142. float lthresh = Mathf.GammaToLinearSpace(settings.threshold.value);
  143. float knee = lthresh * settings.softKnee.value + 1e-5f;
  144. var threshold = new Vector4(lthresh, lthresh - knee, knee * 2f, 0.25f / knee);
  145. sheet.properties.SetVector(ShaderIDs.Threshold, threshold);
  146. float lclamp = Mathf.GammaToLinearSpace(settings.clamp.value);
  147. sheet.properties.SetVector(ShaderIDs.Params, new Vector4(lclamp, 0f, 0f, 0f));
  148. int qualityOffset = settings.fastMode ? 1 : 0;
  149. // Downsample
  150. var lastDown = context.source;
  151. for (int i = 0; i < iterations; i++)
  152. {
  153. int mipDown = m_Pyramid[i].down;
  154. int mipUp = m_Pyramid[i].up;
  155. int pass = i == 0
  156. ? (int)Pass.Prefilter13 + qualityOffset
  157. : (int)Pass.Downsample13 + qualityOffset;
  158. context.GetScreenSpaceTemporaryRT(cmd, mipDown, 0, context.sourceFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, tw_stereo, th);
  159. context.GetScreenSpaceTemporaryRT(cmd, mipUp, 0, context.sourceFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, tw_stereo, th);
  160. cmd.BlitFullscreenTriangle(lastDown, mipDown, sheet, pass);
  161. lastDown = mipDown;
  162. tw_stereo = (singlePassDoubleWide && ((tw_stereo / 2) % 2 > 0)) ? 1 + tw_stereo / 2 : tw_stereo / 2;
  163. tw_stereo = Mathf.Max(tw_stereo, 1);
  164. th = Mathf.Max(th / 2, 1);
  165. }
  166. // Upsample
  167. int lastUp = m_Pyramid[iterations - 1].down;
  168. for (int i = iterations - 2; i >= 0; i--)
  169. {
  170. int mipDown = m_Pyramid[i].down;
  171. int mipUp = m_Pyramid[i].up;
  172. cmd.SetGlobalTexture(ShaderIDs.BloomTex, mipDown);
  173. cmd.BlitFullscreenTriangle(lastUp, mipUp, sheet, (int)Pass.UpsampleTent + qualityOffset);
  174. lastUp = mipUp;
  175. }
  176. var linearColor = settings.color.value.linear;
  177. float intensity = RuntimeUtilities.Exp2(settings.intensity.value / 10f) - 1f;
  178. var shaderSettings = new Vector4(sampleScale, intensity, settings.dirtIntensity.value, iterations);
  179. // Debug overlays
  180. if (context.IsDebugOverlayEnabled(DebugOverlay.BloomThreshold))
  181. {
  182. context.PushDebugOverlay(cmd, context.source, sheet, (int)Pass.DebugOverlayThreshold);
  183. }
  184. else if (context.IsDebugOverlayEnabled(DebugOverlay.BloomBuffer))
  185. {
  186. sheet.properties.SetVector(ShaderIDs.ColorIntensity, new Vector4(linearColor.r, linearColor.g, linearColor.b, intensity));
  187. context.PushDebugOverlay(cmd, m_Pyramid[0].up, sheet, (int)Pass.DebugOverlayTent + qualityOffset);
  188. }
  189. // Lens dirtiness
  190. // Keep the aspect ratio correct & center the dirt texture, we don't want it to be
  191. // stretched or squashed
  192. var dirtTexture = settings.dirtTexture.value == null
  193. ? RuntimeUtilities.blackTexture
  194. : settings.dirtTexture.value;
  195. var dirtRatio = (float)dirtTexture.width / (float)dirtTexture.height;
  196. var screenRatio = (float)context.screenWidth / (float)context.screenHeight;
  197. var dirtTileOffset = new Vector4(1f, 1f, 0f, 0f);
  198. if (dirtRatio > screenRatio)
  199. {
  200. dirtTileOffset.x = screenRatio / dirtRatio;
  201. dirtTileOffset.z = (1f - dirtTileOffset.x) * 0.5f;
  202. }
  203. else if (screenRatio > dirtRatio)
  204. {
  205. dirtTileOffset.y = dirtRatio / screenRatio;
  206. dirtTileOffset.w = (1f - dirtTileOffset.y) * 0.5f;
  207. }
  208. // Shader properties
  209. var uberSheet = context.uberSheet;
  210. if (settings.fastMode)
  211. uberSheet.EnableKeyword("BLOOM_LOW");
  212. else
  213. uberSheet.EnableKeyword("BLOOM");
  214. uberSheet.properties.SetVector(ShaderIDs.Bloom_DirtTileOffset, dirtTileOffset);
  215. uberSheet.properties.SetVector(ShaderIDs.Bloom_Settings, shaderSettings);
  216. uberSheet.properties.SetColor(ShaderIDs.Bloom_Color, linearColor);
  217. uberSheet.properties.SetTexture(ShaderIDs.Bloom_DirtTex, dirtTexture);
  218. cmd.SetGlobalTexture(ShaderIDs.BloomTex, lastUp);
  219. // Cleanup
  220. for (int i = 0; i < iterations; i++)
  221. {
  222. if (m_Pyramid[i].down != lastUp)
  223. cmd.ReleaseTemporaryRT(m_Pyramid[i].down);
  224. if (m_Pyramid[i].up != lastUp)
  225. cmd.ReleaseTemporaryRT(m_Pyramid[i].up);
  226. }
  227. cmd.EndSample("BloomPyramid");
  228. context.bloomBufferNameID = lastUp;
  229. }
  230. }
  231. }