ContrastStretchEffect.cs 5.8 KB

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