1 using System;
2 using
UnityEngine;
3
4 namespace
UnityStandardAssets.ImageEffects
5 {
6     
[ExecuteInEditMode]
7     
[AddComponentMenu("Image Effects/Color Adjustments/Contrast Stretch")]
8     
public class ContrastStretch : MonoBehaviour
9     {

10         ///
Adaptation speed - percents per frame, if playing at 30FPS.
11         ///
Default is 0.02 (2% each 1/30s).
12         
[Range(0.0001f, 1.0f)]
13         
public float adaptationSpeed = 0.02f;
14
15         ///
If our scene is really dark (or really bright), we might not want to
16         ///
stretch its contrast to the full range.
17         ///
limitMinimum=0, limitMaximum=1 is the same as not applying the effect at all.
18         ///
limitMinimum=1, limitMaximum=0 is always stretching colors to full range.
19
20         ///
The limit on the minimum luminance (0...1) - we won't go above this.
21         
[Range(0.0f,1.0f)]
22         
public float limitMinimum = 0.2f;
23
24         ///
The limit on the maximum luminance (0...1) - we won't go below this.
25         
[Range(0.0f, 1.0f)]
26         
public float limitMaximum = 0.6f;
27
28
29         
// To maintain adaptation levels over time, we need two 1x1 render textures
30         
// and ping-pong between them.
31         
private RenderTexture[] adaptRenderTex = new RenderTexture[2];
32         
private int curAdaptIndex = 0;
33
34
35         
// Computes scene luminance (grayscale) image
36         
public Shader shaderLum;
37         
private Material m_materialLum;
38         
protected Material materialLum {
39             
get {
40                 
if ( m_materialLum == null ) {
41                     m_materialLum =
new Material(shaderLum);
42                     m_materialLum.hideFlags = HideFlags.HideAndDontSave;
43                 }
44                 
return m_materialLum;
45             }
46         }
47
48         
// Reduces size of the image by 2x2, while computing maximum/minimum values.
49         
// By repeatedly applying this shader, we reduce the initial luminance image
50         
// to 1x1 image with minimum/maximum luminances found.
51         
public Shader shaderReduce;
52         
private Material m_materialReduce;
53         
protected Material materialReduce {
54             
get {
55                 
if ( m_materialReduce == null ) {
56                     m_materialReduce =
new Material(shaderReduce);
57                     m_materialReduce.hideFlags = HideFlags.HideAndDontSave;
58                 }
59                 
return m_materialReduce;
60             }
61         }
62
63         
// Adaptation shader - gradually "adapts" minimum/maximum luminances,
64         
// based on currently adapted 1x1 image and the actual 1x1 image of the current scene.
65         
public Shader shaderAdapt;
66         
private Material m_materialAdapt;
67         
protected Material materialAdapt {
68             
get {
69                 
if ( m_materialAdapt == null ) {
70                     m_materialAdapt =
new Material(shaderAdapt);
71                     m_materialAdapt.hideFlags = HideFlags.HideAndDontSave;
72                 }
73                 
return m_materialAdapt;
74             }
75         }
76
77         
// Final pass - stretches the color values of the original scene, based on currently
78         
// adpated minimum/maximum values.
79         
public Shader shaderApply;
80         
private Material m_materialApply;
81         
protected Material materialApply {
82             
get {
83                 
if ( m_materialApply == null ) {
84                     m_materialApply =
new Material(shaderApply);
85                     m_materialApply.hideFlags = HideFlags.HideAndDontSave;
86                 }
87                 
return m_materialApply;
88             }
89         }
90
91         
void Start()
92         {
93             
// Disable if we don't support image effects
94             
if (!SystemInfo.supportsImageEffects) {
95                 enabled =
false;
96                 
return;
97             }
98
99             
if (!shaderAdapt.isSupported || !shaderApply.isSupported || !shaderLum.isSupported || !shaderReduce.isSupported) {
100                 enabled =
false;
101                 
return;
102             }
103         }
104
105         
void OnEnable()
106         {
107             
for( int i = 0; i < 2; ++i )
108             {
109                 
if ( !adaptRenderTex[i] ) {
110                     adaptRenderTex[i] =
new RenderTexture(1, 1, 0);
111                     adaptRenderTex[i].hideFlags = HideFlags.HideAndDontSave;
112                 }
113             }
114         }
115
116         
void OnDisable()
117         {
118             
for( int i = 0; i < 2; ++i )
119             {
120                 DestroyImmediate( adaptRenderTex[i] );
121                 adaptRenderTex[i] =
null;
122             }
123             
if ( m_materialLum )
124                 DestroyImmediate( m_materialLum );
125             
if ( m_materialReduce )
126                 DestroyImmediate( m_materialReduce );
127             
if ( m_materialAdapt )
128                 DestroyImmediate( m_materialAdapt );
129             
if ( m_materialApply )
130                 DestroyImmediate( m_materialApply );
131         }

132
133
134         ///
Apply the filter
135         
void OnRenderImage (RenderTexture source, RenderTexture destination)
136         {
137             
// Blit to smaller RT and convert to luminance on the way
138             
const int TEMP_RATIO = 1; // 4x4 smaller
139             RenderTexture rtTempSrc = RenderTexture.GetTemporary(source.width/TEMP_RATIO, source.height/TEMP_RATIO);
140             Graphics.Blit (source, rtTempSrc, materialLum);
141
142             
// Repeatedly reduce this image in size, computing min/max luminance values
143             
// In the end we'll have 1x1 image with min/max luminances found.
144             
const int FINAL_SIZE = 1;
145             
//const int FINAL_SIZE = 1;
146             
while( rtTempSrc.width > FINAL_SIZE || rtTempSrc.height > FINAL_SIZE )
147             {
148                 
const int REDUCE_RATIO = 2; // our shader does 2x2 reduction
149                 
int destW = rtTempSrc.width / REDUCE_RATIO;
150                 
if ( destW < FINAL_SIZE ) destW = FINAL_SIZE;
151                 
int destH = rtTempSrc.height / REDUCE_RATIO;
152                 
if ( destH < FINAL_SIZE ) destH = FINAL_SIZE;
153                 RenderTexture rtTempDst = RenderTexture.GetTemporary(destW,destH);
154                 Graphics.Blit (rtTempSrc, rtTempDst, materialReduce);
155
156                 
// Release old src temporary, and make new temporary the source
157                 RenderTexture.ReleaseTemporary( rtTempSrc );
158                 rtTempSrc = rtTempDst;
159             }
160
161             
// Update viewer's adaptation level
162             CalculateAdaptation( rtTempSrc );
163
164             
// Apply contrast strech to the original scene, using currently adapted parameters
165             materialApply.SetTexture(
"_AdaptTex", adaptRenderTex[curAdaptIndex] );
166             Graphics.Blit (source, destination, materialApply);
167
168             RenderTexture.ReleaseTemporary( rtTempSrc );
169         }

170
171
172         ///
Helper function to do gradual adaptation to min/max luminances
173         
private void CalculateAdaptation( Texture curTexture )
174         {
175             
int prevAdaptIndex = curAdaptIndex;
176             curAdaptIndex = (curAdaptIndex+
1) % 2;
177
178             
// Adaptation speed is expressed in percents/frame, based on 30FPS.
179             
// Calculate the adaptation lerp, based on current FPS.
180             
float adaptLerp = 1.0f - Mathf.Pow( 1.0f - adaptationSpeed, 30.0f * Time.deltaTime );
181             
const float kMinAdaptLerp = 0.01f;
182             adaptLerp = Mathf.Clamp( adaptLerp, kMinAdaptLerp,
1 );
183
184             materialAdapt.SetTexture(
"_CurTex", curTexture );
185             materialAdapt.SetVector(
"_AdaptParams", new Vector4(
186                                                         adaptLerp,
187                                                         limitMinimum,
188                                                         limitMaximum,
189                                                         
0.0f
190                                                         ));
191             
// clear destination RT so its contents don't need to be restored
192             Graphics.SetRenderTarget(adaptRenderTex[curAdaptIndex]);
193             GL.Clear(
false, true, Color.black);
194             Graphics.Blit (
195                 adaptRenderTex[prevAdaptIndex],
196                 adaptRenderTex[curAdaptIndex],
197                 materialAdapt);
198         }
199     }
200 }