23.噪声雾效具体实现

23.噪声-噪声雾效-具体实现


23.1 知识点

知识回顾:噪声雾效 基本原理

噪声雾效是在之前实现的屏幕后处理全局雾效的基础上进行修改,通过添加噪声纹理并结合 Shader 内置时间变量,实现雾效的不均匀分布以及动态变化效果。

关键点包括:

  • 不均匀效果的实现
    从噪声纹理中采样一个系数,并让该系数参与雾混合因子的计算。
  • 动态效果的实现
    自定义 X 轴和 Y 轴的移动速度变量,利用 Shader 内置时间参数 _Time.y 累积变化,并将计算结果用于噪声纹理的偏移采样,达到动态变化的效果。

噪声雾效 Shader 实现

主要步骤

  • 复制文件 Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现
  • 新建 Shader Lesson23_NoiseFog,删除不需要的代码,并复制上述全局雾效 Shader(文件名:Lesson78_FogWithDepthTexture)。
  • 修改 Shader 代码:
    • 属性添加
      添加噪声纹理 _Noise、噪声值偏移系数 _NoiseAmount、X、Y 轴移动速度 _FogXSpeed_FogYSpeed,并进行属性映射。
    • 速度计算与噪声采样
      在片元着色器中计算移动速度,通过偏移采样噪声纹理,将采样结果从 01 转换为 -0.50.5。
    • 混合因子计算
      将噪声值参与雾混合因子的计算,产生上下扰动的效果。

复制 Shader开发基础中 Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现的场景

新建Shader Lesson23_NoiseFog 删除相关代码 复制Shader开发基础中 Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现 屏幕后处理效果的全局雾效的shader Lesson78_FogWithDepthTexture

Shader "Unlit/Lesson23_NoiseFog"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {} // 主纹理
        _FogColor("FogColor", Color) = (1,1,1,1) // 雾的颜色
        _FogDensity("FogDensity", Float) = 1 // 雾的密度
        _FogStart("FogStart", Float) = 0 // 雾的起始高度
        _FogEnd("FogEnd", Float) = 10 // 雾的结束高度
    }
    SubShader
    {
        ZTest Always // 始终通过深度测试
        Cull Off // 关闭背面剔除
        ZWrite Off // 禁止写入深度缓冲

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex; // 主纹理采样器
            half4 _MainTex_TexelSize; // 主纹理像素大小(检查可能的翻转检测)
            sampler2D _CameraDepthTexture; // 深度纹理采样器(定义好的,注意不能拼错!)
            fixed4 _FogColor; // 雾的颜色
            fixed _FogDensity; // 雾的密度
            float _FogStart; // 雾的起始高度
            float _FogEnd; // 雾的结束高度
            //矩阵相关 里面存储了 4条射线向量
            //0-左下 1-右下 2-右上 3-左上
            float4x4 _RayMatrix; // 存储四个角的射线向量矩阵

            struct v2f
            {
                float2 uv : TEXCOORD0; // 主纹理的UV坐标
                float2 uv_depth : TEXCOORD1; // 深度纹理的UV坐标
                float4 ray : TEXCOORD2; // 射线方向,用于计算像素的世界空间坐标,指向四个角的方向向量 (传递到片元时 会自动进行插值 运算)
                float4 vertex : SV_POSITION; // 顶点位置(裁剪空间)
            };


            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 转换为裁剪空间
                v2f.uv = appdata_base.texcoord; // 传递UV坐标
                v2f.uv_depth = appdata_base.texcoord; //先给深度纹理的UV坐标赋值一下

                //顶点着色器函数 每一个顶点都会执行一次
                //对于屏幕后处理来说 就会执行4次 因为有4个顶点 (4个角)
                //通过uv坐标判断 当前的顶点位置
                int index = 0;
                if (appdata_base.texcoord.x < 0.5 && appdata_base.texcoord.y < 0.5)
                    index = 0;
                else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y < 0.5)
                    index = 1;
                else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y > 0.5)
                    index = 2;
                else
                    index = 3;

                //判断 是否需要进行纹理翻转 如果翻转了 深度的uv和对应顶点需要变化
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                {
                    v2f.uv_depth.y = 1 - v2f.uv_depth.y;
                    //索引也要变 比如左上变左下
                    index = 3 - index;
                }
                #endif

                //根据顶点的位置 决定使用那一个射线向量
                v2f.ray = _RayMatrix[index];
                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //观察空间下 离摄像机的实际距离(Z分量)
                float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, v2f.uv_depth));

                //计算世界空间下 像素的坐标
                float3 worldPos = _WorldSpaceCameraPos + linearDepth * v2f.ray;

                //雾相关的计算
                //混合因子 
                float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
                //取0~1之间 超过会取极值
                f = saturate(f * _FogDensity);
                //利用插值 在两个颜色之间进行融合
                fixed3 color = lerp(tex2D(_MainTex, v2f.uv).rgb, _FogColor.rgb, f);

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback Off// 不使用回退Shader
}

添加属性,包括噪声纹理,噪声值偏移系数,X、Y轴移动速度,并进行属性映射

Properties
{
    _MainTex ("Texture", 2D) = "white" {} // 主纹理
    _FogColor("FogColor", Color) = (1,1,1,1) // 雾的颜色
    _FogDensity("FogDensity", Float) = 1 // 雾的密度
    _FogStart("FogStart", Float) = 0 // 雾的起始高度
    _FogEnd("FogEnd", Float) = 10 // 雾的结束高度

    // 决定不均匀效果的噪声纹理
    _Noise("Noise", 2D) = ""{}
    // 噪声系数:控制偏移范围
    _NoiseAmount("NoiseAmount", Float) = 1
    // 动态移动速度(X 轴)
    _FogXSpeed("FogXSpeed", Float) = 0.1
    // 动态移动速度(Y 轴)
    _FogYSpeed("FogYSpeed", Float) = 0.1
}
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 主纹理像素大小(检查可能的翻转检测)
sampler2D _CameraDepthTexture; // 深度纹理采样器(定义好的,注意拼写)
fixed4 _FogColor; // 雾的颜色
fixed _FogDensity; // 雾的密度
float _FogStart; // 雾的起始高度
float _FogEnd; // 雾的结束高度
// 存储 4 条射线向量的矩阵(0-左下,1-右下,2-右上,3-左上)
float4x4 _RayMatrix; 

sampler2D _Noise;      // 决定不均匀效果的噪声纹理
float _NoiseAmount;    // 噪声系数,控制偏移范围
float _FogXSpeed;      // 动态移动 X 速度
float _FogYSpeed;      // 动态移动 Y 速度

片元着色器中计算速度,噪声纹理偏移采样得到噪声值并参与雾混合因子计算

fixed4 frag(v2f v2f) : SV_Target
{
    //观察空间下 离摄像机的实际距离(Z分量)
    float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, v2f.uv_depth));
    //计算世界空间下 像素的坐标
    float3 worldPos = _WorldSpaceCameraPos + linearDepth * v2f.ray;

    //噪声纹理采样以及偏移
    float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
    //噪声纹理中采样 采样出来的结果是0~1 我们需要将其转为 -0.5~0.5
    //然后还可以通过自定义系数_NoiseAmount控制这个正负范围
    float noise = (tex2D(_Noise, v2f.uv + speed).r - 0.5) * _NoiseAmount;
    
    //雾相关的计算
    //混合因子 
    float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
    
    // //取0~1之间 超过会取极值
    // f = saturate(f * _FogDensity);
    //之所以乘以1 + noise,是为了在正常计算出来的混合因子中去进行 上下的扰动
    f = saturate(f * _FogDensity * (1 + noise));
    
    //利用插值 在两个颜色之间进行融合
    fixed3 color = lerp(tex2D(_MainTex, v2f.uv).rgb, _FogColor.rgb, f);

    return fixed4(color.rgb, 1);
}

噪声雾效 C# 实现

主要步骤

  • 新建 C# 脚本 Lesson23_NoiseFog,复制屏幕后处理全局雾效的 C# 代码(文件:Lesson78_FogWithDepthTexture.cs)。
  • 修改 C# 代码:
    • 添加成员变量:纹理、系数、速度。
    • 设置 Shader 参数

新建C#代码 Lesson23_NoiseFog 复制Shader开发基础中 屏幕后处理效果的全局雾效C#代码 Lesson78_FogWithDepthTexture.cs

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

public class Lesson23_NoiseFog : PostEffectBase
{
    //雾的颜色
    public Color fogColor = Color.gray;

    //雾的浓度
    [Range(0, 3)] public float fogDensity = 1f;

    //雾开始的距离
    public float fogStart = 0f;

    //雾最浓时的距离
    public float fogEnd = 5;

    //4x4的矩阵 用于传递 4个向量参数
    private Matrix4x4 rayMatrix;


    void Start()
    {
        Camera.main.depthTextureMode = DepthTextureMode.Depth;
    }

    protected override void UpdateProperty()
    {
        if (material != null)
        {
            //得到摄像机 视口 夹角度数
            float fov = Camera.main.fieldOfView / 2f;
            //得到近裁剪面距离
            float near = Camera.main.nearClipPlane;
            //得到窗口比例
            float aspect = Camera.main.aspect;

            //计算高的一半 
            float halfH = near * Mathf.Tan(fov * Mathf.Deg2Rad);
            //宽的一半
            float halfW = halfH * aspect;

            //计算竖直向上的和水平向右的偏移向量
            Vector3 toTop = Camera.main.transform.up * halfH;
            Vector3 toRight = Camera.main.transform.right * halfW;

            //算出指向四个顶点的向量
            Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;
            Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;
            Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;
            Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;

            //为了让深度值 计算出来是两点间距离 所以需要乘以一个缩放值
            float scale = TL.magnitude / near;

            //向量乘上缩放值 得到真正的最终想要的四条射线向量
            TL = TL.normalized * scale;
            TR = TR.normalized * scale;
            BL = BL.normalized * scale;
            BR = BR.normalized * scale;

            //通过4x4的矩阵装载各摄像向量,传递给材质
            //为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
            rayMatrix.SetRow(0, BL);
            rayMatrix.SetRow(1, BR);
            rayMatrix.SetRow(2, TR);
            rayMatrix.SetRow(3, TL);

            //设置材质球相关属性(Shader属性)
            material.SetColor("_FogColor", fogColor);
            material.SetFloat("_FogDensity", fogDensity);
            material.SetFloat("_FogStart", fogStart);
            material.SetFloat("_FogEnd", fogEnd);
            material.SetMatrix("_RayMatrix", rayMatrix);
        }
    }
}

添加成员变量,包括纹理、系数、速度

public Texture noiseTexture; // 噪声纹理
public float noiseAmount;    // 噪声系数
public float fogXSpeed;      // 噪声 X 轴移动速度
public float fogYSpeed;      // 噪声 Y 轴移动速度

设置 Shader 参数

protected override void UpdateProperty()
{
    if (material != null)
    {
        // ...

        // 设置噪声相关参数
        material.SetTexture("_Noise", noiseTexture);
        material.SetFloat("_NoiseAmount", noiseAmount);
        material.SetFloat("_FogXSpeed", fogXSpeed);
        material.SetFloat("_FogYSpeed", fogYSpeed);
    }
}

挂载C#脚本到摄像机,赋值shader和贴图, 修改参数可以看到效果


23.2 知识点代码

Lesson23_NoiseFog.shader

Shader "Unlit/Lesson23_NoiseFog"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {} // 主纹理
        _FogColor("FogColor", Color) = (1,1,1,1) // 雾的颜色
        _FogDensity("FogDensity", Float) = 1 // 雾的密度
        _FogStart("FogStart", Float) = 0 // 雾的起始高度
        _FogEnd("FogEnd", Float) = 10 // 雾的结束高度

        //决定不均匀效果的噪声纹理
        _Noise("Noise", 2D) = ""{}
        //噪声系数 控制 偏移范围的
        _NoiseAmount("NoiseAmount", Float) = 1
        //用于控制动态移动速度的
        _FogXSpeed("FogXSpeed", Float) = 0.1
        _FogYSpeed("FogYSpeed", Float) = 0.1
    }
    SubShader
    {
        ZTest Always // 始终通过深度测试
        Cull Off // 关闭背面剔除
        ZWrite Off // 禁止写入深度缓冲

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex; // 主纹理采样器
            half4 _MainTex_TexelSize; // 主纹理像素大小(检查可能的翻转检测)
            sampler2D _CameraDepthTexture; // 深度纹理采样器(定义好的,注意不能拼错!)
            fixed4 _FogColor; // 雾的颜色
            fixed _FogDensity; // 雾的密度
            float _FogStart; // 雾的起始高度
            float _FogEnd; // 雾的结束高度
            //矩阵相关 里面存储了 4条射线向量
            //0-左下 1-右下 2-右上 3-左上
            float4x4 _RayMatrix; // 存储四个角的射线向量矩阵

            sampler2D _Noise;//决定不均匀效果的噪声纹理
            float _NoiseAmount;//噪声系数 控制 偏移范围的
            float _FogXSpeed;//动态移动X速度
            float _FogYSpeed;//动态移动Y速度
            
            struct v2f
            {
                float2 uv : TEXCOORD0; // 主纹理的UV坐标
                float2 uv_depth : TEXCOORD1; // 深度纹理的UV坐标
                float4 ray : TEXCOORD2; // 射线方向,用于计算像素的世界空间坐标,指向四个角的方向向量 (传递到片元时 会自动进行插值 运算)
                float4 vertex : SV_POSITION; // 顶点位置(裁剪空间)
            };


            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 转换为裁剪空间
                v2f.uv = appdata_base.texcoord; // 传递UV坐标
                v2f.uv_depth = appdata_base.texcoord; //先给深度纹理的UV坐标赋值一下

                //顶点着色器函数 每一个顶点都会执行一次
                //对于屏幕后处理来说 就会执行4次 因为有4个顶点 (4个角)
                //通过uv坐标判断 当前的顶点位置
                int index = 0;
                if (appdata_base.texcoord.x < 0.5 && appdata_base.texcoord.y < 0.5)
                    index = 0;
                else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y < 0.5)
                    index = 1;
                else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y > 0.5)
                    index = 2;
                else
                    index = 3;

                //判断 是否需要进行纹理翻转 如果翻转了 深度的uv和对应顶点需要变化
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                {
                    v2f.uv_depth.y = 1 - v2f.uv_depth.y;
                    //索引也要变 比如左上变左下
                    index = 3 - index;
                }
                #endif

                //根据顶点的位置 决定使用那一个射线向量
                v2f.ray = _RayMatrix[index];
                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //观察空间下 离摄像机的实际距离(Z分量)
                float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, v2f.uv_depth));
                //计算世界空间下 像素的坐标
                float3 worldPos = _WorldSpaceCameraPos + linearDepth * v2f.ray;

                //噪声纹理采样以及偏移
                float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
                //噪声纹理中采样 采样出来的结果是0~1 我们需要将其转为 -0.5~0.5
                //然后还可以通过自定义系数_NoiseAmount控制这个正负范围
                float noise = (tex2D(_Noise, v2f.uv + speed).r - 0.5) * _NoiseAmount;
                
                //雾相关的计算
                //混合因子 
                float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
                
                // //取0~1之间 超过会取极值
                // f = saturate(f * _FogDensity);
                //之所以乘以1 + noise,是为了在正常计算出来的混合因子中去进行 上下的扰动
                f = saturate(f * _FogDensity * (1 + noise));
                
                //利用插值 在两个颜色之间进行融合
                fixed3 color = lerp(tex2D(_MainTex, v2f.uv).rgb, _FogColor.rgb, f);

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback Off// 不使用回退Shader
}

Lesson23_NoiseFog.cs

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

public class Lesson23_NoiseFog : PostEffectBase
{
    //雾的颜色
    public Color fogColor = Color.gray;

    //雾的浓度
    [Range(0, 3)] public float fogDensity = 1f;

    //雾开始的距离
    public float fogStart = 0f;

    //雾最浓时的距离
    public float fogEnd = 5;

    //4x4的矩阵 用于传递 4个向量参数
    private Matrix4x4 rayMatrix;

    public Texture noiseTexture; //噪声纹理
    public float noiseAmount; //噪声系数
    public float fogXSpeed; //噪声X速度
    public float fogYSpeed; //噪声Y速度

    void Start()
    {
        Camera.main.depthTextureMode = DepthTextureMode.Depth;
    }

    protected override void UpdateProperty()
    {
        if (material != null)
        {
            //得到摄像机 视口 夹角度数
            float fov = Camera.main.fieldOfView / 2f;
            //得到近裁剪面距离
            float near = Camera.main.nearClipPlane;
            //得到窗口比例
            float aspect = Camera.main.aspect;

            //计算高的一半 
            float halfH = near * Mathf.Tan(fov * Mathf.Deg2Rad);
            //宽的一半
            float halfW = halfH * aspect;

            //计算竖直向上的和水平向右的偏移向量
            Vector3 toTop = Camera.main.transform.up * halfH;
            Vector3 toRight = Camera.main.transform.right * halfW;

            //算出指向四个顶点的向量
            Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;
            Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;
            Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;
            Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;

            //为了让深度值 计算出来是两点间距离 所以需要乘以一个缩放值
            float scale = TL.magnitude / near;

            //向量乘上缩放值 得到真正的最终想要的四条射线向量
            TL = TL.normalized * scale;
            TR = TR.normalized * scale;
            BL = BL.normalized * scale;
            BR = BR.normalized * scale;

            //通过4x4的矩阵装载各摄像向量,传递给材质
            //为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
            rayMatrix.SetRow(0, BL);
            rayMatrix.SetRow(1, BR);
            rayMatrix.SetRow(2, TR);
            rayMatrix.SetRow(3, TL);

            //设置材质球相关属性(Shader属性)
            material.SetColor("_FogColor", fogColor);
            material.SetFloat("_FogDensity", fogDensity);
            material.SetFloat("_FogStart", fogStart);
            material.SetFloat("_FogEnd", fogEnd);
            material.SetMatrix("_RayMatrix", rayMatrix);
            
            //设置噪声相关
            material.SetTexture("_Noise", noiseTexture);
            material.SetFloat("_NoiseAmount", noiseAmount);
            material.SetFloat("_FogXSpeed", fogXSpeed);
            material.SetFloat("_FogYSpeed", fogYSpeed);
        }
    }
}

Lesson23_噪声_噪声雾效_具体实现.cs

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

public class Lesson23_噪声_噪声雾效_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 噪声雾效 基本原理

        //噪声雾效可以基于我们之前实现的屏幕后处理效果的全局雾效进行修改
        //通过添加噪声纹理结合Shader内置时间变量实现雾的不均匀以及动态效果

        //关键点:
        //1.不均匀效果的实现
        //从噪声纹理中采样系数,让该系数参与雾的混合因子的计算中
        //2.动态效果的实现
        //自定义x轴和y轴的两个速度变量,利用Shader内置时间参数_Time.y 得到累积变化。
        //用计算结果在噪声纹理中进行偏移采样,从而达到动态效果。

        #endregion

        #region 知识点一 噪声雾效 Shader实现

        //1.复制  Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现
        //2.新建Shader Lesson23_NoiseFog 删除相关代码 复制Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现 屏幕后处理效果的全局雾效的shader Lesson78_FogWithDepthTexture
        //3.修改Shader代码
        //  3-1.属性添加
        //      噪声纹理 _Noise
        //      噪声值偏移系数 _NoiseAmount
        //      X、Y轴移动速度 _FogXSpeed, FogYSpeed
        //      属性映射
        //  3-2.速度计算,噪声纹理偏移采样,0~1 转 -0.5~0.5
        //  3-3.参与雾混合因子计算

        #endregion

        #region 知识点二 噪声雾效 C#实现

        //1.新建C#代码 Lesson23_NoiseFog 复制屏幕后处理效果的全局雾效C#代码 Lesson78_FogWithDepthTexture.cs
        //2.修改C#代码
        //  2-1.添加成员变量
        //      纹理、系数、速度
        //  2-2.设置Shader参数

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏