光照

基础光照

漫反射光照模型(兰伯特模型)

1. 逐顶点漫反射光照

在背光面和向光面交界有一些锯齿,背光面明暗一样。

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
Shader "Unlit/myDiffusePixelLevelMat"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
//指明该Pass的光照模式
Tags { "LightMode" = "ForwardBase" }

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

fixed4 _Diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};


//逐顶点的漫反射光照,漫反射部分的计算都在顶点着色器中进行
v2f vert(a2v v)
{
v2f o;
//把顶点位置从模型空间转换到裁剪空间
//原写法:o.pos = mul(UNITY_MATRIX_MVP,v.vertex);新版本所有跟UNITY_MATRIX_MVP运算的矩阵或者向量的mul方法,会被自动转成UnityObjectToClipPos方法
o.pos = UnityObjectToClipPos(v.vertex);

//得到环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

//将法线转换到世界空间
//使用顶点变换矩阵的逆转置矩阵对法线进行相同变换,unity_WorldToObject即为模型矩阵的逆矩阵,同时改变mul参数位置,左乘省去转置步骤。
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));

//得到世界坐标空间的光源方向(单光源且平行光,复杂光不能使用此方法)
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

//法线和光源方向点积要求同一空间
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//saturate(x)函数起到和max(0,n*l)相同的效果,作用是把x截取到[0,1]范围
o.color = ambient + diffuse;

return o;
}

fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}

ENDCG
}
}
Fallback "Diffuse"
}

2. 逐像素漫反射光照

可以得到更加平滑的光照效果,背光面明暗一样。

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
Shader "Unlit/myDiffusePixelLevelMat"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }

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

fixed4 _Diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
};
//逐像素的漫反射光照,漫反射部分的计算都在片元着色器中进行
//顶点着色器只需要把世界空间下的法线传递给片元着色器即可
v2f vert(a2v v)
{
v2f o;
//把顶点位置从模型空间转换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);

//将法线部分转换到世界空间
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
//得到环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

//得到世界坐标空间下的法线
fixed3 worldNormal = normalize(i.worldNormal);
//得到世界坐标空间的光源方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射部分
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));

fixed3 color = ambient + diffuse;

return fixed4(color, 1.0);
}

ENDCG
}
}
Fallback "Diffuse"
}

3. 半兰伯特模型(Half Lambert)

image-20220701175540145

背光面明暗有变化

image-20220702150528648

image-20220702150543738

1
2
3
4
修改逐像素漫反射光照的一行代码即可:
//计算漫反射部分
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

高光反射光照模型

image-20220702150311075

image-20220702150324599

reflect()函数计算反射方向:

image-20220702152627034

image-20220702152639284

image-20220702120229635

逐顶点光照:高光部分不平滑

逐像素光照:高光部分平滑

BlinnPhong光照:高光反射部分更大更亮,在实际渲染中常采用此模型。

1. 逐顶点光照

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
Shader "Unlit/MySpecularVertexLevelMat"
{
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Specular("Specluar",Color) = (1,1,1,1) //控制高光反射属性
_Gloss("Gloss",Range(8.0,256)) = 20 //控制高光区域大小
}
SubShader
{

Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

struct a2v
{
float4 vertex : POSITION;
float4 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};

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

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));

fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));

//入射光线方向关于表面法线的反射方向
//由于Cg的reflect函数的入射方向要求是由光源指向交点处,因此我们需要对worldLightDir(方向为点指向光源)【取反】后再传给reflect函数
fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));

//得到世界空间下的视角方向
//_WorldSpaceCameraPos.xyz得到世界空间中的摄像机位置,再把顶点位置从模型空间变换到世界空间下,向量相减得到视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

//计算高光反射部分
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);
//pow () 函数用来求 x 的 y 次幂(次方)
o.color = ambient + diffuse + specular;

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
return fixed4(i.color,1.0);
}
ENDCG
}
}
Fallback "Specular"
}

2. 逐像素光照

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
Shader "Unlit/MySpecularPixelLevelMat"
{
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Specular("Specluar",Color) = (1,1,1,1) //控制高光反射属性
_Gloss("Gloss",Range(8.0,256)) = 20 //控制高光区域大小
}
SubShader
{

Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};

//顶点着色器计算世界空间下的法线方向和顶点坐标,将他们传递给片元着色器
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//把顶点位置从模型空间变换到世界空间,用于片元着色器计算视角方向
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 worldNormal = normalize(i.worldNormal);

Dir = normalize(_WorldSpaceLightPos0.xyz);

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));

fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));

fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);

return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
Fallback "Specular"
}

3. BlinnPhong模型

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
Shader "Unlit/NewUnlitShader"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1,1,1,1)
_SpecularColor ("Specular Color", Color) = (1,1,1,1)
_SpecularPower ("Specular Power", Range(0, 256)) = 32
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100

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

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include <UnityLightingCommon.cginc>
#include "UnityCG.cginc"

float4 _DiffuseColor;
float4 _SpecularColor;
float _SpecularPower;


struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};


v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}

fixed4 frag(v2f o) : SV_Target
{
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 worldNormal = normalize(o.worldNormal);
float3 LightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - o.worldPos);
float3 halfDir = normalize(LightDir + viewDir);

float3 diffuse = _DiffuseColor.rgb * _LightColor0.rgb * (0.5 * dot(worldNormal, LightDir) + 0.5);
float3 specular = _SpecularColor.rgb * _LightColor0.rgb * pow(
saturate(dot(worldNormal, halfDir)), _SpecularPower);

return float4(diffuse + ambient + specular, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}

复杂光照

1. unity渲染路径

image-20220707185938110

常用前向渲染路径

image-20220707190035317

image-20220707190046705

image-20220707190056605

2. 前向渲染处理不同光照类型

image-20220707190732227

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
165
Shader "Unlit/MyForwardRenderingMat"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_SpecularPower ("SpecularPower", Range(8.0, 256)) = 20
}
SubShader
{
//Base Pass
Pass
{
Tags
{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _SpecularPower;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);

//得到平行光方向
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

//_LightColor0.rgb得到平行光颜色和强度(_LightColor0.rgb是颜色和强度相乘的结果)
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(
saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _SpecularPower);

//平行光可以认为是没有衰减的,这里直接令衰减值为1.0
fixed atten = 1.0;

return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}

//Additional Pass
//相比于BasePass,要去掉环境光、自发光、逐顶点光照、SH光源等,并添加对不同光源类型的支持
Pass
{
Tags
{
"LightMode" = "ForwardAdd" //为每一个逐像素灯光生成一个Pass进行光照计算
}

Blend One One //开启混合模式,任何混合模式都可以

CGPROGRAM
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag

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

fixed4 _Diffuse;
fixed4 _Specular;
float _SpecularPower;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}

fixed4 frag(v2f i) : SV_Target
{

fixed3 worldNormal = normalize(i.worldNormal);

fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(
saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _SpecularPower);

//处理不同光源的衰减
//Unity默认选择使用_LightTexture0纹理作为查找表,以在片元着色器中得到光源的衰减。我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值。
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
// 把点坐标转换到点光源的坐标空间中,unity_WorldToLight由引擎代码计算后传递到shader中,这里包含了对点光源范围的计算,具体可参考Unity引擎源码。经过unity_WorldToLight变换后,在点光源中心处lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
// 使用点到光源中心距离的平方dot(lightCoord, lightCoord)构成二维采样坐标,对衰减纹理_LightTexture0采样。_LightTexture0纹理具体长什么样可以看后面的内容
// UNITY_ATTEN_CHANNEL是衰减值所在的纹理通道,可以在内置的HLSLSupport.cginc文件中查看。一般PC和主机平台的话UNITY_ATTEN_CHANNEL是r通道,移动平台的话是a通道
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
// 把点坐标转换到聚光灯的坐标空间中,unity_WorldToLight由引擎代码计算后传递到shader中,这里面包含了对聚光灯的范围、角度的计算,具体可参考Unity引擎源码。经过unity_WorldToLight变换后,在聚光灯光源中心处或聚光灯范围外的lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
// 与点光源不同,由于聚光灯有更多的角度等要求,因此为了得到衰减值,除了需要对衰减纹理采样外,还需要对聚光灯的范围、张角和方向进行判断
// 此时衰减纹理存储到了_LightTextureB0中,这张纹理和点光源中的_LightTexture0是等价的
// 聚光灯的_LightTexture0存储的不再是基于距离的衰减纹理,而是一张基于张角范围的衰减纹理
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif

return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}

3. 阴影

image-20220707191223777

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
Shader "Unlit/MyShadowMat"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags
{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc" //计算阴影时所用的宏都在这个文件中声明

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;


struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
⭐SHADOW_COORDS(2)
//添加内置宏,声明一个用于对阴影纹理采样的坐标。
//参数是下一个可用的插值寄存器的索引值
//ps:注意这里结尾没有分号
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

⭐TRANSFER_SHADOW(o); //这个宏用于在顶点着色器中计算上一步声明的阴影纹理坐标
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);

fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(
saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _Gloss);

//使用内置宏统一计算衰减值和阴影值
//它会将光照衰减和阴影值相乘后的结果储存早第一个参数atten中
⭐UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

// 计算衰减值和阴影值
// fixed atten = 1.0; //平行光可以认为是没有衰减的,这里直接令衰减值为1.0
// fixed shadow = SHADOW_ATTENUATION(i); //计算阴影值,最后和(diffuse + specular)相乘即可

return fixed4(ambient + (diffuse + specular) * atten , 1.0);
//return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}

4. 标准光照着色器

image-20220707191440870

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
Shader "Unlit/MyShadowMat"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags
{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc" //计算阴影时所用的宏都在这个文件中声明

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;


struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
//添加内置宏,声明一个用于对阴影纹理采样的坐标。
//参数是下一个可用的插值寄存器的索引值
//ps:注意这里结尾没有分号
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

TRANSFER_SHADOW(o); //这个宏用于在顶点着色器中计算上一步声明的阴影纹理坐标
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);

fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(
saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _Gloss);

//使用内置宏统一计算衰减值和阴影值
//它会将光照衰减和阴影值相乘后的结果储存早第一个参数atten中
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

// 计算衰减值和阴影值
// fixed atten = 1.0; //平行光可以认为是没有衰减的,这里直接令衰减值为1.0
// fixed shadow = SHADOW_ATTENUATION(i); //计算阴影值,最后和(diffuse + specular)相乘即可

return fixed4(ambient + (diffuse + specular) * atten , 1.0);
//return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}

纹理

基础纹理

1. 单张纹理

image-20220706173433382

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
Shader "Unlit/MySingleTextureMat"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {} //纹理属性声明方式:2D
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST; //声明纹理的属性
fixed4 _Specular;
float _Gloss;

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

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
//计算纹理坐标
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//或者使用内置函数:
//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

//纹理采样
//使用tex2D(被采样的纹理,纹理坐标)函数,返回纹素值。和颜色属性相乘得到反射率albedo
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

//albedo和环境光照相乘得到环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

//使用albedo计算漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 specular =_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _Gloss);

return fixed4(ambient + diffuse+ specular, 1.0);
}

ENDCG
}
}
Fallback "Specular"
}

2. 凹凸映射

image-20220706173502660

凹凸映射有两种方法:高度映射和法线映射,本章讨论的都是法线映射。

切线空间计算

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
//在切线空间计算光照模型
Shader "Unlit/MyNormalMapTangentSpaceMat"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map",2D) = "bump" {} //bump是内置的法线纹理,当没有提供任何法线纹理时,bump就对应了模型自带的法线信息
_BumpScale ("Bump Scale",Float) = 1.0 //用于控制凹凸程度,为0时表示法线纹理不会对光照产生任何影响
_Specular ("Soecular",Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{

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

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;

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

struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0; //由于使用了两个纹理,因此需要存储两个纹理坐标。为此把uv定义为float4类型。单张纹理中定义为float2。
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//xy分量存储_Maintex的纹理坐标,zw存储_BumpTex的纹理坐标
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//使用内置函数
//o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
//o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

//计算副切线
//副切线由法线和切线叉积得到。再乘以切线的w值,可以确定副切线的方向。
float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;


//模型空间下切线方向,副切线方向,法线方向按行排列可以得到从模型空间到切线空间的变换矩阵rotation
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
//也可以使用内置函数TANGENT_SPACE_ROTATION;该函数生成变换矩阵rotation

//计算切线空间下的光照和视角方向
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);

//纹理采样,得到法线纹理_BumpMap的纹素
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;

//如果该法线纹理类型没有被设置成"Normal Map",则需要手动计算反映射过程,以得到原先的法线方向
//tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

//若设置成"Normal Map",可以使用Unity内置函数UnpackNormal来得到正确的法线方向
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale; //相乘得到xy分量
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tangentNormal, tangentLightDir));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, normalize(tangentLightDir + tangentViewDir))), _Gloss);

return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}

世界空间下计算

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
//在世界空间计算光照模型
Shader "Unlit/MyNormalMapWorldSpaceMat"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map",2D) = "bump" {}
_BumpScale ("Bump Scale",Float) = 1.0
_Specular ("Soecular",Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{

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

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;

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

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

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

o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.vertex);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

//按【列】摆放得到从切线空间转换到世界空间的矩阵
//并且把世界空间下的顶点位置存储在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 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

//得到切线空间下的法向量
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));

//将法线变换到世界空间下
//通过点乘操作,将矩阵的每一行和法线相乘得到
bump = normalize(half3(dot(i.TtoW0.xyz, bump),dot(i.TtoW1.xyz, bump),dot(i.TtoW2.xyz, bump)));

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(bump, lightDir));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump, normalize(lightDir + viewDir))), _Gloss);

return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}

3. 渐变纹理

使用渐变纹理可以控制物体的漫反射光照

image-20220706174001859

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
Shader "Unlit/MyRampTextureMat"
{
Properties
{
_Color ("Color Tint",Color) = (1, 1, 1, 1)
_RampTex ("Ramp Tex", 2D) = "white" {}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20

}
SubShader
{

Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;

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

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

//使用纹理采样漫反射颜色

//使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;

//由于_RampTex是一个一维纹理(在纵轴颜色不变),因此纹理坐标的u和v方向都使用了halfLambert
//然后,把从渐变纹理采样得到的颜色和材质颜色Color相乘,得到最终的漫反射颜色(同albedo)
fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * diffuseColor;

fixed3 diffuse = _LightColor0.rgb * diffuseColor * saturate(dot(worldNormal, worldLightDir));

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 specular =_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, normalize(worldLightDir + viewDir))), _Gloss);

return fixed4(ambient + diffuse+ specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}

4. 遮罩纹理

image-20220704115924826

控制模型表面的各种性质

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
Shader "Unlit/MyMaskTextureMat"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap("Normal Map", 2D) = "bump" {}
_BumpScale("Bump Scale", Float) = 1.0
_SpecularMask ("Specular Mask", 2D) = "white" {} //高光反射遮罩纹理
_SpecularScale("Specular Scale", Float) = 1.0 //控制遮罩影响度
_Specluar ("Specular",Color) = (1, 1, 1, 1)
_Gloss ("Gloss",Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST; //三种纹理共用一个纹理属性变量
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specluar;
float _Gloss;

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

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};

//在切线空间下计算
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}

//使用遮罩纹理的地方时片元着色器,我们使用它来控制模型表面的高光反射强度
fixed4 frag(v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);

fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tangentNormal, tangentViewDir));

//选用r分量计算掩码值,和_SpecularScale相乘来控制高光反射强度
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;

fixed3 specular = _LightColor0.rgb * _Specluar.rgb * pow(saturate(dot(tangentNormal, normalize(tangentLightDir + tangentViewDir))),_Gloss) * specularMask;

return fixed4 (ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}

高级纹理

【脚本】创建用于环境映射的立方体纹理

P212:我们希望根据物体在场景位置的不同,生成他们各自不同的立方体纹理。该代码需要添加菜单栏条目,因此我们需要把它放在Editor文件夹下才能正确执行。

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

public class RenderCubemapWizard : ScriptableWizard {

public Transform renderFromPosition;
public Cubemap cubemap;

void OnWizardUpdate () {
helpString = "Select transform to render from and cubemap to render into";
isValid = (renderFromPosition != null) && (cubemap != null);
}

void OnWizardCreate () {
// create temporary camera for rendering
GameObject go = new GameObject( "CubemapCamera");
go.AddComponent<Camera>();
// place it on the object
go.transform.position = renderFromPosition.position;
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);

// destroy temporary camera
DestroyImmediate( go );
}

[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap () {
ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
"Render cubemap", "Render!");
}
}

立方体纹理

image-20220706170725606

1. 反射

通过入射光线的方向和法线方向来计算反射方向(使用reflect函数),再利用反射方向对立方体纹理采样。

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
Shader "Unlit/MyReflection"
{
Properties
{
_Color("Color Tint", Color) = (1,1,1,1)
_ReflectColor ("Reflection Color", Color) = (1,1,1,1) //用于控制反射颜色
_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //控制材质反射程度
_Cubemap ("Reflection Cubemap", Cube) = "_Skybos" {} //模拟反射的环境映射纹理
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass
{
Tags {"LightMode" = "ForwardBase" }

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

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

fixed4 _Color;
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldViewDir : TEXCOORD2;
float3 worldRefl : TEXCOORD3; //世界空间下的反射方向
SHADOW_COORDS(4)
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.worldViewDir = UnityObjectToViewPos(o.worldPos);


//计算世界空间下的反射方向,通过reflect()函数实现
//由于Cg的reflect函数的入射方向要求是由光源指向交点处,因此我们需要对worldLightDir(方向为点指向光源)【取反】后再传给reflect函数
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);

TRANSFER_SHADOW(o);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal, worldLightDir));

//利用反射方向对立方体纹理取样
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

//利用_ReflectAmount混合漫反射颜色和反射颜色,并和环境光照相加后返回
fixed3 color = ambient + lerp(diffuse, reflection ,_ReflectAmount ) * atten;

return fixed4(color, 1.0);
}

ENDCG
}
}
FallBack "Specular"
}

2. 折射

image-20220707191639020

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
Shader "Unlit/MyRefraction"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1) //用于控折射颜色
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1 //控制材质反折射程度
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5 //得到不同介质的透射比(入射光线所在介质的折射率和折射光线所在介质的折射率之比)
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {} //模拟折射的环境映射纹理
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

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

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

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

fixed4 _Color;
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefr : TEXCOORD3; //世界空间下的折射方向
SHADOW_COORDS(4)
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldViewDir= UnityWorldSpaceViewDir(o.worldPos);

//使用refract函数计算世界空间下的折射方向
//第一个参数是入射方向,第二个参数是表面法线,前两个参数都要归一化。第三个参数是透射比
//函数的入射方向是由光源指向交点处,因此我们需要对worldLightDir(方向为点指向光源)【取反】后再传给refract函数
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);

TRANSFER_SHADOW(o);
return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));

//利用折射方向对立方体纹理取样
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

//利用_RefractAmount混合漫反射颜色和反射颜色,并和环境光照相加后返回
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;

return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}

3.菲涅尔反射

image-20220707191714775

使用Schlick菲涅尔近似等式计算

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
Shader "Unlit/MyFresnelMat"
{
Properties
{
_Color("Color Tint", Color) = (1,1,1,1)
_FresnelScale ("Fresnel Scale" , Range(0, 1)) = 0.5 //作为Schlick菲涅尔近似等式的反射系数,用于调整菲涅尔反射
_Cubemap ("Refflection Cubemap", Cube) = "_Skybox" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }

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

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

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

fixed4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};

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

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);

TRANSFER_SHADOW(o);

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;

//使用Schlick菲涅尔近似等式来计算fresnel变量,并使用它来混合漫反射颜色和反射颜色
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;

return fixed4(color, 1.0);
}

ENDCG
}
}
FallBack "Reflective/VertexLit"
}

渲染纹理

1. 额外摄像机抓取屏幕图像实现镜子效果

image-20220708175328503

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
Shader "Unlit/MyMirrorMat"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Geometry" }


Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;

struct a2v
{
float4 vertex : POSITION;
float3 texcoord : TEXCOORD0;
};

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

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

o.uv = v.texcoord;

//镜像需要反转x分量的纹理坐标,因为镜子里现实的图像都是左右相反的
o.uv.x = 1 - o.uv.x;

return o;
}

//对渲染纹理进行采样和输出
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
FallBack Off
}

2. GrabPass抓取屏幕图像实现玻璃效果

image-20220708175525493

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
Shader "Unlit/MyGlassRefractionMat"
{
Properties
{
_MainTex ("Mai", 2D) = "white" {} //玻璃的纹理材质,默认白色纹理
_BumpMap("Normal Map", 2D) = "bump" {} //玻璃的法线纹理
_CubeMap("EnvironmentCubemap",Cube) = "_Skybox" {} //模拟反射的环境纹理
_Distotion ("Distortion", Range(0,100)) = 10 //控制模拟折射时的图像的扭曲程度
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0 //控制折射程度,值为0时只有反射效果,值为1时只有折射效果
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Opaque" }

//通过GrabPass定义一个抓取当前屏幕图像的Pass,字符串名称决定了抓取得到的屏幕图像存入哪个纹理中
GrabPass { "_RefractionTex"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _CubeMap;
float _Distotion;
fixed _RefractAmount;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize; //得到纹理的纹素大小,在对屏幕图像的采样坐标进行偏移时使用该变量

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 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, _BumpMap);

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 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

//得到切线空间下的法线方向
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));

//计算切线空间的偏移量,对屏幕图像的采样坐标进行偏移,模拟折射效果
//_Distotion值越大,偏移量越大,玻璃背后的物体看起来变形程度越大
float2 offset = bump.xy * _Distotion * _RefractionTex_TexelSize.xy;
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)));
//计算世界空间下的反射方向
fixed3 reflDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCol = texCUBE(_CubeMap, reflDir).rgb * texColor.rgb; //得到反射颜色

//对反射和折射颜色进行混合,作为最终的输出颜色
fixed3 finalColor = lerp(reflCol, refrCol, _RefractAmount);
//fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;

return fixed4(finalColor,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}

程序纹理

【脚本】简单程序纹理

image-20220708175742129

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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode] //使脚本的所有实例能在编辑器模式下执行。
public class MyProceduralTextureGeneration : MonoBehaviour
{
//声明一个材质,该材质将使用该脚本中生成的程序纹理
public Material material = null;

//下面代码中,我们声明了四个纹理属性:纹理大小(数值通常为2的整数幂),纹理背景颜色,圆点颜色,模糊因子blurFactor(用来模糊圆形边界)
#region Material properties
//为了在面板上修改属性时仍可以执行set函数,使用开源插件SetProperty。
//这使得当我们修改了材质属性时,可以执行_UpdateMaterial函数来使用新的属性重新生成程序纹理
[SerializeField, SetProperty("textureWidth")]

private int m_textureWidth = 512;
public int textureWidth
{
get { return m_textureWidth; }
set { m_textureWidth = value; _UpdateMaterial(); }
}

[SerializeField, SetProperty("backgroundColor")]
private Color m_backgroundColor = Color.white;
public Color backgroundColor
{
get { return m_backgroundColor; }
set { m_backgroundColor = value; _UpdateMaterial(); }
}

[SerializeField, SetProperty("circleColor")]
private Color m_circleColor = Color.yellow;
public Color circleColor
{
get { return m_circleColor; }
set { m_circleColor = value; _UpdateMaterial(); }
}

[SerializeField, SetProperty("blurFactor")]
private float m_blurFactor = 2.0f;
public float blurFactor
{
get { return m_blurFactor; }
set { m_blurFactor = value; _UpdateMaterial(); }
}

//保存生成的程序纹理
private Texture2D m_generatedTexture = null;

//首先,我们需要在Start函数中进行相应的检查,以得到需要使用该程序的纹理
private void Start()
{
//如果为空,就尝试从使用该脚本所在的物体上得到相应的材质
if (material == null)
{
Renderer renderer = gameObject.GetComponent<Renderer>();
if (renderer == null)
{
Debug.LogWarning("Cannot find a renderer");
return;
}

material = renderer.sharedMaterial;
}
_UpdateMaterial();
}
private void _UpdateMaterial()
{
if(material != null)
{
m_generatedTexture = _GenerateProceduralTexture();
material.SetTexture("_MainTex", m_generatedTexture);
}
}
private Texture2D _GenerateProceduralTexture()
{
Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);

//定义圆与圆之间的间距
float circleInterval = textureWidth / 4.0f;
//定义圆的半径
float radius = textureWidth / 10.0f;
//定义模糊系数
float edgeBlur = 1.0f / blurFactor;

for(int w = 0; w < textureWidth; w++)
{
for (int h = 0; h < textureWidth; h++)
{
//使用背景颜色进行初始化
Color pixel = backgroundColor;

//依次画9个园
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//计算当前所绘制的圆的圆心位置
Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));
//计算当前像素与愿心的距离
float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;
//模糊圆的边界
Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));
//与之前得到的颜色进行混合
pixel = _MixColor(pixel, color, color.a);
}
}
proceduralTexture.SetPixel(w, h, pixel);
}
}

//将像素值写入纹理
proceduralTexture.Apply();

return proceduralTexture;
}
//这里我用了lerp函数的方法进行混合
private Color _MixColor(Color pixel, Color color, float a)
{
return (1 - a) * pixel + a * color;
}
#endregion
}

噪声

消融效果

1

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
Shader "Unlit/MyDissolveMat"
{
Properties
{
_BurnAmount("Burn Amount", Range(0.0,1.0)) = 0.0 //控制消融程度(值为1时,物体完全消融)
_LineWidth("Burn Line Width",Range(0.0,2.0)) = 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(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 * saturate(dot(tangentNormal, tangentLightDir));

//在宽度为_LineWidth的范围内模拟一个烧焦的颜色变化

//使用smoothstep函数来计算混合系数t。
//当t为1时,表明该像素位于消融的边界处。t为0时,表明该像素为正常的模型颜色。中间的插值表示需要模拟一个烧焦效果
fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
burnColor = pow(burnColor,5);

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

return fixed4(finalColor,1);
}
ENDCG
}

// 自定义透明度测试的阴影投射Pass
Pass {
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
{
V2F_SHADOW_CASTER; //声明阴影投射需要定义的变量
float2 uvBurnMap : TEXCOORD1;
};


v2f vert(appdata_base v)
{
v2f o;

TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) //填充V2F_SHADOW_CASTER声明的变量

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) //让Unity完成阴影投射,把结果输出到深度图和阴影映射纹理中
}
ENDCG
}
}
FallBack "Diffuse"
}

水波效果

image-20220714161413612

2

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
Shader "Unlit/MyWaterWaveMat"
{
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"{} //模拟反射的立方体纹理
_WaveXSpeed("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 //控制法线纹理在X/Y方向上的平移速度
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
_Distortion ("Distortion", Range(0, 100)) = 10 //控制模拟折射时的图像的扭曲程度
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
//通过GrabPass定义一个抓取当前屏幕图像的Pass,字符串名称决定了抓取得到的屏幕图像存入哪个纹理中
GrabPass { "_RefractionTex" }

//定义渲染水面所需的Pass
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; //对应了GrabPass指定的纹理名称
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));
//计算法线纹理当前偏移量
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);

//计算切线空间的偏移量,对屏幕图像的采样坐标进行偏移,模拟折射效果
//_Distotion值越大,偏移量越大
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//offset * i.scrPos.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 Off
}

不均匀雾效

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

public class MyFogWithNoise : MyPostEffectsBase
{
public Shader fogShader;
private Material fogMaterial = null;

public Material material
{
get
{
fogMaterial = CheckShaderAndCreatMaterial(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; //噪声纹理

[Range(-0.5f, 0.5f)]
public float fogXSpeed = 0.1f; //噪声纹理在X方向上的平移速度

[Range(-0.5f, 0.5f)]
public float fogYSpeed = 0.1f; //噪声纹理在Y方向上的平移速度

[Range(0.0f, 3.0f)]
public float noiseAmount = 1.0f; //控制噪声程度(为0时表示不应用任何噪声,得到均匀的基于高度的全局雾效)

void OnEnable()
{
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);
}
}
}
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
Shader "Unlit/MyFogWithNoise"
{
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; //Unity传来的深度纹理
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
half _FogXSpeed;
half _FogYSpeed;
half _NoiseAmount;

struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2; //由顶点着色器输出并插值后得到的射线,包括该像素到摄像机的方向和距离信息
};

v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityViewToClipPos(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
//使用索引值获取_FrustumCornersRay对应的行作为该顶点的interpolatedRay值
o.interpolatedRay = _FrustumCornersRay[index];

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
//使用LinearEyeDepth将深度纹理的采样结果转换成视角空间下的线性深度值
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));

//得到世界空间下的位置
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;

//计算噪声纹理的偏移量并对噪声纹理采样得到噪声值
float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
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
Pass {
ZTest Always Cull Off ZWrite Off

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

ENDCG
}
}
FallBack Off
}

透明效果

实现透明效果的两种方法:透明度测试和透明度混合

透明度测试

image-20220704153603251

透明度测试的显示效果比较极端:要么完全透明(看不到),要么完全不透明。而且,透明效果的边缘有锯齿。
image-20220704153317101image-20220704153515750

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
Shader "Unlit/MyAlphaTest"
{
Properties
{
_Color ("Main Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1))= 0.5
//为了控制透明度测试时使用的阈值。_Cutoff参数用于决定我们调用clip进行透明度测试时使用的判断条件,范围是[0,1],这是因为纹理像素的透明度就在此范围内

}
SubShader
{
Tags
{
"Queue"="AlphaTest" //透明度测试使用AlphaTest渲染队列
"IgnoreProjector"="True" //该Shader不会受到投影器的影响
"RenderType"="TransparentCutout" //把该Shader归入到提前定义的组中(TransparentCutout组),以指明该Shader时一个使用了透明度测试的Shader,该标签通常被用于着色器替换功能
}

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

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

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;

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

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed4 texColor = tex2D(_MainTex, i.uv);

//透明度测试
clip(texColor.a - _Cutoff);

fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLightDir));

return fixed4(ambient + diffuse, 1.0);
}

ENDCG
}
}
Fallback "Transparent/Cutout/VertexLit"
}

透明度混合

1. 一般方法

image-20220704153712478

image-20220704153735600

透明度混合可以得到更加柔和的透明效果,但是这种方法仍有弊端:关闭深度写入后,造成错误排序,无法对模型进行像素级别的深度排序。

image-20220704154122183

实现效果:

image-20220704153821708

image-20220704154027806

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
Shader "Unlit/MyAlphaBlendMat"
{
Properties
{
_Color ("Main Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1))= 0.5
}
SubShader
{
Tags
{
"Queue"="Transparent" //透明度混合使用Transparent渲染队列
"IgnoreProjector"="True" //该Shader不会受到投影器的影响
"RenderType"="Transparent" //把该Shader归入到提前定义的组中(Transparent组),以指明该Shader时一个使用了透明度混合的Shader,该标签通常被用于着色器替换功能
}

Pass
{
Tags{"LightMode"="ForwardBase"}
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //Blend设置混合模式

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

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale; //用于在透明纹理的基础上控制整体的透明度

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

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));


fixed4 texColor = tex2D(_MainTex, i.uv);

fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLightDir));

return fixed4(ambient + diffuse, texColor.a * _AlphaScale); //设置了该片元着色器返回值中的透明通道,它是纹理像素的透明通道和材质参数_AlphaScale的乘积
}

ENDCG
}
}
Fallback "Transparent/VertexLit"
}

2. 开启深度写入的半透明效果

使用两个Pass来渲染模型,使用这种方法我们仍可以实现模型与他后面的背景混合的效果,同时模型内部之间不会有任何的半透明效果。

image-20220704154337978

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
Shader "Unlit/MyAlphaBlendZwriteMat"
{
Properties
{
_Color ("Main Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1))= 0.5
}
SubShader
{
Tags
{
"Queue"="Transparent" //透明度混合使用Transparent渲染队列
"IgnoreProjector"="True" //该Shader不会受到投影器的影响
"RenderType"="Transparent" //把该Shader归入到提前定义的组中(Transparent组),以指明该Shader时一个使用了透明度混合的Shader,该标签通常被用于着色器替换功能
}

//使用两个Pass来渲染模型

//第一个Pass开启深度写入,但不输出颜色,目的仅仅是为了把该模型的深度值写入深度缓冲中
pass
{
Zwrite On
ColorMask 0
//ColorMask用于设置颜色通道的写掩码(write mask),设为0,意味着该Pass不写入任何颜色通道
}

//第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染
Pass
{
Tags{"LightMode"="ForwardBase"}
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //Blend设置混合模式

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

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale; //用于在透明纹理的基础上控制整体的透明度

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

struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));


fixed4 texColor = tex2D(_MainTex, i.uv);

fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLightDir));

return fixed4(ambient + diffuse, texColor.a * _AlphaScale); //设置了该片元着色器返回值中的透明通道,它是纹理像素的透明通道和材质参数_AlphaScale的乘积
}

ENDCG
}
}
Fallback "Transparent/VertexLit"
}

双面渲染

通过双面渲染可以看到物体内部和背部的结构。

image-20220704154930176

image-20220704155211450

1. 透明度测试的双面渲染

image-20220704155008714

2. 透明度混合的双面渲染

image-20220704155236569

image-20220704155047959

动画

image-20220710165419946

纹理动画

1. 序列帧动画

1

image-20220710165432957

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
Shader "Unlit/MyImageSequenceAnimationMat"
{
Properties
{
_Color ("Color Tint",Color) = (1,1,1,1)
_MainTex ("Image Sequence", 2D) = "white" {} //包含了所有关键帧图像的纹理
_HorizontalAmount ("Horizontal Amount", Float) = 4 //图像在水平方向包含的关键帧图像个数
_VerticalAmount ("Vertical Amount", Float) = 4 //图像在竖直方向包含的关键帧图像个数
_Speed ("Speed", Range(1, 100)) = 30 //控制序列帧动画的播放速度

}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}

Pass
{
Tags { "LightMode" = "ForwardBase"}
Zwrite Off
Blend SrcAlpha OneMinusSrcAlpha

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

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _HorizontalAmount;
float _VerticalAmount;
float _Speed;

struct a2v {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};

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

v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
//floor函数:向下取整
float time = floor (_Time.y * _Speed); //_Time.y:自该场景加载后所经过的时间
float row = floor (time / _HorizontalAmount); //行索引
float column = time - row * _HorizontalAmount; //余数作为列索引

//half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
//uv.x += column / _HorizontalAmount;
//uv.y -= row / _VerticalAmount;
half2 uv = i.uv + half2(column, -row);
uv.x /= _HorizontalAmount;
uv.y /= _VerticalAmount;

fixed4 c = tex2D(_MainTex, uv);
c.rgb *= _Color;

return c;
}
ENDCG
}
}
Fallback "Transparent/VertexLit"
}

2. 滚动卷轴动画

1

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
Shader "Unlit/MyScrollingBackgroundMat"
{
Properties
{
_MainTex("Base Layer(RGB)",2D) = "white"{} //第一层(较远)的背景纹理
_DetailTex("2nd Layer(RGB)",2D) = "white"{} //第一层(较近)的背景纹理
_ScrollX("Base layer Scroll Speed", Float) = 1.0 //水平滚动速度
_Scroll2X("2nd layer Scroll Speed", Float) = 1.0
_Multiplier("Layer Multiplier", Float) = 1 //控制纹理的整体亮度
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

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

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
sampler2D _DetailTex;
float4 _MainTex_ST;
float4 _DetailTex_ST;
float _ScrollX;
float _Scroll2X;
float _Multiplier;

struct a2v
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

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

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

//利用内置的_Time.y变量在水平方向上对纹理坐标进行偏移,达到滚动效果
//frac函数返回标量或每个矢量中各分量的【小数部分】
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);
fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);

//用第二层纹理的透明通道来混和两张纹理
fixed4 c = lerp(firstLayer,secondLayer, secondLayer.a);
c.rgb *= _Multiplier; //调整背景亮度

return c;
}
ENDCG
}
}
FallBack "VertexLit"
}

顶点动画

1. 河流

1

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
Shader "Unlit/MyWaterMat"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {} //河流纹理
_Color("Color Tint", Color) = (1,1,1,1) //控制整体颜色
_Magnitude("Distortion Magnitude", Float) = 1 //控制水流流动幅度
_Frequency("Distortion Frequency", Float) =1 //控制波动频率
_InvWaveLength ("Distorion Inverse Wave Length ", Float) = 10 //控制波长的倒数(_InvWaveLength越大,波长越小)
_Speed("Speed", Float) = 0.5 //控制河流纹理的移动速度
}
SubShader
{
//批处理会合并所有相关模型,而这些模型各自的模型空间就会被丢失
//我们需要在物体的模型空间下对顶点位置进行偏移,因此,在这里需要取消对该Shader的批处理操作(DisableBatching标签)
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}

Pass
{
Tags { "LightMode"="ForwardBase" }
//关闭深度写入,开启混合模式,关闭剔除功能,这是为了让河流每个面都能显示
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;

struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

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

v2f vert(a2v v)
{
v2f o;
//计算定点位移量,我们只对x方向进行位移
float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);

//_Frequency * _Time.y 控制正弦函数频率
//加上模型空间下的位置分量,让不同位置具有不同的唯一。同时乘以_InvWaveLength来控制波长
//乘_Magnitude控制波动幅度,得到最终的位移
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);

o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed4 c =tex2D(_MainTex, i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}

2. 广告牌

使多边形看起来总是面对着摄像机,应用:渲染烟雾,云朵,闪光效果等

image-20220710171029224

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
Shader "Unlit/MyBillboardMat"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_VerticalBillboarding("Vertical Restrains",Range(0, 1)) =1 //调整固定法线还是固定指向上的方向,即约束垂直方向的程度

}
SubShader
{
//在广告牌技术中,我们需要使用物体的模型空间下的位置来作为锚点进行计算,因此,这里取消批处理
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
Pass
{
Tags { "LightMode"="ForwardBase" }
//关闭深度写入,开启混合模式,关闭剔除功能,这是为了让广告牌每个面都能显示
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _VerticalBillboarding;

struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

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

//所有计算都在模型空间下进行
v2f vert(a2v v)
{
v2f o;
//假设模型空间的中心是固定的,选择模型空间原点最为广告牌锚点
//锚点:在旋转过程中固定不变,以此来确定多边形在空间中的位置
float3 center = float3(0,0,0);
//获取模型空间下的视角位置
float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos,1));

//计算三个正交基矢量:表面法线,指向上的方向,指向右的方向
//1. 根据观察位置和锚点计算目标法线方向,并根据_VerticalBillboarding控制垂直方向上的约束度
float3 normalDir = viewer - center;
normalDir.y = normalDir.y * _VerticalBillboarding;
//当_VerticalBillboarding为1时,法线方向为固定方向。
//当_VerticalBillboarding为0时,意味着向上方向固定为(0,1,0)

//2. 对计算得到的法线方向进行归一化得到单位矢量
normalDir = normalize(normalDir);

//3. 得到粗略的向上方向
//如果normal dir已经朝上,那么up dir就朝前(防止法线方向和向上法向平行)
//abs函数返回绝对值
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);

//4. 根据法线方向和粗略的向上方向得到向右方向,并归一化
float3 rightDir = normalize(cross(upDir,normalDir));
//5. 得到最后的向上方向
upDir = normalize(cross(normalDir, rightDir));

//根据原始位置相对于锚点的偏移量以及三个正交基矢量,计算新的顶点位置
float3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;

//将模型空间的顶点变换到裁剪空间中
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _Color.rgb;

return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}

3. 顶点动画添加阴影

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
Shader "Unlit/MyVertexAnimationWithShadowMat"
{
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_Magnitude ("Distortion Magnitude", Float) = 1
_Frequency ("Distortion Frequency", Float) = 1
_InvWaveLength ("Distortion Inverse Wave Length", Float) = 10
_Speed ("Speed", Float) = 0.5
}
SubShader {
// Need to disable batching because of the vertex animation
Tags {"DisableBatching"="True"}

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

Cull Off

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;

struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

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

v2f vert(a2v v) {
v2f o;

float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);

o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _Color.rgb;

return c;
}

ENDCG
}

// Pass to render object as a shadow caster
Pass {
Tags { "LightMode" = "ShadowCaster" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#pragma multi_compile_shadowcaster

#include "UnityCG.cginc"

float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;

struct v2f {
V2F_SHADOW_CASTER;
};

v2f vert(appdata_base v) {
v2f o;

float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
//直接把偏移值加到顶点位置变量中
v.vertex = v.vertex + offset;

TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

return o;
}

fixed4 frag(v2f i) : SV_Target {
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "VertexLit"
}

屏幕后处理效果

【脚本】父类

image-20220711161127951

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
//用于屏幕后处理效果的基类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))] //所有屏幕后处理效果都需要绑定在某个摄像机上

public class MyPostEffectsBase : MonoBehaviour
{
//提前检查各种资源和条件是否满足
protected void CheckResources()
{
bool isSupported = CheckSupport();
if(isSupported == false)
{
NotSupported();
}
}

private bool CheckSupport()
{
if(SystemInfo.supportsImageEffects == false||SystemInfo.supportsRenderTextures == false)
{
Debug.LogWarning("This platform does not support image effects or render textures.");
//这个平台不支持图像效果或渲染纹理。
return false;
}
return true;
}
//若平台不支持此效果
private void NotSupported()
{
enabled = false;
}

protected void Start()
{
CheckResources();
}

//每个屏幕后处理效果通常都需要指定一个Shader来创建一个用于处理渲染纹理的材质
protected Material CheckShaderAndCreatMaterial(Shader shader, Material material)
{
if(shader == null)
{
return null;
}

if(shader.isSupported && material && material.shader == shader)
{
//检查通过后返回一个使用了该shader的材质
return material;
}

if(!shader.isSupported)
{
return null;
}
else
{
material = new Material(shader);
material.hideFlags = HideFlags.DontSave;
if (material)
return material;
else
return null;
}
}
}

调整屏幕亮度,饱和度和对比度

image-20220711161225906

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
//调整屏幕亮度,饱和度和对比度
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//继承基类
public class MyBrightnessSaturationAndContrast : MyPostEffectsBase
{
public Shader briSatuConShader;
private Material briSatuConMaterial;

//声明该效果需要的Shader,并据此创建相应的材质
public Material material
{
get
{
briSatuConMaterial = CheckShaderAndCreatMaterial(briSatuConShader, briSatuConMaterial);
return briSatuConMaterial;
}
}

[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;

private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if(material != null)
{
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
//如果材质可用,就先把参数传递给材质,再显示到屏幕上
Graphics.Blit(src, dest, material);
}
else
{
//如果材质不可用,不做任何处理,直接显示在屏幕上
Graphics.Blit(src, dest);
}
}
}
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
Shader "Unlit/MyBrightnessSaturationAndContrast"
{
Properties
{
_MainTex ("Base(RGB)", 2D) = "white" {}
_Brightness("Brightness",Float) = 1
_Saturation("Saturation",Float) = 1
_Contrast("Contrast",Float) = 1

}
SubShader
{
Tags { "RenderType"="Opaque" }

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;
};

//使用Unity内置的appdata_img结构体作为顶点着色器的输入,它只包含图像处理时必须的顶点坐标和纹理坐标等变量。
v2f vert (appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed4 renderTex = tex2D(_MainTex, i.uv);

//调整亮度
fixed3 finalColor = renderTex.rgb * _Brightness;

//调整饱和度
//luminance是亮度值,同过对每个颜色分量乘以一个特定的系数再相加得到。并使用该亮度值创建了一个饱和度为0的颜色值
fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
//使用_Saturation和上一步得到的颜色之间进行插值,得到希望的饱和度颜色
finalColor = lerp(luminanceColor, finalColor, _Saturation);

//调整对比度
//首先创建一个对比度为0的颜色值(各分量均为0.5)
fixed3 avgColor = fixed3(0.5,0.5,0.5);
//使用_Contrast和上一步得到的颜色之间进行插值,得到希望的对比度颜色
finalColor = lerp(avgColor, finalColor, _Contrast);

return fixed4(finalColor, renderTex.a);

}
ENDCG
}
}
Fallback Off
}

边缘检测

image-20220711161535317

image-20220711161519208

image-20220711161725422

1. 颜色纹理(Soble算子)

image-20220711161415453

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

public class MyEdgeDetection : MyPostEffectsBase
{
public Shader edgeDetectShader;
private Material edgeDetectMaterial = null;
public Material material
{
get
{
edgeDetectMaterial = CheckShaderAndCreatMaterial(edgeDetectShader, edgeDetectMaterial);
return edgeDetectMaterial;
}
}

//用于调整边缘线强度,描边颜色和背景颜色的参数
[Range(0.0f, 1.0f)]
public float edgesOnly = 0.0f;
//当edgesOnly为0时,边缘将会叠加在原渲染图像上;当edgesOnly为1时,则只显示边缘,不显示原渲染图象。
public Color edgeColor = Color.white;
public Color backgroundColor = Color.white;

private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
material.SetFloat("_EdgeOnly", edgesOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);

Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
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
Shader "Unlit/MyEdgeDetection"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_EdgeOnly ("Edge Only",Float) = 1.0
_EdgeColor ("Edge Color", Color) = (0,0,0,1)
_BackgroundColor("Background Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Ztest Always
Cull Off
ZWrite Off

CGPROGRAM
#pragma vertex vert
#pragma fragment fragSobel
#include "UnityCG.cginc"

sampler2D _MainTex;
half4 _MainTex_TexelSize; //xxx_TexelSize是Unity为我们提供的访问xxx纹理对应的每个纹素的大小。由于卷积需要对相邻区域内的纹理进行采样,因此我们需要利用_MainTex_TexelSize计算各个相邻区域的纹理坐标。
fixed _EdgeOnly; //边缘线强度
fixed4 _EdgeColor;
fixed4 _BackgroundColor;

struct v2f
{
float4 pos : SV_POSITION;
half2 uv[9] : TEXCOORD0; //定义一个维数为9的纹理数组,对应了使用Sobel算子采样时需要的9个邻域纹理坐标
};

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

o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

return o;
}

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

//利用Sobel算子对原图进行边缘检测
half Sobel(v2f i)
{
//定义水平方向和竖直方向使用的卷积核
const half Gx[9] =
{
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
};

const half Gy[9] =
{
-1, -2, -1,
0, 0, 0,
1, 2, 1
};

half texColor;
half edgeX = 0;
half edgeY = 0;
//依次对9个像素进行采样,并计算他们的亮度值,再与卷积核对应的权重相乘后叠加到各自的梯度值上
for(int it = 0; it < 9; it++)
{
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}

half edge = 1 - abs(edgeX) - abs(edgeY);

return edge;
}

fixed4 fragSobel(v2f i) : SV_TARGET
{
//调用Sobel函数计算当前像素的梯度值edge,edge值越小,表明该位置越可能是一个边缘点
half edge = Sobel(i);

//利用梯度值分别计算背景为原图和纯色下的颜色值
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);

//插值得到最终的像素值
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
ENDCG
}
}
FallBack Off
}

2. 深度+法线纹理(Robert算子)

image-20220713111746238

image-20220713111504974

image-20220713111424379

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

public class MyEdgeDetectNormalAndDepth : MyPostEffectsBase
{
public Shader edgeDetectShader;
private Material edgeDetectMaterial = null;

public Material material
{
get
{
edgeDetectMaterial = CheckShaderAndCreatMaterial(edgeDetectShader, edgeDetectMaterial);
return edgeDetectMaterial;
}
}

[Range(0.0f, 1.0f)]
public float edgesOnly = 0.0f; //边缘线强度

public Color edgeColor = Color.black; //边缘线颜色

public Color backgroundColor = Color.white;

public float sampleDistance = 1.0f; //控制采样距离(越大,边缘线越宽)

//控制边缘检测灵敏度,当邻域的深度值和法线只相差x时就判定为边界
public float sensitivityDepth = 1.0f;
public float sensitivityNormals = 1.0f;

//设置摄像机状态,获取摄像机的深度+法线纹理
void OnEnable()
{
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
}

//只对不透明物体进行描边
[ImageEffectOpaque]

//只对不透明物体进行描边
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if(material != null)
{
material.SetFloat("_EdgeOnly", edgesOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
material.SetFloat("_SampleDistance", sampleDistance);
material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));

Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
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
Shader "Unlit/MyEdgeDetectNormalAndDepth"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_EdgeOnly ("Edge Only", Float) = 1.0
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
_SampleDistance ("Sample Distance", Float) = 1.0
_Sensitivity ("Sensitivity", Vector) = (1, 1, 1, 1) //xy分量分别对应法线和深度的灵敏度,zw分量无实际用途
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"

sampler2D _MainTex;
half4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
float _SampleDistance;
half4 _Sensitivity;

sampler2D _CameraDepthNormalsTexture; //深度+法线纹理

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

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

half2 uv = v.texcoord;
o.uv[0] = uv;

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

o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance;
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance;
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance;
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance;

return o;
}

//
half CheckSame(half4 center, half4 sample)
{
half2 centerNormal = center.xy;
//DecodeFloatRG解码之前编码的RG浮点数。
float centerDepth = DecodeFloatRG(center.zw);
half2 sampleNormal = sample.xy;
float sampleDepth = DecodeFloatRG(sample.zw);

// difference in normals
//不需要解码得到真正的法线值,直接使用xy分量
half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
//小于阈值返回1,说明差异不明显,不存在边界,否则返回0
int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;

// difference in depth
float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.xy;
int isSameDepth = diffDepth < 0.1 * centerDepth;

//返回1说明不存在边界,返回0说明存在边界
return isSameNormal * isSameDepth ? 1.0 : 0.0;
}

fixed4 fragRobertsCrossDepthAndNormal(v2f i) : SV_TARGET
{
//对深度+法线纹理进行采样
half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);

//调用CheckSame函数分别计算对角线上两个纹理值的差值
half edge = 1.0;
edge *= CheckSame(sample1, sample2);
edge *= CheckSame(sample3, sample4);

//利用梯度值分别计算背景为原图和纯色下的颜色值
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);

//插值得到最终的像素值
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
ENDCG

Pass
{
ZTest Always Cull Off ZWrite Off

CGPROGRAM

#pragma vertex vert
#pragma fragment fragRobertsCrossDepthAndNormal

ENDCG
}
}
FallBack Off
}

高斯模糊

image-20220711161801634

image-20220711161615368

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

public class MyGaussianBlur : MyPostEffectsBase
{
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;

public Material material
{
get
{
gaussianBlurMaterial = CheckShaderAndCreatMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}

//高斯模糊迭代次数(次数越多越模糊)
[Range(0, 4)]
public int iterations = 3;

//模糊范围(值越大越模糊)
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;

//缩放系数
[Range(1, 8)]
public int downSample = 2;
/// 1st edition: just apply blur
/*private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
int rtW = src.width;
int rtH = src.height;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);

//Render the vertical pass
Graphics.Blit(src, buffer, material, 0);
//Render the horizontal pass
Graphics.Blit(buffer, dest, material, 1);

RenderTexture.ReleaseTemporary(buffer);
}
else
{
Graphics.Blit(src, dest);
}
}*/

//2st: 利用缩放对图像进行降采样,从而减少需要处理的像素个数,提高性能
/*private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer.filterMode = FilterMode.Bilinear; //滤波模式设置为双线性

//Render the vertical pass
Graphics.Blit(src, buffer, material, 0);
//Render the horizontal pass
Graphics.Blit(buffer, dest, material, 1);

RenderTexture.ReleaseTemporary(buffer);
}
else
{
Graphics.Blit(src, dest);
}
}*/

//3st:增加高斯模糊的迭代次数
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
int rtW = src.width / downSample;
int rtH = src.height / downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;

Graphics.Blit(src, buffer0);

for (int i = 0; i < iterations; i++)
{
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

//Render the vertical pass
Graphics.Blit(buffer0, buffer1, material, 0);

RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

//Render the horizontal pass
Graphics.Blit(buffer0, buffer1, material, 1);

RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Graphics.Blit(buffer0, dest);
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(src, dest);
}
}
}
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
Shader "Unlit/MyGaussianBlur"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_BlurSize("Blur Size", Float) = 1.0
}
SubShader
{
//使用CGINCLUDE可以避免编写两个完全一样的frag函数
//高斯模糊的两个Pass的片元着色器代码完全相同
CGINCLUDE

#include "UnityCG.cginc"

sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize; //控制采样距离

struct v2f
{
float4 pos : 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;
}

//两个Pass共用的片元着色器
fixed4 fragBlur(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*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}

return fixed4(sum, 1.0);
}

ENDCG

//定义两个Pass
Ztest Always
Cull Off
Zwrite Off

Pass
{
NAME "GAUSSIAN_BLUR_VERTICAL"

CGPROGRAM

#pragma vertex vertBlurVertical
#pragma fragment fragBlur

ENDCG
}

Pass
{
NAME "GAUSSIAN_BLUR_HORIZONTAL"

CGPROGRAM

#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur

ENDCG
}
}
FallBack "Diffuse"
}

Bloom效果

image-20220711161920077

image-20220711161909283

image-20220711161845995

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

public class MyBloom : MyPostEffectsBase
{
public Shader bloomShader;
private Material bloomMaterial = null;
public Material material {
get {
bloomMaterial = CheckShaderAndCreatMaterial(bloomShader, bloomMaterial);
return bloomMaterial;
}
}

// Blur iterations - larger number means more blur.
[Range(0, 4)]
public int iterations = 3;

// Blur spread for each iteration - larger value means more blur
[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;
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_LuminanceThreshold", luminanceThreshold);

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

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

Graphics.Blit(src, buffer0, material, 0);

for (int i = 0; i < iterations; i++) {
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

// Render the vertical pass
Graphics.Blit(buffer0, buffer1, material, 1);

RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

// Render the horizontal pass
Graphics.Blit(buffer0, buffer1, material, 2);

RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}

material.SetTexture ("_Bloom", buffer0);
Graphics.Blit (src, dest, material, 3);

RenderTexture.ReleaseTemporary(buffer0);
} else {
Graphics.Blit(src, dest);
}
}
}
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
Shader "Unlit/MyBloom"
{
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;
}

fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
//得到提取后的较量区域
fixed4 fragExtractBright(v2f i) : SV_TARGET
{
fixed4 c = tex2D(_MainTex, i.uv);
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; //xy分量对应_MainTex(原图像纹理坐标)
o.uv.zw = v.texcoord; //xy分量对应_Bloom(模糊后较量区域纹理坐标)

//我们需要对_Bloom坐标进行平台差异化处理 P116
#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

//定义Bloom效果需要的4个Pass
ZTest Always Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment fragExtractBright

ENDCG
}

UsePass "Unlit/MyGaussianBlur/GAUSSIAN_BLUR_VERTICAL"

UsePass "Unlit/MyGaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"

Pass {
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom

ENDCG
}
}
FallBack Off
}

运动模糊

1. 累积缓存(颜色纹理)

保存之前的渲染结果,不断把当前的渲染图像叠加到之前的渲染图象中,从而产生一种运动轨迹的视觉效果。

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

public class MyMotionBlur : MyPostEffectsBase
{
public Shader motionBlurShader;
private Material motionBlurMaterial = null;

public Material material
{
get
{
motionBlurMaterial = CheckShaderAndCreatMaterial(motionBlurShader, motionBlurMaterial);
return motionBlurMaterial;
}
}

//定义运动模糊在混合图像时使用的模糊参数
//blurAmount值越大,运动拖尾效果越明显
[Range(0.0f, 0.9f)]
public float blurAmount = 0.5f;

//用于保存之前图像叠加的结果
private RenderTexture accumulationTexture;

//在脚本不运行时,即调用OnDisabel时,立即销毁accumlationTexture,我们希望在下一次开始应用运动模式时重新叠加图像。
void OnDisable()
{
DestroyImmediate(accumulationTexture);
}

void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
if (accumulationTexture == null || accumulationTexture.width != src.width ||
accumulationTexture.height != src.height)
{
DestroyImmediate(accumulationTexture);
accumulationTexture = new RenderTexture(src.width, src.height, 0);
accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
Graphics.Blit(src, accumulationTexture);
}

accumulationTexture.MarkRestoreExpected();

material.SetFloat("_BlurAmount", 1.0f - blurAmount);
Graphics.Blit(src, accumulationTexture, material);
Graphics.Blit(accumulationTexture, dest);
}
else
{
Graphics.Blit(src,dest);
}
}
}

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
Shader "Unlit/MyMotionBlur"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_BlurAmount("Blur Amount",Float) = 1.0
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"

sampler2D _MainTex;
fixed _BlurAmount; //混合系数

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

v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
};
//定义两个片元着色器
//用于更新渲染纹理的RGB通道部分
fixed4 fragRGB(v2f i) : SV_Target
{
return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
}

//用于更新渲染纹理的A通道部分
half4 fragA (v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG

//定义两个Pass,分开原因:在更新RGB时我们需要设置它的A通道来混和图像,但又不希望A通道写入渲染纹理中
//用于更新渲染纹理的RGB通道
ZTest Always
Cull Off
ZWrite Off

Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
CGPROGRAM

#pragma vertex vert
#pragma fragment fragRGB

ENDCG
}

//用于更新A通道
Pass {
Blend One Zero
ColorMask A

CGPROGRAM

#pragma vertex vert
#pragma fragment fragA

ENDCG
}
}
FallBack Off
}

2. 速度缓存(深度纹理)

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

public class MyMotionBlurWithDepthTexture : MyPostEffectsBase
{
public Shader motionBlurShader;
private Material motionBlurMaterial = null;

public Material material
{
get
{
motionBlurMaterial = CheckShaderAndCreatMaterial(motionBlurShader, motionBlurMaterial);
return motionBlurMaterial;
}
}


//本节需要得到摄像机的视角和投影矩阵,定义一个Camera类型的变量,以获取该脚本所在的摄像机组件
private Camera myCamera;
public Camera camera
{
get
{
if(myCamera == null)
{
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}

//定义运动模糊时模糊图像使用的大小
[Range(0.0f, 1.0f)]
public float blurSize = 0.5f;
//保存上一帧摄像机的视角*投影矩阵
private Matrix4x4 previousViewProjectionMatrix;

//获取摄像机的深度纹理,我们在OnEnable函数中设置摄像机的状态:
void OnEnable()
{
camera.depthTextureMode |= DepthTextureMode.Depth;

previousViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
}

void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if(material != null)
{
material.SetFloat("_BlurSize", blurSize);

material.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix);

//得到当前帧的视角*投影矩阵的逆矩阵
Matrix4x4 currentViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse;

material.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjectionInverseMatrix);

//存放取逆前的结果,以便在下一帧时传递给材质的_PreviousViewProjectionMatrix属性
previousViewProjectionMatrix = currentViewProjectionMatrix;

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

}

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
Shader "Unlit/MyMotionBlurWithDepthTexture"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurSize("Blur Size", Float) = 1.0 //模糊图像时使用的参数,控制采样距离
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"

sampler2D _MainTex;
half4 _MainTex_TexelSize; //主纹理的纹素大小,我们用其对深度纹理的采样坐标进行平台化差异处理
sampler2D _CameraDepthTexture; //Unity传递的深度纹理
float4x4 _CurrentViewProjectionInverseMatrix; //脚本传来的矩阵
float4x4 _PreviousViewProjectionMatrix;
half _BlurSize;

struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1; //用于对深度纹理采样
};

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

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
//使用SAMPLE_DEPTH_TEXTURE宏对深度纹理进行采样得到深度值d(与tex2D不同的是这个宏可以处理平台差异问题)
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);

//构建像素的NDC坐标H,需要将深度值d重新映射回NDC,使用原映射的反函数:d*2-1
float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);

//得到世界空间下的坐标
float4 D = mul(_CurrentViewProjectionInverseMatrix, H);
float4 worldPos = D/D.w;

//当前视角位置
float4 currentPos = H;

//得到前一帧在NDC下的坐标
float4 previousPos = mul(_PreviousViewProjectionMatrix, worldPos);
// Convert to nonhomogeneous points [-1,1] by dividing by w.
previousPos /= previousPos.w;

//计算前一帧和当前帧在屏幕空间下的位置差,得到该像素的速度velocity
float2 velocity = (currentPos.xy - previousPos.xy)/2.0f;


/* //平均分的颜色权重分配
float2 uv = i.uv;
float4 c = tex2D(_MainTex, uv);
uv += velocity * _BlurSize;
for (int it = 1; it < 3; it++, uv += velocity * _BlurSize) {
float4 currentColor = tex2D(_MainTex, uv);
c += currentColor;
}
c /= 3;

return fixed4(c.rgb, 1.0); */



//在纹理采样的时候根据距离运动点远近进行合适的颜色权重分配,距离运动点越近颜色权重越高
float2 uv = i.uv;
float vecColRate[3] = { 0.7,0.2,0.1 };
float4 c = tex2D(_MainTex, uv) * vecColRate[0];
uv += velocity * _BlurSize;
for (int it = 1; it < 3; it++, uv += velocity * _BlurSize)
{
float4 currentColor = tex2D(_MainTex, uv);
c += currentColor * vecColRate[it];

}
return fixed4(c.rgb, 1.0);
}
ENDCG

Pass {
ZTest Always Cull Off ZWrite Off

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

ENDCG
}
}
FallBack Off
}

全局雾效

基于高度的雾效

在同一高度上,雾的浓度是相同的

image-20220712165656122

image-20220712165431439

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

public class MyFogWithDepthTexture : MyPostEffectsBase
{
public Shader fogShader;
private Material fogMaterial = null;
public Material material
{
get
{
fogMaterial = CheckShaderAndCreatMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}

//我们需要获取摄像机的相关参数,如进裁剪平面的距离,FOV等,同时还需要获取摄像机在世界空间下的前方上方和右方等方向。因此我们用两个变量存储摄像机的Camera和Transform组件
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.0f, 3.0f)]
public float fogDensity = 1.0f;
public Color fogColor = Color.white;
public float fogStart = 0.0f; //控制雾效的起始高度
public float fogEnd = 2.0f; //控制雾效的终止高度

//获取摄像机的深度纹理
void OnEnable()
{
camera.depthTextureMode |= DepthTextureMode.Depth;
}

//公式计算P277
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
//Matrix4x4.identity返回单位矩阵
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);

Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
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
//基于高度的雾效模拟
Shader "Unlit/MyFogWithDepthTexture"
{
Properties
{
_MainTex ("Texture", 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
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"

float4x4 _FrustumCornersRay;

sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture; //Unity传来的深度纹理
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;

struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2; //由顶点着色器输出并插值后得到的射线,包括该像素到摄像机的方向和距离信息
};

v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityViewToClipPos(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
//使用索引值获取_FrustumCornersRay对应的行作为该顶点的interpolatedRay值
o.interpolatedRay = _FrustumCornersRay[index];

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
//使用LinearEyeDepth将深度纹理的采样结果转换成视角空间下的线性深度值
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));

//得到世界空间下的位置
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;

//得到雾效系数
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity);

//使用雾效系数将雾的颜色和原始颜色混合
fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

return finalColor;
}
ENDCG

//定义雾效渲染所需的Pass
Pass {
ZTest Always Cull Off ZWrite Off

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

ENDCG
}
}
FallBack Off
}

非真实感渲染(NPR)

卡通风格渲染

image-20220713164412159

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
Shader "Unlit/MyToonShadingMat"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_Ramp("Ramp Texture",2D) = "white"{} //控制漫反射色调的渐变纹理
_Outline("Outline",Range(0,1)) = 0.1 //控制轮廓线宽度
_OutlineColor("Outline Color",Color) = (0,0,0,1) //控制轮廓线颜色
_Specular("Specular",Color) = (1,1,1,1) //高光反射颜色
_SpecularScale("Specular Scale",Range(0, 0.1))= 0.01 //控制计算高光反射时使用的阈值
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

//第一个Pass:使用轮廓线颜色渲染整个背面的面片
Pass
{
NAME "OUTLINE" //方便后面重复使用,只需要调用该Pass的名字
Cull Front //把正面的三角形面片剔除,之渲染背面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

float _Outline;
fixed4 _OutlineColor;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : SV_POSITION;
};

v2f vert(a2v v)
{
v2f o;

//把顶点和法线变换到视角空间下,用于对背部轮廓线处理P289
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);

normal.z = -0.5;
pos = pos + float4(normalize(normal), 0) * _Outline;
//从视角空间转换到剪切空间
o.pos = UnityViewToClipPos(pos);

return o;
}

//有轮廓线颜色渲染整个背面
float4 frag(v2f i) : SV_Target
{
return float4(_OutlineColor.rgb,1);
}
ENDCG
}

//第二个Pass:正常渲染正面
Pass
{
Tags { "LightMode"="ForwardBase" }
Cull Back

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#pragma multi_compile_fwdbase

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Ramp;
fixed4 _Specular;
fixed _SpecularScale;

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

struct v2f
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
SHADOW_COORDS(3)
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

TRANSFER_SHADOW(o);
return o;
}

float4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

fixed4 c = tex2D (_MainTex, i.uv);
fixed3 albedo = c.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
//半兰伯特漫反射系数和阴影值相乘得到最终的漫反射系数
fixed diff = halfLambert * atten;

//使用渐变纹理采样漫反射颜色
fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

//添加高光(抗锯齿处理方法见P290)
fixed spec = dot(worldNormal, worldHalfDir);
fixed w = fwidth(spec) * 2.0;
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}

素描风格渲染

image-20220713164341463

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
Shader "Unlit/MyHatchingMat"
{
Properties
{
_Color ("Color Tint",Color) = (1,1,1,1)
_TileFactor ("Tile Factor", Float) = 1 //纹理的平铺系数(越大,素描线条越密)
_Outline ("Outline",Range(0, 1)) = 1
//_Hatch0~_Hatch5对应了渲染时使用的6张素描纹理,它们的线条密度依次增大
_Hatch0 ("Hatch 0", 2D) = "white" {}
_Hatch1 ("Hatch 1", 2D) = "white" {}
_Hatch2 ("Hatch 2", 2D) = "white" {}
_Hatch3 ("Hatch 3", 2D) = "white" {}
_Hatch4 ("Hatch 4", 2D) = "white" {}
_Hatch5 ("Hatch 5", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

//第一个Pass:直接调用上一节中实现的轮廓线渲染Pass
//使用UsePass指令
UsePass "Unlit/MyToonShadingMat/OUTLINE"

//第二个Pass
Pass
{
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#pragma multi_compile_fwdbase

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc"

fixed4 _Color;
float _TileFactor;
sampler2D _Hatch0;
sampler2D _Hatch1;
sampler2D _Hatch2;
sampler2D _Hatch3;
sampler2D _Hatch4;
sampler2D _Hatch5;

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

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
//6张纹理,需要6个混合权重,我们把它们存储在两个fixed3类型的变量中
float3 hatchWeights0 : TEXCOORD1;
fixed3 hatchWeights1 : TEXCOORD2;
float3 worldPos : TEXCOORD3;
SHADOW_COORDS(4)
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy * _TileFactor;

fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
fixed3 worldNormal = UnityObjectToWorldDir(v.normal);

//计算漫反射系数
fixed diff = saturate(dot(worldLightDir,worldNormal));

//初始化混合权重
o.hatchWeights0 = fixed3(0,0,0);
o.hatchWeights1 = fixed3(0,0,0);

//将diff缩放到[0,7]范围
float hatchFactor = diff * 7.0;

//把[0,7]的区间均匀划分为7个子区间,通过判断hatchFactor所处的子区间来计算对应的纹理混合权重
if (hatchFactor > 6.0)
{
// Pure white, do nothing
}
else if (hatchFactor > 5.0)
{
o.hatchWeights0.x = hatchFactor - 5.0;
}
else if (hatchFactor > 4.0)
{
o.hatchWeights0.x = hatchFactor - 4.0;
o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
}
else if (hatchFactor > 3.0)
{
o.hatchWeights0.y = hatchFactor - 3.0;
o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
}
else if (hatchFactor > 2.0)
{
o.hatchWeights0.z = hatchFactor - 2.0;
o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
}
else if (hatchFactor > 1.0)
{
o.hatchWeights1.x = hatchFactor - 1.0;
o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
}
else
{
o.hatchWeights1.y = hatchFactor;
o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
}

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);

return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
//对每张纹理采样,并和权重值相乘得到采样颜色
fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;

//计算纯白在渲染中的贡献度,用于留白部分。光照最亮的部分时纯白色
fixed4 whiteColor = fixed4(1,1,1,1) * ( 1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);

fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
}

ENDCG
}
}
FallBack "Diffuse"
}