US4L8-5——噪声雾效

噪声雾效

因此所谓的噪声雾效,其实就是在我们之前已经实现的屏幕后处理效果中的全局雾效中,
结合噪声和内置时间参数,去实现出不规则、动态的全局雾效,让我们的雾效更具动态感,真实感!

实现的全局雾效相关内容可见:US3S11L6——深度纹理实现全局雾效

imageimage

噪声雾效基本原理

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

关键点:

  • 不均匀效果的实现

    我们可以利用柏林噪声算法生成的噪声纹理灰度图来制作不均匀感。
    从灰度图中采样得到 0~1 范围的值,再通过减去 0.5 的方式将其区间变为 -0.5~0.5 之间,
    并用这个系数参与最后的雾的混合因子计算中,从而让均匀的雾变得不均匀。

    PerlinNoinseTexture

    1
    2
    3
    4
    5
    6
    float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);            // 雾的动态计算
    float noise = (tex2D(_Noise, i.uv + speed).r - 0.5) * _NoiseAmount; // 把原本0~1的采样变化到-0.5~0.5,再乘以噪声系数,用于控制正负范围
    // ...
    float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); // 混合因子的计算
    // 取0~1之间,超过取极值,然后用该噪声系数参与到最终的雾的混合因子计算中,让雾的密度产生正负方向的动态扰动
    f = saturate(f * _FogDensity * (1 + noise));
  • 动态效果的实现

    类似水波效果,自定义 x 轴和 y 轴的两个速度变量。利用 Shader 内置时间参数 _Time.y​ 得到累积变化。
    用该速度变量从噪声纹理中偏移采样,从而达到动态效果。

    1
    2
    3
    4
    5
    6
    float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);            // 雾的动态计算
    float noise = (tex2D(_Noise, i.uv + speed).r - 0.5) * _NoiseAmount; // 把原本0~1的采样变化到-0.5~0.5,再乘以噪声系数,用于控制正负范围
    // ...
    float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); // 混合因子的计算
    // 取0~1之间,超过取极值,然后用该噪声系数参与到最终的雾的混合因子计算中,让雾的密度产生正负方向的动态扰动
    f = saturate(f * _FogDensity * (1 + noise));

噪声雾效 Shader 实现

  1. 新建 Shader,命名为 NoiseFog​,复制屏幕后处理效果的全局雾效Shader

    代码详见:US3S11L6——深度纹理实现全局雾效

  2. 修改 Shader 代码

    1. 属性添加

      • 噪声纹理 _Noise
      • 噪声值偏移系数 _NoiseAmount
      • X 轴移动速度 _FogXSpeed
      • Y 轴移动速度 _FogYSpeed

      属性映射

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      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 // X轴移动速度
      _FogYSpeed("FogYSpeed", Float) = 0.1 // Y轴移动速度
      }
    2. 片元着色器修改

      1. 速度计算,噪声纹理偏移采样,从 0~1 范围转到 -0.5~0.5 范围,再乘以 _NoiseAmount​ 噪声系数,用于控制正负范围

        1
        2
        3
        4
        // 计算噪声图采样偏移,把原本0~1的噪声范围值变化到-0.5~0.5,再乘以_NoiseAmount噪声系数,用于控制正负范围
        float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
        float noise = (tex2D(_Noise, i.uv + speed).r - 0.5) * _NoiseAmount;

      2. 参与雾混合因子计算

        1
        2
        // 乘以1 + noise是为了在正常计算出来的混合因子动进行上下的扰动
        f = saturate(f * _FogDensity * (1 + noise)); // 乘以雾的浓度,取0~1之间,超过则取极值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      fixed4 frag (v2f i) : SV_Target
      {
      // 获取观察空间下离摄像机的实际距离(Z分量)并计算世界空间下的像素坐标
      float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
      float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.ray;
      // 计算噪声图采样偏移,把原本0~1的噪声范围值变化到-0.5~0.5,在乘以_NoiseAmount噪声系数,用于控制正负范围
      float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
      float noise = (tex2D(_Noise, i.uv + speed).r - 0.5) * _NoiseAmount;

      // 雾相关的计算,可以根据自己需求修改计算方法,这里是基于高度的全局雾效
      float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); // 使用世界空间的像素坐标高度计算混合因子
      // 乘以1 + noise是为了在正常计算出来的混合因子动进行上下的扰动
      f = saturate(f * _FogDensity * (1 + noise)); // 乘以雾的浓度,取0~1之间,超过则取极值
      // 利用插值,在两个颜色之间进行融合
      fixed3 color = lerp(tex2D(_MainTex, i.uv).rgb, _FogColor.rgb, f);

      return fixed4(color.rgb, 1);
      }

噪声雾效 C# 实现

  1. 新建 C# 代码 NoiseFog

  2. 复用屏幕后处理效果的全局雾效 C# 代码 FogWithDepthTexture

    代码详见:US3S11L6——深度纹理实现全局雾效

  3. 修改 C# 代码

    1. 添加成员变量

      纹理、系数、速度

    2. 设置 Shader 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using UnityEngine;

public class NoiseFog : PostEffect
{
public Color fogColor = Color.gray; // 雾的颜色
[Range(0f, 1f)] public float fogDensity = 1f; // 雾的浓度
public float fogStart = 0f; // 雾开始的距离
public float fogEnd = 5f; // 雾最浓时的距离
private Matrix4x4 rayMatrix; // 用于传递4个向量的参数
public Texture noiseTexture; // 噪声纹理
public float noiseAmount = 1f; // 噪声值偏移系数
public float fogXSpeed = 0.1f; // X轴移动速度
public float fogYSpeed = 0.1f; // Y轴移动速度

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

protected override void UpdateProperty()
{
if (PostEffectMaterial == null)
return;

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 topLeft = Camera.main.transform.forward * near + toTop - toRight;
Vector3 topRight = Camera.main.transform.forward * near + toTop + toRight;
Vector3 bottomLeft = Camera.main.transform.forward * near - toTop - toRight;
Vector3 bottomRight = Camera.main.transform.forward * near - toTop + toRight;
// 为了让深度值计算出来的是两点间距离,所以需要乘以一个缩放值,得到真正的需要的四条射线的向量
float scale = topLeft.magnitude / near;
topLeft = topLeft.normalized * scale;
topRight = topRight.normalized * scale;
bottomLeft = bottomLeft.normalized * scale;
bottomRight = bottomRight.normalized * scale;
// 注意:为了方便之后考虑 UV 翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
rayMatrix.SetRow(0, bottomLeft);
rayMatrix.SetRow(1, bottomRight);
rayMatrix.SetRow(2, topLeft);
rayMatrix.SetRow(3, topRight);
// 设置材质球相关属性(Shader属性)
PostEffectMaterial.SetColor("_FogColor", fogColor);
PostEffectMaterial.SetFloat("_FogDensity", fogDensity);
PostEffectMaterial.SetFloat("_FogStart", fogStart);
PostEffectMaterial.SetFloat("_FogEnd", fogEnd);
PostEffectMaterial.SetMatrix("_RayMatrix", rayMatrix);
PostEffectMaterial.SetTexture("_Noise", noiseTexture);
PostEffectMaterial.SetFloat("_NoiseAmount", noiseAmount);
PostEffectMaterial.SetFloat("_FogXSpeed", fogXSpeed);
PostEffectMaterial.SetFloat("_FogYSpeed", fogYSpeed);
}
}

显示效果

噪声值偏移系数为1,X轴和Y轴移动速度都为0.1

image

可见,雾效呈现出来一种不均匀的,飘动的效果,雾的效果更接近于现实中的团雾了