FiE-Game/Assets/Standard Assets/Effects/ImageEffects/Scripts/ContrastStretch.cs
2023-07-25 00:52:50 +05:00

200 lines
7.9 KiB
C#

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);
}
}
}