200 lines
6.7 KiB
C#
200 lines
6.7 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
|
|
// TBD: if some shader is not supported, turn off!
|
|
|
|
[AddComponentMenu("Image Effects/Contrast Stretch")]
|
|
public class ContrastStretchEffect : MonoBehaviour
|
|
{
|
|
/// Adaptation speed - percents per frame, if playing at 30FPS.
|
|
/// Default is 0.02 (2% each 1/30s).
|
|
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.
|
|
public float limitMinimum = 0.2f;
|
|
|
|
/// The limit on the maximum luminance (0...1) - we won't go below this.
|
|
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;
|
|
}
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
if (!adaptRenderTex[i])
|
|
{
|
|
adaptRenderTex[i] = new RenderTexture(1, 1, 32);
|
|
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);
|
|
ImageEffects.BlitWithMaterial(materialLum, source, rtTempSrc);
|
|
|
|
// 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);
|
|
ImageEffects.BlitWithMaterial(materialReduce, rtTempSrc, rtTempDst);
|
|
|
|
// 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]);
|
|
ImageEffects.BlitWithMaterial(materialApply, source, destination);
|
|
|
|
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
|
|
));
|
|
ImageEffects.BlitWithMaterial(materialAdapt,
|
|
adaptRenderTex[prevAdaptIndex],
|
|
adaptRenderTex[curAdaptIndex]);
|
|
}
|
|
} |