67.Bloom效果具体实现

  1. 67.屏幕后期处理效果-效果实现-Bloom效果-具体实现
    1. 67.1 知识点
      1. 知识回顾 Bloom效果基本原理
      2. 准备工作,新建Shader
      3. 提取
        1. 主要目的
        2. Shader代码
          1. 主要步骤
          2. 声明属性,包括主纹理,亮度区域纹理和亮度阈值
          3. 在SubShader语句块的CGINCLUDE…ENDCG中实现共享CG代码,包括属性映射,结构体(顶点,uv)和灰度值(亮度值)计算函数
          4. 添加屏幕后处理标配指令
          5. 声明提取Pass 实现顶点函数和片元函数。顶点函数中对顶点转换、UV赋值。片元函数对颜色采样、计算亮度贡献值,最后返回颜色*亮度贡献值
        3. C#代码
          1. 主要步骤
          2. 创建C#脚本,名为Bloom,继承屏幕后处理基类PostEffectBase。声明亮度阈值成员变量,重写OnRenderImage函数。设置材质球的亮度阈值。利用Graphics.Blit、RenderTexture.GetTemporary、 RenderTexture.ReleaseTemporary函数对纹理,进行Pass处理。来查看提取后的效果
        4. 挂载到摄像机可以查看到提取后的效果效果
      4. 模糊
        1. 主要目的
        2. Shader代码
          1. 主要步骤
          2. 添加模糊半径属性 _BlurSize,进行属性映射(注意:需要用到纹素)
          3. 复制之前高斯模糊Shader,为其中的两个Pass命名
          4. 在Bloom的shader代码中,使用Pass复用语法复用高斯模糊的两个Pass
        3. C#代码
          1. 主要步骤
          2. 主要是复制高斯模糊C#脚本相关。复制高斯模糊中的3个属性,处理高斯模糊的逻辑。将模糊处理后的纹理,存储到_Bloom纹理属性,为下一步合成做准备。注意循环内的pass取值要改成1和2。因为0是提取Pass,1和2才是高斯模糊Pass
        4. 修改高斯模糊参数可以查看效果
      5. 合成
        1. 主要目的
        2. Shader代码
          1. 主要步骤
          2. 声明合并Pass,写出骨架
          3. 定义新的结构体v2fBloom,用于在这个合成Pass的顶点着色器和片元着色器之间传递数据。包括顶点坐标,4维的uv(xy存主纹理,zw存亮度提取纹理)
          4. 顶点函数中对顶点坐标转换,并对纹理坐标赋值。注意,亮度纹理的uv坐标需要判断是否进行Y轴翻转。
          5. 片元函数中,将主纹理和模糊后的亮度纹理采样得到的颜色相加返回。
          6. 注意,在写模糊C#逻辑时,有把buffer写入到_Bloom中,相当于把亮度提取出来模糊后写入回Shader中。所以片元函数中只需要取出_Bloom合成即可。
          7. 添加失败处理
        3. C#代码
        4. 修改参数,可以看到朦胧的效果
    2. 67.2 知识点代码
      1. Lesson67_屏幕后期处理效果_效果实现_Bloom效果_具体实现.cs
      2. Lesson67_GaussianBlurPerfect.shader
      3. Lesson67_Bloom.shader
      4. Lesson67_Bloom.cs

67.屏幕后期处理效果-效果实现-Bloom效果-具体实现


67.1 知识点

知识回顾 Bloom效果基本原理

Bloom效果通常由四个Pass来完成三个处理步骤:

  • 提取(1个Pass):提取原图像中的亮度区域并存储到一张新纹理中。
  • 模糊(2个Pass):对提取出来的亮度纹理进行模糊处理,通常使用高斯模糊。
  • 合成(1个Pass):将模糊后的亮度纹理与源纹理进行颜色叠加。

准备工作,新建Shader

创建一个名为Lesson67_Bloom的Shader,删除无用的代码,保留基本骨架:

Shader "Unlit/Lesson67_Bloom"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;

        struct v2f
        {
            float2 uv : TEXCOORD0;
            float4 vertex : SV_POSITION;
        };

        //提取的Pass
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

    
            ENDCG
        }

        //复用高斯模糊的2个Pass

        //用于合成的Pass
        Pass
        {
            CGPROGRAM
            #pragma vertex vertBloom
            #pragma fragment fragBloom

            ENDCG
        }
    }
}

提取

主要目的

提取原图像中的亮度区域并将其存储到新纹理中。

Shader代码

主要步骤
  1. 声明属性

    • 主纹理 _MainTex
    • 亮度区域纹理 _Bloom
    • 亮度阈值 _LuminanceThreshold
  2. 在CGINCLUDE…ENDCG中实现共享CG代码

    • 属性映射
    • 结构体(顶点,UV)
    • 灰度值(亮度值)计算函数
  3. 屏幕后处理标配

    • ZTest Always
    • Cull Off
    • ZWrite Off
  4. 提取Pass实现

    • 顶点着色器:顶点转换、UV赋值
    • 片元着色器:颜色采样、亮度贡献值计算、颜色与亮度贡献值相乘
声明属性,包括主纹理,亮度区域纹理和亮度阈值
Properties
{
    // 主纹理属性
    _MainTex ("Texture", 2D) = "white" {}

    // 用于存储亮度纹理模糊后的结果的纹理属性
    _Bloom("Bloom", 2D) = ""{}

    // 亮度阈值属性,用于控制亮度纹理中亮度区域的界定
    _LuminanceThreshold("LuminanceThreshold", Float) = 0.5
}
在SubShader语句块的CGINCLUDE…ENDCG中实现共享CG代码,包括属性映射,结构体(顶点,uv)和灰度值(亮度值)计算函数
CGINCLUDE

#include "UnityCG.cginc"

// 主纹理
sampler2D _MainTex;

// 存储亮度纹理模糊后结果
sampler2D _Bloom;

// 亮度阈值变量
float _LuminanceThreshold;

struct v2f
{
    // 纹理坐标,用于在片元着色器中采样纹理
    float2 uv : TEXCOORD0;
    // 顶点在裁剪空间中的位置
    float4 vertex : SV_POSITION;
};

// 计算颜色的亮度值(灰度值)的函数
// 输入一个fixed4类型的颜色,按照一定的权重计算其亮度值并返回
fixed luminance(fixed4 color)
{
    // 根据常见的亮度计算权重公式,计算颜色的亮度值
    // 分别对颜色的红、绿、蓝通道乘以相应权重后相加得到亮度值
    return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
ENDCG
添加屏幕后处理标配指令
// 始终进行深度测试
ZTest Always
// 关闭背面剔除
Cull Off
// 关闭深度写入
ZWrite Off
声明提取Pass 实现顶点函数和片元函数。顶点函数中对顶点转换、UV赋值。片元函数对颜色采样、计算亮度贡献值,最后返回颜色*亮度贡献值
// 提取的Pass,用于提取亮度相关信息
Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    // 顶点着色器函数,将输入的顶点数据进行处理并传递给片元着色器
    v2f vert(appdata_base appdata_base)
    {
        v2f v2f;
        // 将顶点从模型空间转换到裁剪空间
        v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
        // 获取顶点的纹理坐标
        v2f.uv = appdata_base.texcoord;
        return v2f;
    }

    // 片元着色器函数,根据提取的亮度信息对主纹理颜色进行处理
    fixed4 frag(v2f v2f):SV_Target
    {
        // 从主纹理中根据传入的纹理坐标采样得到颜色
        fixed4 color = tex2D(_MainTex, v2f.uv);
        // 计算颜色的亮度贡献值,先计算亮度与亮度阈值的差值,然后通过clamp函数限制在0到1之间
        fixed value = clamp(luminance(color) - _LuminanceThreshold, 0, 1);
        // 返回经过亮度贡献值调整后的颜色
        return color * value;
    }
    ENDCG
}

C#代码

主要步骤
  1. 创建C#脚本,名为Lesson67_Bloom
  2. 继承屏幕后处理基类 PostEffectBase
  3. 声明亮度阈值成员变量
  4. 重写 OnRenderImage 函数
  5. 设置材质球的亮度阈值
  6. OnRenderImage 中利用
    • Graphics.Blit
    • RenderTexture.GetTemporary
    • RenderTexture.ReleaseTemporary
      函数对纹理进行Pass处理
创建C#脚本,名为Bloom,继承屏幕后处理基类PostEffectBase。声明亮度阈值成员变量,重写OnRenderImage函数。设置材质球的亮度阈值。利用Graphics.Blit、RenderTexture.GetTemporary、 RenderTexture.ReleaseTemporary函数对纹理,进行Pass处理。来查看提取后的效果
public class Lesson67_Bloom : PostEffectBase
{
    //亮度阈值变量
    [Range(0, 4)] public float luminanceThreshold = .5f;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //设置亮度阈值变量
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);
            //渲染纹理缓冲区
            RenderTexture buffer = RenderTexture.GetTemporary(source.width, source.height, 0);
            //提取 用我们的提取Pass去得到对应的亮度信息 存入到缓冲区纹理中
            Graphics.Blit(source, buffer, material, 0);

            //测试 看到提取效果
            Graphics.Blit(buffer, destination);

            RenderTexture.ReleaseTemporary(buffer);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

挂载到摄像机可以查看到提取后的效果效果



模糊

主要目的

将提取出来的亮度区域纹理进行模糊处理,通常使用高斯模糊。

Shader代码

主要步骤
  1. 添加模糊半径属性 _BlurSize,进行属性映射(注意:需要用到纹素)
  2. 复制之前的高斯模糊Shader,为其中的两个Pass命名
  3. 在Bloom Shader中,利用 UsePass 复用高斯模糊Shader中的两个Pass
添加模糊半径属性 _BlurSize,进行属性映射(注意:需要用到纹素)
Properties
{
    // 主纹理属性
    _MainTex ("Texture", 2D) = "white" {}

    // 用于存储亮度纹理模糊后的结果的纹理属性
    _Bloom("Bloom", 2D) = ""{}

    // 亮度阈值属性,用于控制亮度纹理中亮度区域的界定
    _LuminanceThreshold("LuminanceThreshold", Float) = 0.5

    // 模糊半径属性,用于控制模糊的程度
    _BlurSize("BlurSize", Float) = 1
}
// 主纹理
sampler2D _MainTex;

// 主纹理的纹素
half4 _MainTex_TexelSize;

// 存储亮度纹理模糊后结果
sampler2D _Bloom;

// 亮度阈值变量
float _LuminanceThreshold;

// 模糊半径变量
float _BlurSize;
复制之前高斯模糊Shader,为其中的两个Pass命名
Shader "Unlit/Lesson67_GaussianBlurPerfect"
{
    Properties
    {
        //...
    }
    SubShader
    {
        CGINCLUDE
        //...
        ENDCG

        Tags
        {
            "RenderType"="Opaque"
        }

        //...

        Pass
        {
            Name "GAUSSIAN_BLUR_HORIZONTAL"
            //...
        }

        Pass
        {
            Name "GAUSSIAN_BLUR_VERTICAL"
            //...
        }
    }

    Fallback Off
}
在Bloom的shader代码中,使用Pass复用语法复用高斯模糊的两个Pass
// 复用高斯模糊的2个Pass,分别是水平方向和垂直方向的模糊Pass
UsePass "Unlit/Lesson67_GaussianBlurPerfect/GAUSSIAN_BLUR_HORIZONTAL"
UsePass "Unlit/Lesson67_GaussianBlurPerfect/GAUSSIAN_BLUR_VERTICAL"

C#代码

主要步骤
  1. 复制高斯模糊中的3个属性
  2. 复制高斯模糊中C#代码中处理高斯模糊的逻辑
  3. 将模糊处理后的纹理,存储 _Bloom 纹理属性
主要是复制高斯模糊C#脚本相关。复制高斯模糊中的3个属性,处理高斯模糊的逻辑。将模糊处理后的纹理,存储到_Bloom纹理属性,为下一步合成做准备。注意循环内的pass取值要改成1和2。因为0是提取Pass,1和2才是高斯模糊Pass
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson67_Bloom : PostEffectBase
{
    //亮度阈值变量
    [Range(0, 4)]
    public float luminanceThreshold = .5f;

    [Range(1, 8)]
    public int downSample = 1;
    [Range(1, 16)]
    public int iterations = 1;
    [Range(0, 3)]
    public float blurSpread = 0.6f;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(material != null)
        {
            //设置亮度阈值变量
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);

            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            //渲染纹理缓冲区
            RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
            //采用双线性过滤模式来缩放 可以让缩放效果更平滑
            buffer.filterMode = FilterMode.Bilinear;
            //第一步 提取处理 用我们的提取Pass去得到对应的亮度信息 存入到缓冲区纹理中
            Graphics.Blit(source, buffer, material, 0);

            //第二步 模糊处理
            //多次去执行 高斯模糊逻辑
            for (int i = 0; i < iterations; i++)
            {
                //如果想要模糊半径影响模糊想过更强烈 更平滑
                //一般可以在我们的迭代中进行设置 相当于每次迭代处理高斯模糊时 都在增加我们的间隔距离
                material.SetFloat("_BlurSpread", 1 + i * blurSpread);

                //又声明一个新的缓冲区
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                //因为我们需要用两个Pass 处理图像两次 
                //进行第一次 水平卷积计算
                Graphics.Blit(buffer, buffer1, material, 1); //Color1
                //这时 关键内容都在buffer1中 buffer没用了 释放掉
                RenderTexture.ReleaseTemporary(buffer);

                buffer = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                //进行第二次 垂直卷积计算
                Graphics.Blit(buffer, buffer1, material, 2);//在Color1的基础上乘上Color2 得到最终的高斯模糊计算结果
                                                            //释放缓存区
                RenderTexture.ReleaseTemporary(buffer);
                //buffer和buffer1指向的都是这一次高斯模糊处理的结果
                buffer = buffer1;
            }
            //把提取出来的内容进行高斯模糊后 存储Shader当中的一个纹理变量
            //用于之后进行合成
            material.SetTexture("_Bloom", buffer);

            //测试 看到提取效果
            Graphics.Blit(buffer, destination);

            RenderTexture.ReleaseTemporary(buffer);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

修改高斯模糊参数可以查看效果

合成

主要目的

将模糊处理后的亮度纹理与源纹理进行颜色叠加。

Shader代码

主要步骤
  1. 结构体

    • 顶点坐标,4维的 UV(xy 存主纹理,zw 存亮度提取纹理)
  2. 顶点着色器

    • 顶点坐标转换,纹理坐标赋值
    • 注意:亮度纹理的 UV 坐标需要判断是否进行 Y 轴翻转
    • 因为使用 RenderTexture 写入到 Shader 的纹理变量时,Unity 可能会对其进行 Y 轴翻转
    • 可以利用 Unity 提供的预处理宏进行判断
      #if UNITY_UV_STARTS_AT_TOP
      #endif
      
    • 如果该宏被定义,说明当前平台的纹理坐标系的 Y 轴原点在顶部
    • 还可以在该宏中用纹素进行判断,如果纹素的 Y 小于 0,为负数,表示需要对 Y 轴进行调整
    • 配合使用:
      #if UNITY_UV_STARTS_AT_TOP
      if (_MainTex_TexelSize.y < 0.0)
          // 翻转 uv 的 y 轴坐标
      #endif
      
    • 主纹理不需要额外处理,Unity 会自动处理,一般只在使用 RenderTexture 时才需要考虑该问题
  3. 片元着色器

    • 两个纹理颜色采样后相加
  4. FallBack Off

声明合并Pass,写出骨架
// 用于合成的Pass,将主纹理和模糊后的亮度纹理进行合成
Pass
{
    CGPROGRAM
    #pragma vertex vertBloom
    #pragma fragment fragBloom

    ENDCG
}
定义新的结构体v2fBloom,用于在这个合成Pass的顶点着色器和片元着色器之间传递数据。包括顶点坐标,4维的uv(xy存主纹理,zw存亮度提取纹理)
// 定义新的结构体v2fBloom,用于在这个合成Pass的顶点着色器和片元着色器之间传递数据
struct v2fBloom
{
    // 顶点在裁剪空间中的位置,存储在SV_POSITION语义中
    float4 pos:SV_POSITION;
    // 一个half4类型的变量,xy主要用于对主纹理进行采样,zw主要用于对亮度模糊后的纹理采样
    half4 uv:TEXCOORD0;
};
顶点函数中对顶点坐标转换,并对纹理坐标赋值。注意,亮度纹理的uv坐标需要判断是否进行Y轴翻转。
// 合成Pass的顶点着色器函数,处理顶点数据并设置纹理采样坐标等
v2fBloom vertBloom(appdata_base appdata_base)
{
    v2fBloom v2fBloom;

    // 将顶点从模型空间转换到裁剪空间
    v2fBloom.pos = UnityObjectToClipPos(appdata_base.vertex);
    
    // 设置主纹理和亮度纹理要采样相同的地方进行颜色叠加,所以xy和zw都先设置为传入的纹理坐标
    v2fBloom.uv.xy = appdata_base.texcoord;
    v2fBloom.uv.zw = appdata_base.texcoord;

    // 用宏去判断uv坐标是否被翻转
    #if UNITY_UV_STARTS_AT_TOP
    // 如果纹素的y小于0 为负数,表示需要对Y轴进行调整
    if (_MainTex_TexelSize.y < 0)
        v2fBloom.uv.w = 1 - v2fBloom.uv.w;
    #endif

    return v2fBloom;
}
片元函数中,将主纹理和模糊后的亮度纹理采样得到的颜色相加返回。
// 合成Pass的片元着色器函数,将主纹理和模糊后的亮度纹理采样得到的颜色相加返回
fixed4 fragBloom(v2fBloom v2fBloom):SV_TARGET
{
    return tex2D(_MainTex, v2fBloom.uv.xy) + tex2D(_Bloom, v2fBloom.uv.zw);
}
注意,在写模糊C#逻辑时,有把buffer写入到_Bloom中,相当于把亮度提取出来模糊后写入回Shader中。所以片元函数中只需要取出_Bloom合成即可。
//把提取出来的内容进行高斯模糊后 存储Shader当中的一个纹理变量
//用于之后进行合成
material.SetTexture("_Bloom", buffer);
添加失败处理
// 关闭回退,如果当前着色器无法运行,不会自动使用其他备用着色器
Fallback Off

C#代码

注释掉测试代码,对源纹理进行合并处理,取出第三个合成Pass生成的材质处理效果,设置到目标纹理中

//测试 看到提取效果
//Graphics.Blit(buffer, destination);

//合成步骤
Graphics.Blit(source, destination, material, 3);

修改参数,可以看到朦胧的效果


67.2 知识点代码

Lesson67_屏幕后期处理效果_效果实现_Bloom效果_具体实现.cs

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

public class Lesson67_屏幕后期处理效果_效果实现_Bloom效果_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 Bloom效果基本原理

        //利用4个Pass进行3个处理步骤
        //提取(1个Pass) :提取原图像中的亮度区域存储到一张新纹理中
        //模糊(2个Pass) :将提取出来的纹理进行模糊处理(一般采用高斯模糊)
        //合成(1个Pass) :将模糊处理后的亮度纹理和源纹理进行颜色叠

        #endregion


        #region 准备工作

        //新建Shader,取名 Bloom ,删除无用代码

        #endregion

        #region 第一步 提取

        //主要目的:提取原图像中的亮度区域存储到一张新纹理中
        
        //Shader代码
        
        //1.声明属性
        //  主纹理 _MainTex
        //  亮度区域纹理 _Bloom
        //  亮度阈值 _LuminanceThreshold
        
        //2.在CGINCLUDE...ENDCG中实现共享CG代码
        //  2-1:属性映射
        //  2-2:结构体(顶点,uv)
        //  2-3: 灰度值(亮度值)计算函数
        
        //3.屏幕后处理标配
        //  ZTest Always
        //  Cull Off
        //  ZWrite Off
        
        //4.提取Pass 实现
        //  顶点着色器 
        //      顶点转换、UV赋值
        //  片元着色器
        //      颜色采样、亮度贡献值计算、颜色*亮度贡献值

        //C#代码
        //1.创建C#脚本,名为Bloom
        //2.继承屏幕后处理基类PostEffectBase
        //3.声明亮度阈值成员变量
        //3.重写OnRenderImage函数
        //5.设置材质球的亮度阈值
        //4.在其中利用
        //  Graphics.Blit、RenderTexture.GetTemporary、 RenderTexture.ReleaseTemporary
        //  函数对纹理进行Pass处理 

        #endregion

        #region 第二步 模糊

        //主要目的:将提取出来的纹理进行模糊处理(一般采用高斯模糊)
        
        //Shader代码
        //1.添加模糊半径属性 _BlurSize,进行属性映射(注意:需要用到纹素)
        //2.修改之前高斯模糊Shader,为其中的两个Pass命名
        //3.在Bloom Shader中,利用UsePass 复用 高斯模糊Shader中两个Pass

        //C#代码
        //1.复制高斯模糊中的3个属性
        //2.复制高斯模糊中C#代码中处理高斯模糊的逻辑
        //3.将模糊处理后的纹理,存储_Bloom纹理属性

        #endregion

        #region 第三步 合成

        //主要目的:将模糊处理后的亮度纹理和源纹理进行颜色叠加
        
        //Shader代码
        //合并Pass实现
        //1.结构体
        //  顶点坐标,4维的uv(xy存主纹理,zw存亮度提取纹理)
        
        //2.顶点着色器
        //  顶点坐标转换,纹理坐标赋值
        //
        //  注意:亮度纹理的uv坐标需要判断是否进行Y轴翻转
        //  因为使用RenderTexture写入到Shader的纹理变量时
        //  Unity可能会对其进行Y轴翻转
        //  我们可以利用Unity提供的预处理宏进行判断
        //  #if UNITY_UV_STARTS_AT_TOP
        //  
        //  #endif
        //  如果这个宏被定义,说明当前平台的纹理坐标系的Y轴原点在顶部
        //  还可以在该宏中用纹素进行判断
        //  如果纹素的 y 小于0,为负数,表示需要对Y轴进行调整
        //  配合起来使用就是
        //  #if UNITY_UV_STARTS_AT_TOP
        //  if (_MainTex_TexelSize.y < 0.0)
        //      翻转uv的y轴坐标
        //  #endif
        //  主纹理不需要我们进行额外处理,一般Unity会自动处理
        //  一般只需要在使用RenderTexture时才考虑该问题

        //3.片元着色器
        //  两个纹理颜色采样后相加
        
        //4.FallBack Off

        
        //C#代码
        //对源纹理进行合并处理

        #endregion
    }
}

Lesson67_GaussianBlurPerfect.shader

Shader "Unlit/Lesson67_GaussianBlurPerfect"
{
    Properties
    {
        // 主纹理
        _MainTex ("Texture", 2D) = "white" {}
        //纹理偏移间隔单位
        _BlurSpread("BlurSpread", Float) = 1
    }
    SubShader
    {
        //用于包裹共用代码 在之后的多个Pass当中都可以使用的代码
        CGINCLUDE
        //内置文件
        #include "UnityCG.cginc"

        // 主纹理
        sampler2D _MainTex;

        //纹素 x=1/宽  y=1/高
        half4 _MainTex_TexelSize;

        //纹理偏移间隔单位
        float _BlurSpread;

        struct v2f
        {
            //5个像素的uv坐标偏移
            half2 uv[5] : TEXCOORD0;
            //顶点在裁剪空间下坐标
            float4 vertex : SV_POSITION;
        };

        //片元着色器函数
        //两个Pass可以使用同一个 我们把里面的逻辑写的通用即可
        fixed4 fragBlur(v2f v2f):SV_Target
        {
            //卷积运算
            //卷积核 其中的三个数 因为只有这三个数 没有必要声明为5个单位的卷积核
            float weight[3] = {0.4026, 0.2442, 0.0545};

            //先计算当前像素点
            fixed3 sum = tex2D(_MainTex, v2f.uv[0]).rgb * weight[0];

            //去计算左右偏移1个单位的 和 左右偏移两个单位的 对位相乘 累加
            // 其实就是遍历拿到index为1 2 3 4的uv采样并和对应的卷积核相乘
            // 1 2 对应0.2442
            // 3 4 对应0.0545
            for (int it = 1; it < 3; it++)
            {
                //要和右元素相乘
                sum += tex2D(_MainTex, v2f.uv[it * 2 - 1]).rgb * weight[it];
                //和左元素相乘
                sum += tex2D(_MainTex, v2f.uv[it * 2]).rgb * weight[it];
            }

            return fixed4(sum, 1);
        }
        ENDCG

        Tags
        {
            "RenderType"="Opaque"
        }

        // 设置深度测试模式为始终通过
        // 这意味着无论新绘制的片元的深度值与当前深度缓冲区中的值关系如何,该片元都将通过深度测试并有可能被绘制
        ZTest Always

        // 关闭背面剔除功能
        // 通常在渲染场景时,为了提高性能会剔除那些背对摄像机的面,这里关闭此功能后,所有面都会被考虑渲染
        Cull Off

        // 关闭深度写入功能
        // 即不将绘制的片元的深度值写入深度缓冲区,这样可能会导致一些渲染效果上的特殊情况,比如多个物体在深度上可能会出现重叠绘制的现象
        ZWrite Off

        Pass
        {
            Name "GAUSSIAN_BLUR_HORIZONTAL"
            CGPROGRAM
            #pragma vertex vertBlurHorizontal
            #pragma fragment fragBlur

            //水平方向的 顶点着色器函数
            v2f vertBlurHorizontal(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);

                //5个像素的uv偏移
                half2 uv = appdata_base.texcoord;

                //去进行5个像素 水平位置的偏移获取
                v2f.uv[0] = uv;
                v2f.uv[1] = uv + half2(_MainTex_TexelSize.x * 1, 0) * _BlurSpread;
                v2f.uv[2] = uv - half2(_MainTex_TexelSize.x * 1, 0) * _BlurSpread;
                v2f.uv[3] = uv + half2(_MainTex_TexelSize.x * 2, 0) * _BlurSpread;
                v2f.uv[4] = uv - half2(_MainTex_TexelSize.x * 2, 0) * _BlurSpread;

                return v2f;
            }
            ENDCG
        }

        Pass
        {
            Name "GAUSSIAN_BLUR_VERTICAL"
            CGPROGRAM
            #pragma vertex vertBlurVertical
            #pragma fragment fragBlur

            //竖直方向的 顶点着色器函数
            v2f vertBlurVertical(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);

                //5个像素的uv偏移
                half2 uv = appdata_base.texcoord;

                //去进行5个像素 水平位置的偏移获取
                v2f.uv[0] = uv;
                v2f.uv[1] = uv + half2(0, _MainTex_TexelSize.x * 1) * _BlurSpread;
                v2f.uv[2] = uv - half2(0, _MainTex_TexelSize.x * 1) * _BlurSpread;
                v2f.uv[3] = uv + half2(0, _MainTex_TexelSize.x * 2) * _BlurSpread;
                v2f.uv[4] = uv - half2(0, _MainTex_TexelSize.x * 2) * _BlurSpread;

                return v2f;
            }
            ENDCG
        }
    }

    Fallback Off
}

Lesson67_Bloom.shader

Shader "Unlit/Lesson67_Bloom"
{
    Properties
    {
        // 主纹理属性
        _MainTex ("Texture", 2D) = "white" {}

        // 用于存储亮度纹理模糊后的结果的纹理属性
        _Bloom("Bloom", 2D) = ""{}

        // 亮度阈值属性,用于控制亮度纹理中亮度区域的界定
        _LuminanceThreshold("LuminanceThreshold", Float) = 0.5

        // 模糊半径属性,用于控制模糊的程度
        _BlurSize("BlurSize", Float) = 1
    }

    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        // 主纹理
        sampler2D _MainTex;

        // 主纹理的纹素
        half4 _MainTex_TexelSize;

        // 存储亮度纹理模糊后结果
        sampler2D _Bloom;

        // 亮度阈值变量
        float _LuminanceThreshold;

        // 模糊半径变量
        float _BlurSize;

        struct v2f
        {
            // 纹理坐标,用于在片元着色器中采样纹理
            float2 uv : TEXCOORD0;
            // 顶点在裁剪空间中的位置
            float4 vertex : SV_POSITION;
        };

        // 计算颜色的亮度值(灰度值)的函数
        // 输入一个fixed4类型的颜色,按照一定的权重计算其亮度值并返回
        fixed luminance(fixed4 color)
        {
            // 根据常见的亮度计算权重公式,计算颜色的亮度值
            // 分别对颜色的红、绿、蓝通道乘以相应权重后相加得到亮度值
            return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
        }
        ENDCG

        // 始终进行深度测试
        ZTest Always
        // 关闭背面剔除
        Cull Off
        // 关闭深度写入
        ZWrite Off

        // 提取的Pass,用于提取亮度相关信息
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 顶点着色器函数,将输入的顶点数据进行处理并传递给片元着色器
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                // 将顶点从模型空间转换到裁剪空间
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
                // 获取顶点的纹理坐标
                v2f.uv = appdata_base.texcoord;
                return v2f;
            }

            // 片元着色器函数,根据提取的亮度信息对主纹理颜色进行处理
            fixed4 frag(v2f v2f):SV_Target
            {
                // 从主纹理中根据传入的纹理坐标采样得到颜色
                fixed4 color = tex2D(_MainTex, v2f.uv);
                // 计算颜色的亮度贡献值,先计算亮度与亮度阈值的差值,然后通过clamp函数限制在0到1之间
                fixed value = clamp(luminance(color) - _LuminanceThreshold, 0, 1);
                // 返回经过亮度贡献值调整后的颜色
                return color * value;
            }
            ENDCG
        }

        // 复用高斯模糊的2个Pass,分别是水平方向和垂直方向的模糊Pass
        UsePass "Unlit/Lesson67_GaussianBlurPerfect/GAUSSIAN_BLUR_HORIZONTAL"
        UsePass "Unlit/Lesson67_GaussianBlurPerfect/GAUSSIAN_BLUR_VERTICAL"
        
        // 用于合成的Pass,将主纹理和模糊后的亮度纹理进行合成
        Pass
        {
            CGPROGRAM
            #pragma vertex vertBloom
            #pragma fragment fragBloom

            // 定义新的结构体v2fBloom,用于在这个合成Pass的顶点着色器和片元着色器之间传递数据
            struct v2fBloom
            {
                // 顶点在裁剪空间中的位置,存储在SV_POSITION语义中
                float4 pos:SV_POSITION;
                // 一个half4类型的变量,xy主要用于对主纹理进行采样,zw主要用于对亮度模糊后的纹理采样
                half4 uv:TEXCOORD0;
            };

            // 合成Pass的顶点着色器函数,处理顶点数据并设置纹理采样坐标等
            v2fBloom vertBloom(appdata_base appdata_base)
            {
                v2fBloom v2fBloom;

                // 将顶点从模型空间转换到裁剪空间
                v2fBloom.pos = UnityObjectToClipPos(appdata_base.vertex);

                // 设置主纹理和亮度纹理要采样相同的地方进行颜色叠加,所以xy和zw都先设置为传入的纹理坐标
                v2fBloom.uv.xy = appdata_base.texcoord;
                v2fBloom.uv.zw = appdata_base.texcoord;

                // 用宏去判断uv坐标是否被翻转
                #if UNITY_UV_STARTS_AT_TOP
                // 如果纹素的y小于0 为负数,表示需要对Y轴进行调整
                if (_MainTex_TexelSize.y < 0)
                    v2fBloom.uv.w = 1 - v2fBloom.uv.w;
                #endif

                return v2fBloom;
            }

            // 合成Pass的片元着色器函数,将主纹理和模糊后的亮度纹理采样得到的颜色相加返回
            fixed4 fragBloom(v2fBloom v2fBloom):SV_TARGET
            {
                return tex2D(_MainTex, v2fBloom.uv.xy) + tex2D(_Bloom, v2fBloom.uv.zw);
            }
            ENDCG
        }
    }
    // 关闭回退,如果当前着色器无法运行,不会自动使用其他备用着色器
    Fallback Off
}

Lesson67_Bloom.cs

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

public class Lesson67_Bloom : PostEffectBase
{
    //亮度阈值变量
    [Range(0, 4)] public float luminanceThreshold = .5f;

    [Range(1, 8)] public int downSample = 1;
    [Range(1, 16)] public int iterations = 1;
    [Range(0, 3)] public float blurSpread = 0.6f;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //设置亮度阈值变量
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);

            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            //渲染纹理缓冲区
            RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
            //采用双线性过滤模式来缩放 可以让缩放效果更平滑
            buffer.filterMode = FilterMode.Bilinear;
            //第一步 提取处理 用我们的提取Pass去得到对应的亮度信息 存入到缓冲区纹理中
            Graphics.Blit(source, buffer, material, 0);

            //第二步 模糊处理
            //多次去执行 高斯模糊逻辑
            for (int i = 0; i < iterations; i++)
            {
                //如果想要模糊半径影响模糊想过更强烈 更平滑
                //一般可以在我们的迭代中进行设置 相当于每次迭代处理高斯模糊时 都在增加我们的间隔距离
                material.SetFloat("_BlurSpread", 1 + i * blurSpread);

                //又声明一个新的缓冲区
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                //因为我们需要用两个Pass 处理图像两次 
                //进行第一次 水平卷积计算
                Graphics.Blit(buffer, buffer1, material, 1); //Color1
                //这时 关键内容都在buffer1中 buffer没用了 释放掉
                RenderTexture.ReleaseTemporary(buffer);

                buffer = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                //进行第二次 垂直卷积计算
                Graphics.Blit(buffer, buffer1, material, 2); //在Color1的基础上乘上Color2 得到最终的高斯模糊计算结果
                //释放缓存区
                RenderTexture.ReleaseTemporary(buffer);
                //buffer和buffer1指向的都是这一次高斯模糊处理的结果
                buffer = buffer1;
            }

            //把提取出来的内容进行高斯模糊后 存储Shader当中的一个纹理变量
            //用于之后进行合成
            material.SetTexture("_Bloom", buffer);

            //测试 看到提取效果
            //Graphics.Blit(buffer, destination);

            //合成步骤
            Graphics.Blit(source, destination, material, 3);

            RenderTexture.ReleaseTemporary(buffer);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏