UnityShader-屏幕后处理(调节亮度,饱和度,对比度)

一、什么是屏幕后处理

在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列的操作,实现各种屏幕特效。常用于处理景深、描边,图像模糊等特效。

二、实现原理

想要实现屏幕后处理的基础在于得到渲染后的整个屏幕的图像,即抓取屏幕,在Unity中为我们提供了一个方便的接口——OnRenderImage函数:
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
src表示源纹理,即没有经过处理之前的纹理,dest表示目标纹理,即处理后的纹理,
目标纹理可以在Camera中的Target Texture设置,如下图:


TargetTexture.png

默认情况下OnRenderImage是在整个屏幕渲染完以后(也就是在不透明和透明物体之后都渲染完以后)调用,在一些特殊的情况下,我们需要在渲染完不透明物体的Pass(即渲染队列小于等于2500的pass,例如内置的
AlphaTest,Background,Geometry,等都在这个范围内)后立即就调用OnRenderImage,换句话说就是我们不希望将透明物体也包括在屏幕后处理中,这时我们需要在OnRenderImage函数前面添加[ImageEffectOpaque]属性来达到目的。

在OnRenderImage函数中我们通常通过Graphics.Blit函数对纹理进行处理,它有3种函数声明:
Graphics.Blit(Texture src,RenderTexture dest);
Graphics.Blit(Texture src,RenderTexture dest,Material mat ,int pass = -1);
Graphics.Blit(Texture src,Material mat ,int pass = -1);
src表示当前屏幕纹理或者上一步处理后得到的渲染纹理,dest表示目标渲染纹理,如果他的值为null,那么就会直接将处理后的纹理渲染到屏幕上面。mat是使用的材质,这个材质使用的shader将会对源纹理进行各种特效处理。而源纹理(src)将会被传递给shader中的_MainTex的纹理属性。pass的默认参数是-1,表示将会依次调用shader中的每个pass,否则,只会调用给定索引的pass。

三、实例

1、基类实现

在进行屏幕后处理前我们需要一系列的条件检测,例如当前平台是否支持渲染纹理和屏幕特效,是否支持当前使用的unity shader等。为此,我们创建一个用于屏幕后处理效果的基类(PostEffectsBase.cs),在实现屏幕效果时,我们只需要继承这个基类,再实现派生类中不同操作即可。
基类实现PostEffectsBase.cs:

using UnityEngine;
using System.Collections;

//在编辑器状态下执行该脚本
[ExecuteInEditMode]
//刚需组件(Camera)
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {

    // 在Start()中调用
    protected void CheckResources() {
        bool isSupported = CheckSupport();
        
        if (isSupported == false) {
            NotSupported();
        }
    }

    // 平台渲染纹理与屏幕特效支持检测
    protected bool CheckSupport() {
        if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
            Debug.LogWarning("This platform does not support image effects or render textures.");
            return false;
        }
        
        return true;
    }

    // 当不支持的时候,将脚本的enabled设置为false
    protected void NotSupported() {
        enabled = false;
    }
    
    protected void Start() {
        CheckResources();
    }

    // 检测Material和Shader,在派生类中调用,绑定材质和shader
    protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
        if (shader == null) {
            return null;
        }
        
        if (shader.isSupported && material && material.shader == shader)
            return material;
        
        if (!shader.isSupported) {
            return null;
        }
        else {
            material = new Material(shader);
            material.hideFlags = HideFlags.DontSave;
            if (material)
                return material;
            else 
                return null;
        }
    }
}

2、调节亮度,饱和度,对比度

(1)创建脚本(BrightnessSaturationAndContrast.cs),继承基类,重写OnRenderImage函数

using UnityEngine;
using System.Collections;

public class BrightnessSaturationAndContrast : PostEffectsBase {
    //绑定的shader
    public Shader briSatConShader;
    private Material briSatConMaterial;
    public Material material {  
        get {
            //调用基类的CheckShaderAndCreateMaterial方法绑定shader与Material
            briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
            return briSatConMaterial;
        }  
    }
    //亮度值
    [Range(0.0f, 3.0f)]
    public float brightness = 1.0f;

     //饱和度
    [Range(0.0f, 3.0f)]
    public float saturation = 1.0f;
     //对比度
    [Range(0.0f, 3.0f)]
    public float contrast = 1.0f;
    //重写OnRenderImage方法
    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (material != null) {
            //设置shader中的各个值
            material.SetFloat("_Brightness", brightness);
            material.SetFloat("_Saturation", saturation);
            material.SetFloat("_Contrast", contrast);
            //将源纹理通过material处理,复制到目标纹理中
            Graphics.Blit(src, dest, material);
        } else {
            Graphics.Blit(src, dest);
        }
    }
}

(2)编写Shader特效

Shader "Change Brightness Saturation And Contrast" {
    Properties {
        //Graphics.Blit函数传入的src
        _MainTex ("Source Texture", 2D) = "white" {}
        //亮度值
        _Brightness ("Brightness", Float) = 1
       //饱和度
        _Saturation("Saturation", Float) = 1
       //对比度
        _Contrast("Contrast", Float) = 1
    }
    SubShader {
        Pass {  
            //关闭深度写入
            ZTest Always Cull Off ZWrite Off
            
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
              
            #include "UnityCG.cginc"  
            //CG中代码块中声明对应的变量
            sampler2D _MainTex;  
            half _Brightness;
            half _Saturation;
            half _Contrast;
              
            struct v2f {
                float4 pos : SV_POSITION;
                half2 uv: TEXCOORD0;
            };
            //顶点着色器,坐标转换以及获取uv值  
            v2f vert(appdata_img v) {
                v2f o;
                
                o.pos = UnityObjectToClipPos(v.vertex);
                
                o.uv = v.texcoord;
                         
                return o;
            }
            //片元着色器
            fixed4 frag(v2f i) : SV_Target {
                //纹理采样
                fixed4 renderTex = tex2D(_MainTex, i.uv);  
                  
                // 亮度值调整
                fixed3 finalColor = renderTex.rgb * _Brightness;
                
                // 该像素对应的亮度值
                fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
                //使用该亮度值创建一个饱和度为0的颜色
                fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
                //将之前的颜色与该颜色进行插值运算,得到调整饱和度后的颜色
                finalColor = lerp(luminanceColor, finalColor, _Saturation);
                
                // 创建一个对比度度为0的颜色
                fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
                //将之前的颜色与该颜色进行插值运算,得到调整对比度后的颜色
                finalColor = lerp(avgColor, finalColor, _Contrast);
                 //返回最终颜色   
                return fixed4(finalColor, renderTex.a);  
            }  
              
            ENDCG
        }  
    }
    
    Fallback Off
}

(3)返回unity编辑器,将ChangeBrightnessSaturationAndContrast.shader赋值给BrightnessSaturationAndContrast.cs脚本的briSatConShader变量,调整亮度值,饱和度,对比度的值,查看效果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容