You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
7.9 KiB
200 lines
7.9 KiB
using System; |
|
using UnityEngine; |
|
|
|
namespace UnityStandardAssets.ImageEffects |
|
{ |
|
[ExecuteInEditMode] |
|
[AddComponentMenu("Image Effects/Color Adjustments/Contrast Stretch")] |
|
public class ContrastStretch : MonoBehaviour |
|
{ |
|
/// Adaptation speed - percents per frame, if playing at 30FPS. |
|
/// Default is 0.02 (2% each 1/30s). |
|
[Range(0.0001f, 1.0f)] |
|
public float adaptationSpeed = 0.02f; |
|
|
|
/// If our scene is really dark (or really bright), we might not want to |
|
/// stretch its contrast to the full range. |
|
/// limitMinimum=0, limitMaximum=1 is the same as not applying the effect at all. |
|
/// limitMinimum=1, limitMaximum=0 is always stretching colors to full range. |
|
|
|
/// The limit on the minimum luminance (0...1) - we won't go above this. |
|
[Range(0.0f,1.0f)] |
|
public float limitMinimum = 0.2f; |
|
|
|
/// The limit on the maximum luminance (0...1) - we won't go below this. |
|
[Range(0.0f, 1.0f)] |
|
public float limitMaximum = 0.6f; |
|
|
|
|
|
// To maintain adaptation levels over time, we need two 1x1 render textures |
|
// and ping-pong between them. |
|
private RenderTexture[] adaptRenderTex = new RenderTexture[2]; |
|
private int curAdaptIndex = 0; |
|
|
|
|
|
// Computes scene luminance (grayscale) image |
|
public Shader shaderLum; |
|
private Material m_materialLum; |
|
protected Material materialLum { |
|
get { |
|
if ( m_materialLum == null ) { |
|
m_materialLum = new Material(shaderLum); |
|
m_materialLum.hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
return m_materialLum; |
|
} |
|
} |
|
|
|
// Reduces size of the image by 2x2, while computing maximum/minimum values. |
|
// By repeatedly applying this shader, we reduce the initial luminance image |
|
// to 1x1 image with minimum/maximum luminances found. |
|
public Shader shaderReduce; |
|
private Material m_materialReduce; |
|
protected Material materialReduce { |
|
get { |
|
if ( m_materialReduce == null ) { |
|
m_materialReduce = new Material(shaderReduce); |
|
m_materialReduce.hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
return m_materialReduce; |
|
} |
|
} |
|
|
|
// Adaptation shader - gradually "adapts" minimum/maximum luminances, |
|
// based on currently adapted 1x1 image and the actual 1x1 image of the current scene. |
|
public Shader shaderAdapt; |
|
private Material m_materialAdapt; |
|
protected Material materialAdapt { |
|
get { |
|
if ( m_materialAdapt == null ) { |
|
m_materialAdapt = new Material(shaderAdapt); |
|
m_materialAdapt.hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
return m_materialAdapt; |
|
} |
|
} |
|
|
|
// Final pass - stretches the color values of the original scene, based on currently |
|
// adpated minimum/maximum values. |
|
public Shader shaderApply; |
|
private Material m_materialApply; |
|
protected Material materialApply { |
|
get { |
|
if ( m_materialApply == null ) { |
|
m_materialApply = new Material(shaderApply); |
|
m_materialApply.hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
return m_materialApply; |
|
} |
|
} |
|
|
|
void Start() |
|
{ |
|
// Disable if we don't support image effects |
|
if (!SystemInfo.supportsImageEffects) { |
|
enabled = false; |
|
return; |
|
} |
|
|
|
if (!shaderAdapt.isSupported || !shaderApply.isSupported || !shaderLum.isSupported || !shaderReduce.isSupported) { |
|
enabled = false; |
|
return; |
|
} |
|
} |
|
|
|
void OnEnable() |
|
{ |
|
for( int i = 0; i < 2; ++i ) |
|
{ |
|
if ( !adaptRenderTex[i] ) { |
|
adaptRenderTex[i] = new RenderTexture(1, 1, 0); |
|
adaptRenderTex[i].hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
} |
|
} |
|
|
|
void OnDisable() |
|
{ |
|
for( int i = 0; i < 2; ++i ) |
|
{ |
|
DestroyImmediate( adaptRenderTex[i] ); |
|
adaptRenderTex[i] = null; |
|
} |
|
if ( m_materialLum ) |
|
DestroyImmediate( m_materialLum ); |
|
if ( m_materialReduce ) |
|
DestroyImmediate( m_materialReduce ); |
|
if ( m_materialAdapt ) |
|
DestroyImmediate( m_materialAdapt ); |
|
if ( m_materialApply ) |
|
DestroyImmediate( m_materialApply ); |
|
} |
|
|
|
|
|
/// Apply the filter |
|
void OnRenderImage (RenderTexture source, RenderTexture destination) |
|
{ |
|
// Blit to smaller RT and convert to luminance on the way |
|
const int TEMP_RATIO = 1; // 4x4 smaller |
|
RenderTexture rtTempSrc = RenderTexture.GetTemporary(source.width/TEMP_RATIO, source.height/TEMP_RATIO); |
|
Graphics.Blit (source, rtTempSrc, materialLum); |
|
|
|
// Repeatedly reduce this image in size, computing min/max luminance values |
|
// In the end we'll have 1x1 image with min/max luminances found. |
|
const int FINAL_SIZE = 1; |
|
//const int FINAL_SIZE = 1; |
|
while( rtTempSrc.width > FINAL_SIZE || rtTempSrc.height > FINAL_SIZE ) |
|
{ |
|
const int REDUCE_RATIO = 2; // our shader does 2x2 reduction |
|
int destW = rtTempSrc.width / REDUCE_RATIO; |
|
if ( destW < FINAL_SIZE ) destW = FINAL_SIZE; |
|
int destH = rtTempSrc.height / REDUCE_RATIO; |
|
if ( destH < FINAL_SIZE ) destH = FINAL_SIZE; |
|
RenderTexture rtTempDst = RenderTexture.GetTemporary(destW,destH); |
|
Graphics.Blit (rtTempSrc, rtTempDst, materialReduce); |
|
|
|
// Release old src temporary, and make new temporary the source |
|
RenderTexture.ReleaseTemporary( rtTempSrc ); |
|
rtTempSrc = rtTempDst; |
|
} |
|
|
|
// Update viewer's adaptation level |
|
CalculateAdaptation( rtTempSrc ); |
|
|
|
// Apply contrast strech to the original scene, using currently adapted parameters |
|
materialApply.SetTexture("_AdaptTex", adaptRenderTex[curAdaptIndex] ); |
|
Graphics.Blit (source, destination, materialApply); |
|
|
|
RenderTexture.ReleaseTemporary( rtTempSrc ); |
|
} |
|
|
|
|
|
/// Helper function to do gradual adaptation to min/max luminances |
|
private void CalculateAdaptation( Texture curTexture ) |
|
{ |
|
int prevAdaptIndex = curAdaptIndex; |
|
curAdaptIndex = (curAdaptIndex+1) % 2; |
|
|
|
// Adaptation speed is expressed in percents/frame, based on 30FPS. |
|
// Calculate the adaptation lerp, based on current FPS. |
|
float adaptLerp = 1.0f - Mathf.Pow( 1.0f - adaptationSpeed, 30.0f * Time.deltaTime ); |
|
const float kMinAdaptLerp = 0.01f; |
|
adaptLerp = Mathf.Clamp( adaptLerp, kMinAdaptLerp, 1 ); |
|
|
|
materialAdapt.SetTexture("_CurTex", curTexture ); |
|
materialAdapt.SetVector("_AdaptParams", new Vector4( |
|
adaptLerp, |
|
limitMinimum, |
|
limitMaximum, |
|
0.0f |
|
)); |
|
// clear destination RT so its contents don't need to be restored |
|
Graphics.SetRenderTarget(adaptRenderTex[curAdaptIndex]); |
|
GL.Clear(false, true, Color.black); |
|
Graphics.Blit ( |
|
adaptRenderTex[prevAdaptIndex], |
|
adaptRenderTex[curAdaptIndex], |
|
materialAdapt); |
|
} |
|
} |
|
}
|
|
|