0%

【Unity Shader】(十)噪声纹理

很多时候,向规则的事物里添加一些“杂乱无章”的效果往往会有意想不到的效果。而这些“杂乱无章”的效果来源就是噪声。在本节中,我们将会学习如何使用噪声来模拟火焰消融、波光粼粼、云雾飘渺等各种特效。

1 消融效果

消融 (dissolve) 效果常见于游戏中的角色死亡、地图烧毁等效果。在这些效果中,消融往往从不同的区域开始,并向看似随机的方向扩张,最后整个物体都将消失不见。消融效果的原理非常简单,概括来说就是噪声纹理+透明度测试。我们使用对噪声纹理采样的结果和某个控制消融程度的阈值比较,如果小于阈值,就使用 clip 函数把它对应的像素裁剪掉,这些部分就对应了图中被“烧毁”的区域。而镂空区域边缘的烧焦效果则是将两种颜色混合,再用 pow 函数处理后,与原纹理颜色混合后的结果。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
Shader "Unity Shaders Book/Chapter 15/Dissolve" {
Properties {
// 用于控制消融程度,值为1时物体完全消融
_BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
// 模拟烧焦效果时的线宽,值越大,火焰边缘的蔓延范围越广
_LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1
// 主纹理
_MainTex ("Base (RGB)", 2D) = "white" {}
// 法线纹理
_BumpMap ("Normal Map", 2D) = "bump" {}
// 火焰边缘的两种颜色值
_BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)
_BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
// 噪声纹理
_BurnMap("Burn Map", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

// 关闭剔除,因为模型消融会使内部构造显现出来
Cull Off

CGPROGRAM

#include "Lighting.cginc"
#include "AutoLight.cginc"

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
sampler2D _BurnMap;

float4 _MainTex_ST;
float4 _BumpMap_ST;
float4 _BurnMap_ST;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvBumpMap : TEXCOORD1;
float2 uvBurnMap : TEXCOORD2;
float3 lightDir : TEXCOORD3;
float3 worldPos : TEXCOORD4;
SHADOW_COORDS(5)
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
// 光线从模型空间变换到切线空间,因为用到了法线纹理,要在切线空间计算光照
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
// 为了得到阴影信息,计算世界空间下的顶点位置以及阴影纹理的采样坐标
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);

return o;
}

fixed4 frag(v2f i) : SV_Target {
// 对噪声纹理采样
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
// 采样值和控制消融程度的参数相减传递给clip函数
// 当结果小于0时该像素会被剔除,从而不显示到屏幕上
clip(burn.r - _BurnAmount);
// 切线空间的光照方向
float3 tangentLightDir = normalize(i.lightDir);
// 对法线纹理采样
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));
// 反射率
fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
// 在切线空间计算漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

// 计算烧焦的颜色,要在宽度为_LineWidth范围内模拟一个烧焦的颜色变化
// 首先计算混合系数t,如果t的值为1说明位于消融的边界,t为0时像素为正常的模型颜色
fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
// 使用t混合两种烧焦颜色
fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
// 使用pow对结果进行处理得到最终的烧焦颜色
burnColor = pow(burnColor, 5);

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// 再次使用t来混和光照颜色和烧焦颜色,使用step函数保证_BurnAmount为0时不显示任何消融效果
fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

return fixed4(finalColor, 1);
}

ENDCG
}

// 投射阴影的Pass
// 因为用到了透明度测试,因此不能用默认的阴影投射Pass,不然被剔除的部分也会投射阴影造成穿帮
Pass {
// 正确设置tags
Tags { "LightMode" = "ShadowCaster" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
// 对应的编译指令
#pragma multi_compile_shadowcaster

#include "UnityCG.cginc"

fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;

struct v2f {
// 使用Unity内置宏定义投射阴影需要的变量,已经包含了各种需要用到的变量
V2F_SHADOW_CASTER;
float2 uvBurnMap : TEXCOORD1;
};

// 使用Unity内置的宏来计算阴影需要一些特定的输入
// 比如TRANSFER_SHADOW_CASTER_NORMALOFFSET会使用名称v作为输入结构体,v中需要包含顶点位置和顶点法线
// 内置的appdata_base结构体提供了这些必要的变量
v2f vert(appdata_base v) {
v2f o;
// 使用内置的宏填充上面声明的各种变量
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
clip(burn.r - _BurnAmount);
// 剔除片元后,使用内置的宏计算阴影投射
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
}

接下来编写一个 cs 脚本来随着时间控制材质的 _BurnAmount 属性:

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
using UnityEngine;
using System.Collections;

public class BurnHelper : MonoBehaviour {

public Material material;

[Range(0.01f, 1.0f)]
public float burnSpeed = 0.3f;

private float burnAmount = 0.0f;

void Start () {
if (material == null) {
Renderer renderer = gameObject.GetComponentInChildren<Renderer>();
if (renderer != null) {
material = renderer.material;
}
}

if (material == null) {
this.enabled = false;
} else {
material.SetFloat("_BurnAmount", 0.0f);
}
}

void Update () {
burnAmount = Mathf.Repeat(Time.time * burnSpeed, 1.0f);
material.SetFloat("_BurnAmount", burnAmount);
}
}

最终的效果如下:

burn

2 水波效果

在模拟实时水面的过程中,我们往往也会使用噪声纹理。此时,噪声纹理通常会用作一个高度图,以不断修改水面的法线方向。为了模拟水不断流动的效果,我们会使用和时间相关的变量来对噪声纹理进行采样,当得到法线信息后,再进行正常的反射+折射计算,得到最后的水面波动效果。在本节中,我们将会使用一个由噪声纹理得到的法线贴图,实现一个包含菲涅耳反射的水面效果。

首先我们定义一个平面作为水面,类似于之前我们利用反射和折射实现透明玻璃的效果,我们使用一张立方体纹理 (Cubemap) 作为环境纹理,模拟反射;使用 GrabPass 来获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标进行偏移,再使用该坐标对渲染纹理进行屏幕采样,从而模拟近似的折射效果。但与之前的实现不同,水波的法线纹理是由一张噪声纹理生成而得,而且会随着时间不断平移变化,模拟波光粼粼的效果。此外,我们没有使用一个定值来混合反射和折射的颜色,而是使用之前提到的菲涅耳系数来动态决定混合系数,我们使用如下公式计算菲涅尔系数:
$$
fresnel = pow(1-max(0,\vec v ·\vec n),4)
$$
其中 $\vec v$ 和 $\vec n$ 分别对应了视角方向和法线方向,他们之间的夹角越小,得到的菲涅尔系数就越小,反射越弱,折射越强。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 15/Water Wave" {
Properties {
// 用于控制水面颜色
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1)
// 水面波纹材质纹理
_MainTex ("Base (RGB)", 2D) = "white" {}
// 噪声纹理生成的法线纹理
_WaveMap ("Wave Map", 2D) = "bump" {}
// 环境纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
// 控制法线纹理再x和y方向上的平移速度
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
// 控制模拟折射时的扭曲程度
_Distortion ("Distortion", Range(0, 100)) = 10
}
SubShader {
// 透明队列,在所有不透明物体之后渲染
Tags { "Queue"="Transparent" "RenderType"="Opaque" }

// 获取屏幕图像作为渲染纹理
GrabPass { "_RefractionTex" }

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#include "UnityCG.cginc"
#include "Lighting.cginc"

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 根据裁剪空间坐标计算被抓取的屏幕图像的采样坐标
o.scrPos = ComputeGrabScreenPos(o.pos);
// 纹理坐标转换
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
// 构造切线空间到世界空间的变换矩阵,并存储世界空间下的顶点坐标
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

return o;
}

fixed4 frag(v2f i) : SV_Target {
// 得到世界坐标
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
// 根据世界坐标计算世界空间的视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 计算法线纹理当前在x和y方向的偏移量
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);

// 对法线纹理采样两次,为了得到两层交叉的水面波动效果
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
// 两次采样结果相加并归一化得到切线空间下的法线方向
fixed3 bump = normalize(bump1 + bump2);

// 利用切线空间的法线方向计算对屏幕图像的采样坐标偏移
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
// 偏移量乘以z分量是为了得到深度越大折射程度越大的效果
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
// 经过透视除法后对屏幕图像采样得到模拟的折射颜色
fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

// 法线转换到世界空间
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
// 采样得到水波颜色,也要随时间偏移
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
// 根据世界空间下的法线方向和视线方向计算得到反射方向
fixed3 reflDir = reflect(-viewDir, bump);
// 利用反射方向对环境贴图采样,并结合水波颜色得到最终的反射颜色
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;
// 计算菲涅尔系数
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
// 利用菲涅尔系数结合反射颜色和折射颜色
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);

return fixed4(finalColor, 1);
}

ENDCG
}
}
// 不要投射阴影,因此关掉FallBack
FallBack Off
}

为了用噪声纹理生成法线纹理,我们在该噪声纹理的属性面板中把纹理类型改为 Normal map,并勾选 Create from grayscale,使其从灰度值生成法线纹理:

image-20220410161026859

渲染效果如下:

water

此时我们的视线方向和水面法线夹角大,反射强,折射弱,我们更容易看到水面的颜色,而水面的颜色是环境反射颜色和水波本身颜色的结合;当我们减小视线方向和水面法线夹角时,效果如下:

water2

此时反射弱,折射强,我们不容易看到水面本身的颜色,更容易透过水面看到折射后的扭曲的地面。

3 再谈全局雾效

我们之前学习了如何使用深度纹理来实现一种基于屏幕后处理的全局雾效。我们由深度纹理重建每个像素在世界空间下的位置,再使用一个基于高度的公式来计算雾效的混合系数,最后使用该系数来混合雾的颜色和原屏幕颜色。这样实现的效果在同一高度下雾的浓度是相同的,但很多时候我们希望可以模拟一种不均匀的雾效,同时让雾不断飘动,使雾看起来更加飘渺,这就可以通过使用噪声纹理来实现。

大部分代码和之前的全局雾效一样,只不过加入了噪声相关的参数和属性,并在 Shader 的片元着色器中对雾效混合系数的计算添加了噪声的影响。

首先是 cs 脚本:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using UnityEngine;
using System.Collections;

public class FogWithNoise : PostEffectsBase {

public Shader fogShader;
private Material fogMaterial = null;
public Material material {
get {
fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}

private Camera myCamera;
public Camera camera {
get {
if (myCamera == null) {
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}

private Transform myCameraTransform;
public Transform cameraTransform {
get {
if (myCameraTransform == null) {
myCameraTransform = camera.transform;
}

return myCameraTransform;
}
}

[Range(0.1f, 3.0f)]
public float fogDensity = 1.0f;

public Color fogColor = Color.white;

public float fogStart = 0.0f;
public float fogEnd = 2.0f;

// 定义噪声纹理的变量
public Texture noiseTexture;
// 噪声纹理在x和y方向的移动速度,模拟飘渺的效果
[Range(-0.5f, 0.5f)]
public float fogXSpeed = 0.1f;
[Range(-0.5f, 0.5f)]
public float fogYSpeed = 0.1f;
// 控制噪声程度,值为0代表不应用噪声,得到和之前一样的均匀雾效
[Range(0.0f, 3.0f)]
public float noiseAmount = 1.0f;

// 脚本可用时,摄像机生成深度纹理
void OnEnable() {
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
}

void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
Matrix4x4 frustumCorners = Matrix4x4.identity;

float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float aspect = camera.aspect;

float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cameraTransform.right * halfHeight * aspect;
Vector3 toTop = cameraTransform.up * halfHeight;

Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near;

topLeft.Normalize();
topLeft *= scale;

Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;

Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;

Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale;

frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);

material.SetMatrix("_FrustumCornersRay", frustumCorners);

material.SetFloat("_FogDensity", fogDensity);
material.SetColor("_FogColor", fogColor);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);

material.SetTexture("_NoiseTex", noiseTexture);
material.SetFloat("_FogXSpeed", fogXSpeed);
material.SetFloat("_FogYSpeed", fogYSpeed);
material.SetFloat("_NoiseAmount", noiseAmount);

Graphics.Blit (src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}

然后是 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
Shader "Unity Shaders Book/Chapter 15/Fog With Noise" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_FogDensity ("Fog Density", Float) = 1.0
_FogColor ("Fog Color", Color) = (1, 1, 1, 1)
_FogStart ("Fog Start", Float) = 0.0
_FogEnd ("Fog End", Float) = 1.0
_NoiseTex ("Noise Texture", 2D) = "white" {}
_FogXSpeed ("Fog Horizontal Speed", Float) = 0.1
_FogYSpeed ("Fog Vertical Speed", Float) = 0.1
_NoiseAmount ("Noise Amount", Float) = 1
}
SubShader {
CGINCLUDE

#include "UnityCG.cginc"

float4x4 _FrustumCornersRay;

sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
half _FogXSpeed;
half _FogYSpeed;
half _NoiseAmount;

struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
};

v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;
o.uv_depth = v.texcoord;

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif

int index = 0;
if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
index = 0;
} else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
index = 1;
} else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
index = 2;
} else {
index = 3;
}
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
index = 3 - index;
#endif

o.interpolatedRay = _FrustumCornersRay[index];

return o;
}

fixed4 frag(v2f i) : SV_Target {
// 还原像素对应的点在世界空间的位置
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
// 计算采样坐标偏移
float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
// 利用偏移量对噪声纹理采样,结果减去0.5并乘以控制噪声程度的属性得到最终的噪声值
float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount;
// 利用噪声计算雾效混合系数
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));
// 利用雾效混合系数混合雾效颜色和原颜色
fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

return finalColor;
}

ENDCG

Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

ENDCG
}
}
FallBack Off
}

渲染效果如下:

fog

4 噪声纹理从何而来

噪声纹理一般属于程序纹理,它们都是由计算机利用某些算法生成的。Perlin 噪声Worley 噪声是两种最常使用的噪声类型,上面的全局雾效使用的噪声纹理就是由 Perlin 噪声生成而来。 Perlin 噪声可以用于生成更自然的噪声纹理,而 Worley 噪声则通常用于模拟诸如石头、水、纸张等多孔噪声。更多关于噪声纹理的内容可以查看 Understanding Perlin Noise 以及其他博客。

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。