ContrastStretch.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using System;
  2. using UnityEngine;
  3. namespace UnityStandardAssets.ImageEffects
  4. {
  5. [ExecuteInEditMode]
  6. [AddComponentMenu("Image Effects/Color Adjustments/Contrast Stretch")]
  7. public class ContrastStretch : MonoBehaviour
  8. {
  9. /// Adaptation speed - percents per frame, if playing at 30FPS.
  10. /// Default is 0.02 (2% each 1/30s).
  11. [Range(0.0001f, 1.0f)]
  12. public float adaptationSpeed = 0.02f;
  13. /// If our scene is really dark (or really bright), we might not want to
  14. /// stretch its contrast to the full range.
  15. /// limitMinimum=0, limitMaximum=1 is the same as not applying the effect at all.
  16. /// limitMinimum=1, limitMaximum=0 is always stretching colors to full range.
  17. /// The limit on the minimum luminance (0...1) - we won't go above this.
  18. [Range(0.0f,1.0f)]
  19. public float limitMinimum = 0.2f;
  20. /// The limit on the maximum luminance (0...1) - we won't go below this.
  21. [Range(0.0f, 1.0f)]
  22. public float limitMaximum = 0.6f;
  23. // To maintain adaptation levels over time, we need two 1x1 render textures
  24. // and ping-pong between them.
  25. private RenderTexture[] adaptRenderTex = new RenderTexture[2];
  26. private int curAdaptIndex = 0;
  27. // Computes scene luminance (grayscale) image
  28. public Shader shaderLum;
  29. private Material m_materialLum;
  30. protected Material materialLum {
  31. get {
  32. if ( m_materialLum == null ) {
  33. m_materialLum = new Material(shaderLum);
  34. m_materialLum.hideFlags = HideFlags.HideAndDontSave;
  35. }
  36. return m_materialLum;
  37. }
  38. }
  39. // Reduces size of the image by 2x2, while computing maximum/minimum values.
  40. // By repeatedly applying this shader, we reduce the initial luminance image
  41. // to 1x1 image with minimum/maximum luminances found.
  42. public Shader shaderReduce;
  43. private Material m_materialReduce;
  44. protected Material materialReduce {
  45. get {
  46. if ( m_materialReduce == null ) {
  47. m_materialReduce = new Material(shaderReduce);
  48. m_materialReduce.hideFlags = HideFlags.HideAndDontSave;
  49. }
  50. return m_materialReduce;
  51. }
  52. }
  53. // Adaptation shader - gradually "adapts" minimum/maximum luminances,
  54. // based on currently adapted 1x1 image and the actual 1x1 image of the current scene.
  55. public Shader shaderAdapt;
  56. private Material m_materialAdapt;
  57. protected Material materialAdapt {
  58. get {
  59. if ( m_materialAdapt == null ) {
  60. m_materialAdapt = new Material(shaderAdapt);
  61. m_materialAdapt.hideFlags = HideFlags.HideAndDontSave;
  62. }
  63. return m_materialAdapt;
  64. }
  65. }
  66. // Final pass - stretches the color values of the original scene, based on currently
  67. // adpated minimum/maximum values.
  68. public Shader shaderApply;
  69. private Material m_materialApply;
  70. protected Material materialApply {
  71. get {
  72. if ( m_materialApply == null ) {
  73. m_materialApply = new Material(shaderApply);
  74. m_materialApply.hideFlags = HideFlags.HideAndDontSave;
  75. }
  76. return m_materialApply;
  77. }
  78. }
  79. void Start()
  80. {
  81. // Disable if we don't support image effects
  82. if (!SystemInfo.supportsImageEffects) {
  83. enabled = false;
  84. return;
  85. }
  86. if (!shaderAdapt.isSupported || !shaderApply.isSupported || !shaderLum.isSupported || !shaderReduce.isSupported) {
  87. enabled = false;
  88. return;
  89. }
  90. }
  91. void OnEnable()
  92. {
  93. for( int i = 0; i < 2; ++i )
  94. {
  95. if ( !adaptRenderTex[i] ) {
  96. adaptRenderTex[i] = new RenderTexture(1, 1, 0);
  97. adaptRenderTex[i].hideFlags = HideFlags.HideAndDontSave;
  98. }
  99. }
  100. }
  101. void OnDisable()
  102. {
  103. for( int i = 0; i < 2; ++i )
  104. {
  105. DestroyImmediate( adaptRenderTex[i] );
  106. adaptRenderTex[i] = null;
  107. }
  108. if ( m_materialLum )
  109. DestroyImmediate( m_materialLum );
  110. if ( m_materialReduce )
  111. DestroyImmediate( m_materialReduce );
  112. if ( m_materialAdapt )
  113. DestroyImmediate( m_materialAdapt );
  114. if ( m_materialApply )
  115. DestroyImmediate( m_materialApply );
  116. }
  117. /// Apply the filter
  118. void OnRenderImage (RenderTexture source, RenderTexture destination)
  119. {
  120. // Blit to smaller RT and convert to luminance on the way
  121. const int TEMP_RATIO = 1; // 4x4 smaller
  122. RenderTexture rtTempSrc = RenderTexture.GetTemporary(source.width/TEMP_RATIO, source.height/TEMP_RATIO);
  123. Graphics.Blit (source, rtTempSrc, materialLum);
  124. // Repeatedly reduce this image in size, computing min/max luminance values
  125. // In the end we'll have 1x1 image with min/max luminances found.
  126. const int FINAL_SIZE = 1;
  127. //const int FINAL_SIZE = 1;
  128. while( rtTempSrc.width > FINAL_SIZE || rtTempSrc.height > FINAL_SIZE )
  129. {
  130. const int REDUCE_RATIO = 2; // our shader does 2x2 reduction
  131. int destW = rtTempSrc.width / REDUCE_RATIO;
  132. if ( destW < FINAL_SIZE ) destW = FINAL_SIZE;
  133. int destH = rtTempSrc.height / REDUCE_RATIO;
  134. if ( destH < FINAL_SIZE ) destH = FINAL_SIZE;
  135. RenderTexture rtTempDst = RenderTexture.GetTemporary(destW,destH);
  136. Graphics.Blit (rtTempSrc, rtTempDst, materialReduce);
  137. // Release old src temporary, and make new temporary the source
  138. RenderTexture.ReleaseTemporary( rtTempSrc );
  139. rtTempSrc = rtTempDst;
  140. }
  141. // Update viewer's adaptation level
  142. CalculateAdaptation( rtTempSrc );
  143. // Apply contrast strech to the original scene, using currently adapted parameters
  144. materialApply.SetTexture("_AdaptTex", adaptRenderTex[curAdaptIndex] );
  145. Graphics.Blit (source, destination, materialApply);
  146. RenderTexture.ReleaseTemporary( rtTempSrc );
  147. }
  148. /// Helper function to do gradual adaptation to min/max luminances
  149. private void CalculateAdaptation( Texture curTexture )
  150. {
  151. int prevAdaptIndex = curAdaptIndex;
  152. curAdaptIndex = (curAdaptIndex+1) % 2;
  153. // Adaptation speed is expressed in percents/frame, based on 30FPS.
  154. // Calculate the adaptation lerp, based on current FPS.
  155. float adaptLerp = 1.0f - Mathf.Pow( 1.0f - adaptationSpeed, 30.0f * Time.deltaTime );
  156. const float kMinAdaptLerp = 0.01f;
  157. adaptLerp = Mathf.Clamp( adaptLerp, kMinAdaptLerp, 1 );
  158. materialAdapt.SetTexture("_CurTex", curTexture );
  159. materialAdapt.SetVector("_AdaptParams", new Vector4(
  160. adaptLerp,
  161. limitMinimum,
  162. limitMaximum,
  163. 0.0f
  164. ));
  165. // clear destination RT so its contents don't need to be restored
  166. Graphics.SetRenderTarget(adaptRenderTex[curAdaptIndex]);
  167. GL.Clear(false, true, Color.black);
  168. Graphics.Blit (
  169. adaptRenderTex[prevAdaptIndex],
  170. adaptRenderTex[curAdaptIndex],
  171. materialAdapt);
  172. }
  173. }
  174. }