unity shader屏幕后处理

屏幕的亮度、饱和度、对比度

实现效果:

unity屏幕后处理之屏幕的亮度、饱和度、对比度

实现代码:
脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//继承脚本中的检测基类:PostEffectsBase
public class BrightnessSaturationAndContrast : PostEffectsBase
{
    //定义shader和材质
    public Shader briSatConShader;
    private Material 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;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    //特效处理
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            material.SetFloat("_Brightness", brightness);
            material.SetFloat("_Saturation", saturation);
            material.SetFloat("_Contrast", contrast);

            Graphics.Blit(source, destination, material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
    //访问private的material
    public Material material
    {
        get
        {
            //使用briSatConShader的briSatConMaterial材质
            briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
            return briSatConMaterial;
        }
    }
}

shader:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter12-BrightnessSaturationAndContrast"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
    Pass{
    //深度写入关闭剔除关闭
    ZTest Always Cull Off ZWrite Off
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #include "unityCG.cginc"

    sampler2D _MainTex;
    half _Brightness;
    half _Saturation;
    half _Contrast;

    struct v2f{
    float4 pos:SV_POSITION;
    half2 uv:TEXCOORD0;
    };

    //调用内置的结构体,只包含顶点信息和纹理坐标
    v2f vert(appdata_img v){
    v2f i;
    i.pos=UnityObjectToClipPos(v.vertex);
    i.uv=v.texcoord;
    return i;
    }
    fixed4 frag(v2f i):SV_Target{
    fixed4 renderTex=tex2D(_MainTex,i.uv);
    //亮度直接乘以颜色
    fixed3 finalColor =renderTex.rgb*_Brightness;

    //饱和度
    //计算亮度值
    fixed luminance =renderTex.r*0.2125+renderTex.g*0.7154+renderTex.b*0.0721;
    fixed3 luminanceColor =fixed3(luminance,luminance,luminance);
    //一开始的颜色值和finalcolor差值得到
    finalColor=lerp(luminanceColor,finalColor,_Saturation);

    //对比度
    fixed3 avgColor=fixed3(0.5,0.5,0.5);
    finalColor=lerp(avgColor,finalColor,_Contrast);

    //用采样颜色的透明通道值
    return fixed4(finalColor,renderTex.a);
    }
    ENDCG
    }
    }
    FallBack Off
}

边缘检测

原理:
利用一些边缘检测算子对图像进行卷积操作。
常见的边缘检测卷积核
因为边缘的判断其实就是相邻的像素之间的存在差异明显的属性,所以卷积核都是水平和竖直方向都要进行计算,得到两个梯度。
在这里插入图片描述
最终的梯度一般有一下两种方式
在这里插入图片描述
在这里插入图片描述
梯度值越大越可能是边缘
实现效果:
在这里插入图片描述
脚本代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EdgeDetection : PostEffectsBase
{
    public Shader edgeDetectShader;
    private Material edgeDetectMaterial;
    //屏幕后处理的所可以调节和被shader所需要的参数
    //边缘线强度,如果为0则边缘线会叠加在原渲染图像上,如果为1则只有边缘线
    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f;
    //边缘线颜色
    public Color edgeColor = Color.black;
    //背景颜色
    public Color backgroundColor = Color.white;
    public Material material
    {
        get
        {
            edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);

            Graphics.Blit(source, destination, material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

shader代码

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter12_EdgeDetection"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader
    {
    Pass{
    ZTest Always Cull Off ZWrite Off

    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #include "unityCG.cginc"

    sampler2D _MainTex;
    //纹理的纹素的大小
    half4 _MainTex_TexelSize;
    //为0的时候是叠加,为1的时候是只有边缘
    half _EdgeOnly;
    half4 _EdgeColor;
    //只有边缘时候的背景颜色
    half4 _BackgroundColor;

    struct v2f{
    float4 pos:SV_POSITION;
    half2 uv[9]:TEXCOORD0;
    };

    v2f vert(appdata_img i){
    v2f o;
    o.pos=UnityObjectToClipPos(i.vertex);
    half2 uv=i.texcoord;
    //原本只用一个纹理坐标,但是卷积核需要使用周围的纹理坐标,所以需要存储9个纹理坐标
    o.uv[0]=uv+_MainTex_TexelSize*half2(-1,-1);
    o.uv[1]=uv+_MainTex_TexelSize*half2(0,-1);
    o.uv[2]=uv+_MainTex_TexelSize*half2(1,-1);
    o.uv[3]=uv+_MainTex_TexelSize*half2(-1,0);
    o.uv[4]=uv+_MainTex_TexelSize*half2(0,0);
    o.uv[5]=uv+_MainTex_TexelSize*half2(1,0);
    o.uv[6]=uv+_MainTex_TexelSize*half2(-1,1);
    o.uv[7]=uv+_MainTex_TexelSize*half2(0,1);
    o.uv[8]=uv+_MainTex_TexelSize*half2(1,1);
    return o;
    }

    //计算颜色的亮度值
    fixed luminance(fixed4 color){
    return 0.2125*color.r+0.7154*color.g+0.0721*color.b;
    }

    half Sobel(v2f i){
    //卷积核
    const half Gx[9]={-1,-2,-1,0,0,0,1,2,1};
    const half Gy[9]={-1,0,1,-2,0,2,-1,0,1};

    //每个纹素的颜色值
    half texColor;
    //梯度值
    half edgeX=0;
    half edgeY=0;
    for(int j=0;j<9;j++){
    texColor=luminance(tex2D(_MainTex,i.uv[j]));
    edgeX+=texColor*Gx[j];
    edgeY+=texColor*Gy[j];
    }
    return 1-abs(edgeX)-abs(edgeY);
    }

    fixed4 frag(v2f i):SV_Target{
    half edge =Sobel(i);
    //edge越大说明梯度值越小,则图像颜色占比较多
    fixed4 withEdgeColor =lerp(_EdgeColor,tex2D(_MainTex,i.uv[4]),edge);
    //edge越大说明梯度值越小,则背景颜色偏大
    fixed4 onlyEdgeColor =lerp(_EdgeColor,_BackgroundColor,edge);
    //最终根据_EdgeOnly差值取得最终的颜色
    fixed4 c=lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly);
    return c;
    }

    ENDCG
    }
    }
    FallBack "Diffuse"
}

高斯模糊

模糊有几种方式:

  • 中值模糊:将领域内的像素排序之后取中值,作为该像素的颜色。
  • 均值模糊:将领域内的像素相加除以个数,得到的均值作为颜色。
    高斯模糊:
    使用高斯核,高斯核由高斯方程得到,高斯核内数据相加为1,不会改变画面亮度。
    在这里插入图片描述
    x和y表示当前位置和卷积核中心的整数距离,距离越远,影响越小,距离越近,影响越大。
    我们可以将正方形的高斯核压缩成两个横向和竖向的卷积核,效果是一样的。
    在这里插入图片描述
    横向的是每一竖列的相加,竖向的是每一横列相加的结果。
    实现效果:
    在这里插入图片描述
    脚本代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GaussianBlur : PostEffectsBase
{
    public Shader gaussianBlurShader;
    private Material gaussianBlurMaterial = null;
    //迭代次数
    [Range(0, 4)]
    public int iterations = 3;
    //模糊范围
    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;
    //缩放系数,越大需要处理的像素越少,同时可以进一步提高模糊程度,但是过大可能会使得图像像素化。
    [Range(1, 8)]
    public int downSample = 2;
    public Material material
    {
        get
        {
            gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
            return gaussianBlurMaterial;
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            //创建缓冲buffer0
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            //设置滤波模式为双线性
            buffer0.filterMode = FilterMode.Bilinear;
            //将src中的屏幕图像传入buffer0
            Graphics.Blit(source, buffer0);

            for(int i = 0; i < iterations; i++)
            {
                //给shader传参数
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

                //新建buffer1,并且进行垂直的模糊处理将得到的图形传递存到buffer1中
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material,0);

                //释放buffer0,并且把模糊后得到的图形赋值给buffer0
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;

                //buffer1重新分配
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material,1);

                RenderTexture.ReleaseTemporary(buffer0);
                //buffer0储存这一次模糊的结果作为下一次模糊处理的输入图像
                buffer0 = buffer1;
            }
            //模糊处理之后的结果输出到屏幕上
            Graphics.Blit(buffer0, destination);
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter12-GaussianBlur"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BlurSize("_BlurSize",Float)=1.0
    }
    SubShader
    {
        //类似头文件
        CGINCLUDE
        #include "unityCG.cginc"
        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        float _BlurSize;

        //片段着色器的结构体
        struct v2f{
        float4 pos:SV_POSITION;
        //存放采样坐标
        half2 uv[5]:TEXCOORD0;
        };

        //垂直顶点着色器
        v2f vertBlurVertical(appdata_img v){
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        half2 uv=v.texcoord;

        //每个像素点的纹理坐标不单单是一个而是五个
        o.uv[0] = uv;
        o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
        o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
		o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
		o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
        return o;
        }

        //水平顶点着色器
        v2f vertBlurHorizontal(appdata_img v){
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        half2 uv=v.texcoord;

        o.uv[0] = uv;
        o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0,0.0) * _BlurSize;
        o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0,0.0) * _BlurSize;
		o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0,0.0) * _BlurSize;
		o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0,0.0) * _BlurSize;
        return o;
        }

        //通用片段着色器
        fixed4 frag(v2f i):SV_Target{
        //横向和纵向的高斯核相对称
        //存储权重,因为对称所以只有三个
        float weight[3]={0.4026,0.2442,0.0545};

        //中间的权重是独特的不是对称的所以单独计算
        fixed3 sum=tex2D(_MainTex,i.uv[0]).rgb*weight[0];
        //计算另外四个
        for(int it=1;it<3;it++){
        sum +=tex2D(_MainTex,i.uv[it]).rgb*weight[it];
        sum +=tex2D(_MainTex,i.uv[it*2]).rgb*weight[it];
        }

        return fixed4(sum,1.0);
        }
        ENDCG

        ZTest Always Cull Off ZWrite Off
        Pass{
        //设置pass名称方便在其他的shader中调用
        NAME "GAUSSIAN_BLUR_VERTICAL"
        CGPROGRAM
        #pragma vertex vertBlurVertical
        #pragma fragment frag
        ENDCG
        }

        Pass{
        //设置pass名称方便在其他的shader中调用
        NAME "GAUSSIAN_BLUR_HORIZONTAL"
        CGPROGRAM
        #pragma vertex vertBlurHorizontal
        #pragma fragment frag
        ENDCG
        }
    }
    FallBack Off
}

CGINCLUDE和ENDCG中间定义的代码,不用包含在任何pass中,使用时也只用调用其中的函数名即可

曝光效果

实现步骤

  1. 首先根据一个阙值,将画面中较亮的区域存储在一张渲染纹理中。
  2. 利用高斯模糊对于这一张渲染纹理做模糊处理,扩散较亮的区域。
  3. 将模糊处理过的纹理与原图像进行混合。
    实现效果:
    在这里插入图片描述
    脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bloom : PostEffectsBase
{
    public Shader bloomShader;
    private Material bloomMaterial = null;
    public Material material
    {
        get
        {
            bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
            return bloomMaterial;
        }
    }

    //迭代次数
    [Range(0, 4)]
    public int iterations = 3;
    //模糊范围
    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;
    //缩放系数
    [Range(1, 8)]
    public int downSample = 2;
    //提取较亮范围的阙值
    [Range(0.0f,4.0f)]
    public float luminanceThreshold = 0.6f;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //传参
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);
            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            buffer0.filterMode = FilterMode.Bilinear;

            //用material中的shader中的第一个pass提取出较亮区域存放在buffer0中
            Graphics.Blit(source, buffer0, material, 0);
            for(int i = 0; i < 3; i++)
            {
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material, 1);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material, 2);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }

            //将提取出来的图像模糊处理后存储到纹理中
            material.SetTexture("_Bloom", buffer0);
            //利用第四个pass将模糊的图像和原图合并起来
            Graphics.Blit(source, destination, material, 3);
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter12-Bloom"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Bloom("Bloom (RGB)",2D)="black"{}
        _LuminanceThreshold("Luminance Threshold",Float)=0.5
        _BlurSize("Blur Size",Float)=1.0
    }
    SubShader
    {
    CGINCLUDE

    #include "unityCG.cginc"
    sampler2D _MainTex;
    half4 _MainTex_TexelSize;
    sampler2D _Bloom;
    float _LuminanceThreshold;
    float _BlurSize;

    struct v2f{
    float4 pos:SV_POSITION;
    half2 uv:TEXCOORD0;
    };

    //提取亮区域的顶点着色器
    v2f vertExtractBright(appdata_img v){
    v2f o;
    o.pos =UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
    }
    float luminance(fixed4 color){
    return color.r*0.2125+color.g*0.7154+color.b*0.0721;
    }
    //提取亮区域的片段着色器
    fixed4 fragExtractBright(v2f i):SV_Target{
    fixed4 c =tex2D(_MainTex,i.uv);
    //当亮度小于阙值的时候,val为0,则原图像的颜色丢失,大于阙值,最大值为1,也就是图像原来的颜色
    fixed val=clamp(luminance(c)-_LuminanceThreshold,0.0,1.0);
    return c*val;
    }

    //混合图像的片段着色器的结构图
    struct v2fbloom{
    float4 pos:SV_POSITION;
    half4 uv:TEXCOORD0;
    };

    //顶点着色器
    v2fbloom vertBloom(appdata_img v){
    v2fbloom o;
    o.pos =UnityObjectToClipPos(v.vertex);
    o.uv.xy =v.texcoord;
    o.uv.zw =v.texcoord;

    //平台差异化处理
    #if UNITY_UV_STARTS_AT_TOP
    if(_MainTex_TexelSize.y<0.0){
    o.uv.w=1.0-o.uv.w;
    }
    #endif

    return o;
    }

    //片段着色器
    fixed4 fragBloom(v2fbloom i):SV_Target{
    return tex2D(_MainTex,i.uv.xy)+tex2D(_Bloom,i.uv.zw);
    }
    ENDCG

    ZTest Always Cull Off ZWrite Off

    //提取亮区域的pass
    Pass{
    CGPROGRAM
    #pragma vertex vertExtractBright
    #pragma fragment fragExtractBright
    ENDCG
    }

    //模糊处理
    UsePass "Custom/Chapter12-GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
    UsePass "Custom/Chapter12-GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"

    //混合图像pass
    Pass{
    CGPROGRAM
    #pragma vertex vertBloom
    #pragma fragment fragBloom
    ENDCG
    }
    }
    FallBack Off
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值