unity-doc-效果

漫反射

漫反射用于描述当光线从光源照射到模型表面时,该部分会向每个方向散射多少幅射量。
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的。因此可以认为在任何反射方向上的分布都是一样的。但是,入射光线的角度很重要。

兰伯特模型

反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。

$$c_{diffuse}=(c_{light}·m_{diffuse})max(0, \hat{n}·I)$$

$\hat{n}$是表面法线,$I$是指向光源方向的单位矢量,$c_{diffuse}$是材质的漫反射颜色,$m_{light}$是光源颜色。需要注意的是,需要防止法线和光源方向点乘的结果为负值,为此,我们使用max函数来将其截取到0,这可以防止物体被从后面来的光源照亮。

顶点着色器处理的兰伯特模型
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
Shader "Unlit/VertexLambertDiffuse"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1, 1, 1, 1)
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色

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 = UnityObjectToClipPos(v.vertex);

// 环境光颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

// 法线转换到世界空间
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

// 标准化世界空间光照方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

// 计算漫反射颜色
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal, worldLight));

// 最终光照计算
o.color = ambient + diffuse;

return o;
}

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

ENDCG
}
}

FallBack "Diffuse"
}

顶点着色兰伯特漫反射

顶点着色兰伯特漫反射
片元着色器处理的兰伯特模型
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
Shader "Unlit/FragLamberDiffuse"
{
Properties
{
_DiffuseColor ("Diffuse", Color) = (1, 1, 1, 1)
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色

struct a2v {
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
};

struct v2f {
float4 pos : SV_POSITION; // 裁剪空间坐标
float3 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 * _DiffuseColor.rgb * saturate(dot(worldNormal, worldLightDir));

// 最终光照计算
fixed3 color = ambient + diffuse;

return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Diffuse"
}

片元着色兰伯特漫反射

片元着色兰伯特漫反射

半兰伯特模型

与半兰伯特模型相比,半兰伯特光照模型没有使用max操作来防止$\hat{n}$和$I$的点积为负数,而是对其结果进行了一个$α$倍的缩放在加上一个$β$大小的偏移。绝大数情况下,$α$和$β$的平均值为0.5,即公式:

$$c_{diffuse}=(c_{light}·m_{diffuse})(α(\hat{n}·I)+β)$$

通过这样的方式,我们可以把$\hat{n}·I$的结果范围从$[-1,1]$映射到$[0,1]$范围内。对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0出;而在ban兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射在不同的值上。

片元着色器处理的半兰伯特模型
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
Shader "Unlit/FragHalfLamberDiffuse"
{
Properties
{
_DiffuseColor ("Diffuse", Color) = (1, 1, 1, 1)
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色

struct a2v {
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
};

struct v2f {
float4 pos : SV_POSITION; // 裁剪空间坐标
float3 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);

// 计算漫反射
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * halfLambert;

// 最终光照计算
fixed3 color = ambient + diffuse;

return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Diffuse"
}

片元着色半兰伯特漫反射

片元着色半兰伯特漫反射

高光反射(specular)

高光反射用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少幅射量。
高光反射是一种经验模型,也就是说,它并不完全符合真实世界中的高光反射现象。它可用于计算那些沿着完全镜面反射方向被反射的光线,这可以让物体看起来是有光泽的,例如金属材质。

计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。

phong高光模型

phong高光反射图

phong高光反射图

$$c_{specular}=(c_{light}·m_{specular})max(0, \hat{v}·r)^{m_{gloss}}$$

公式中$c_{light}$是入射光线的颜色和强度,$m_{specular}$是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色,$\hat{v}$是视角方向,$r$是反射方向,$m_{gloss}$是材质光泽度,也被称为反光度(shininess),它用于控制高光区域的亮点有多宽,gloss越大,亮点越小,同样,这了也需要放置$\hat{v}·r$的结果为负数。其中,反射方向$r$可以由表面法线$\hat{v}$和光源方向$\hat{I}$通过公式计算得到:

$$r=2(\hat{n}·\hat{I})\hat{n}-\hat{I}$$

在cg里面也可以通过reflect(i,n)函数计算得到。

顶点着色器处理的phong高光
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
Shader "Unlit/VertexPhoneSpecular"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1, 1, 1, 1)
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss ("Specular Gloss", Range(8.0, 256)) = 20
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光光泽系数

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 = 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 * _DiffuseColor.rgb * saturate(dot(worldNormal, worldLightDir));

// 计算反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

// 计算视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

// 计算高光
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _SpecularGloss);

// 计算最终颜色
o.color = ambient + diffuse + specular;

return o;
}

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

ENDCG
}
}

FallBack "Specular"
}

顶点着色phong高光

顶点着色phong高光
片元着色器处理的phong高光模型
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/FragPhongSpecular"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1, 1, 1, 1)
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss ("Specular Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光光泽系数

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 = 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);

// 归一化世界空间的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

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

// 计算反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

// 计算视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

// 计算高光
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _SpecularGloss);

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

ENDCG
}
}

FallBack "Specular"
}

片元着色phong高光

片元着色phong高光

blin-phong高光模型

相比于phong高光模型,布林冯氏光照模型的高光效果反射部分看起来更大、更亮一些。在实际渲染中,绝大多数情况我们都会选择Blinn-Phong光照模型。

blinn模型没有使用反射方向,而是引入一个新的矢量$\hat{h}$我们常说的半角向量,它是通过对$\hat{v}$和$\hat{I}$的取平均后再归一化得到。

$$\hat{h}=\dfrac{\hat{v}+\hat{I}}{|\hat{v}+\hat{I}|}$$

$$c_{specular}=(c_{light}·m_{specular})max(0, \hat{n}·\hat{h})$$

在硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,这是因为,此时可以认为v和I都是定值,因此h将是一个常量,但是,当v和I不是定值时,Phong模型可能反而更快一些。

片元着色器处理的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
81
82
83
84
Shader "Unlit/FragBlinnPhongSpecular"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1, 1, 1, 1)
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss ("Gloss Color", Range(8.0, 256)) = 20
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光光泽系数

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 = 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);

// 归一化世界空间的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

// 计算漫反射
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(worldNormal, worldLightDir));

// 计算视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

// 计算h半角向量
fixed3 halfDir = normalize(worldLightDir + viewDir);

// 计算高光
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularGloss);

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

ENDCG
}
}

FallBack "Specular"
}

片元着色blinnPhong高光

片元着色blinnPhong高光

凹凸映射

凹凸映射的实现主要有2种实现方式:一种是使用一张高度纹理来模拟表面位移,然后得到一个修改后的法线值。另一种是使用一张法线纹理来直接存储表面法线。

高度贴图

高度图中存储的是强度值,它用于表示模型表面局部的海拔高度。颜色越浅表面该位置的表面越向外凸起,而颜色越深表示该位置越往里凹。这种方法的好处是非常直观,可以明确知道一个模型表面的凹凸情况,但是计算会比较复杂,在实时计算时不能直接得到表面法线,需要由像素的灰度值计算而得,因此消耗更多的性能。

法线贴图

法线纹理中存储的是表面的法线方向。由于法线方向的分量范围在$[-1,1]$,而像素的分量范围在$[0,1]$,因此要得到像素对应的法线值,需要进行简单运算。

$$pixel=\dfrac{normal+1}{2}$$
$$normal=pixel*2-1$$

方向是相对于坐标空间来说的,对于法线纹理,一种就是直接将模型空间的法线存储在一张纹理里面,这是模型空间的法线纹理,另一种是存储切线空间的法线到纹理里面,这是切线空间的法线纹理,也是常用的处理方式。

法线纹理空间,原点是模型的顶点位置,z轴是订单的法线方向(n),x轴是顶点的切线方向(t),y轴是法线与切线叉积得到的副切线(b)。

模型空间法线特点

  • 计算量少,不需要模型的原始的法线和切线等信息
  • 法线直接可以通过模型的顶点的法线获得,生成简单
  • 在模型空间下的法线纹理存储的是同一坐标系下的法线信息,在边界处通过插值得到的法线可以平滑变换,可以提供更平滑的边界,在纹理坐标的缝合处和尖锐的边角部分,可见的缝隙较少。
  • 模型空间下的法线纹理记录是绝对的法线信息,仅可用于创建它的那个模型,而应用到其他模型上效果完全错误,复用不好。

切线空间法线特点

  • 模型的切线一般是和UV方向相同,想要得到效果比较好的法线映射就要求纹理映射也是映射的,生成较复杂。
  • 切线空间下的法线纹理中的法线是依靠纹理坐标的方向得到的结果,可能会在边缘处或尖锐的部分造成更多的缝合迹象。
  • 切线空间下的法线纹理记录是相对法线信息,即便把纹理应用到一个完全不一样的网格上,也可以得到一个合理的结果。
  • 可以进行UV动画
  • 纹理丢到其它模型上,效果也不会太差,服用性比较好
  • 由于切线空间下的法线纹理中的法线的Z方向总是正方向,因此可以仅存储XY方向,推导出Z的方向,而不存储Z方向数据,来压缩数据。

切线空间下的计算

在片元着色器中通过纹理采样得到切线空间下的法线,然后在与切线空间下的视角方向、光照方向等进行计算,得到最终的光照效果。这期间需要在顶点着色器中把视角方向和光照方向从模型空间变换到切线空间中,即需要知道从模型空间到切线空间的变换矩阵,可以通过切线空间到模型空间的变换矩阵的逆矩阵来得到。

切线空间处理法线贴图
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
Shader "Unlit/normalInTangent"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
_MainTex("Main Tex", 2D) = "white" {}
_NormalTex("Normal Tex", 2D) = "bump" {}
_NormalFac("Normal Fac", Float) = 1.0
_SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss("Specular Gloss", Range(1.0, 256)) = 8
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // 主颜色
sampler2D _MainTex; // 主纹理
float4 _MainTex_ST; // 漫反射纹理系数
sampler2D _NormalTex; // 法线纹理
float4 _NormalTex_ST; // 法线纹理系数
float _NormalFac; // 法线系数
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光因子

struct a2v{
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
float4 tangent : TANGENT; // 顶点切线
float4 texcoord : TEXCOORD0; // 顶点UV
};

struct v2f {
float4 pos : SV_POSITION; // 顶点裁剪坐标
float4 uv : TEXCOORD0; // 存放顶点漫反射纹理uv和法线纹理uv
float3 lightDir: TEXCOORD1; // 切线空间的光照方向
float3 viewDir : TEXCOORD2; // 切线空间的视窗方向
};

v2f vert(a2v v) {
v2f o;

// 计算裁剪坐标
// o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)));
o.pos = UnityObjectToClipPos(v.vertex);

// 计算漫反射纹理和法线纹理uv
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;

// 计算切线空间变换矩阵
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
// float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );
TANGENT_SPACE_ROTATION;

// 光照方向从模型空间变换到切线空间
// float3 objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;
// float3 oSpaceLightDir = objSpaceLightPos.xyz - v.vertex.xyz * _WorldSpaceLightPos0.w;
float3 oSpaceLightDir = ObjSpaceLightDir(v.vertex);
o.lightDir = mul(rotation, oSpaceLightDir).xyz;

// 视窗方向从模型空间变换到切线空间
// float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
// float3 oSpaceViewDir = objSpaceCameraPos - v.vertex.xyz;
float3 oSpaceViewDir = ObjSpaceViewDir(v.vertex);
o.viewDir = mul(rotation, oSpaceViewDir).xyz;

return o;
}

fixed4 frag(v2f i) : SV_Target{
// 标准化光照和视窗方向变量
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);

// 采样法线纹理
fixed4 packedNormal = tex2D(_NormalTex, i.uv.zw);

// packedNormal.x *= packedNormal.w;
// fixed3 normal;
// normal.xy = packedNormal.xy * 2 - 1;
// normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
// fixed3 tNormal = normal;
fixed3 tNormal = UnpackNormal(packedNormal);

// 法线调整
tNormal.xy *= _NormalFac;
tNormal.z = sqrt(1.0 - saturate(dot(tNormal.xy, tNormal.xy)));

// 采样漫反射纹理
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

// 环境光计算
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

// 漫反射颜色计算
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tNormal, tangentLightDir));

// 高光计算
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tNormal, halfDir)), _SpecularGloss);

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Shader "Unlit/normalInWorld"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
_MainTex("Main Tex", 2D) = "white" {}
_NormalTex("Normal Tex", 2D) = "bump" {}
_NormalFac("Normal Fac", Float) = 1.0
_SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss("Specular Gloss", Range(1.0, 256)) = 8
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // 主颜色
sampler2D _MainTex; // 主纹理
float4 _MainTex_ST; // 漫反射纹理系数
sampler2D _NormalTex; // 法线纹理
float4 _NormalTex_ST; // 法线纹理系数
float _NormalFac; // 法线系数
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光因子

struct a2v{
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
float4 tangent : TANGENT; // 顶点切线
float4 texcoord : TEXCOORD0; // 顶点UV
};

struct v2f{
float4 pos : SV_POSITION; // 顶点裁剪坐标
float4 uv : TEXCOORD0; // 存放顶点漫反射纹理uv和法线纹理uv
float4 TtoW0 : TEXCOORD1; // 存放切线空间信息+世界坐标
float4 TtoW1 : TEXCOORD2; // 存放切线空间信息+世界坐标
float4 TtoW2 : TEXCOORD3; // 存放切线空间信息+世界坐标
};

v2f vert(a2v v){
v2f o;

// 计算裁剪坐标
// o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)));
o.pos = UnityObjectToClipPos(v.vertex);

// 计算漫反射纹理和法线纹理uv
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;

// 计算切线空间各轴
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

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

// fixed3 worldTangent = normalize(mul((float3x3)unity_ObjectToWorld, v.tangent.xyz));
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 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));

// 计算世界空间的视图方向
// fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

// 采样法线
// fixed4 texNormal = tex2D(_NormalTex, i.uv.zw);
// texNormal.x *= texNormal.w;
// fixed3 normal;
// normal.xy = texNormal.xy * 2 - 1;
// normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
// fixed3 tNormal = normal;
fixed3 tNormal = UnpackNormal(tex2D(_NormalTex, i.uv.zw));

// 法线调整
tNormal.xy *= _NormalFac;
tNormal.z = sqrt(1.0 - saturate(dot(tNormal.xy, tNormal.xy)));

// 法线变换到世界空间
tNormal = normalize(half3(dot(i.TtoW0.xyz, tNormal), dot(i.TtoW1.xyz, tNormal), dot(i.TtoW2.xyz, tNormal)));

// 采样漫反射纹理
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

// 环境光计算
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

// 漫反射颜色计算
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tNormal, lightDir));

// 高光计算
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tNormal, halfDir)), _SpecularGloss);

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
Shader "Unlit/RampTexture"
{
Properties
{
_DiffuseColor ("Diffuse Color", Color) = (1, 1, 1, 1)
_RampTex ("Ramp Tex", 2D) = "white" {}
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss ("Specular Gloss", Range(8.0, 256)) = 20
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _DiffuseColor; // 漫反射颜色
sampler2D _RampTex; // 渐变纹理
float4 _RampTex_ST; // 渐变纹理系数
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光光泽系数

struct a2v {
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
float4 texcoord : TEXCOORD0; // 顶点uv
};

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;

// 计算漫反射
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _DiffuseColor.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;

// 计算世界空间视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

// 计算高光
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularGloss);

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

ENDCG
}
}
}

渐变纹理

渐变纹理

遮罩纹理

使用纹理图来控制和保护某些区域,使它们免于某些修改,例如使用遮罩图控制模型高光控制,非金属部分不让高光。

高光遮罩纹理处理
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/SpecularMask"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_NormalTex ("Normal Tex", 2D) = "bump" {}
_NormalFac("Normal Fac", Float) = 1.0
_SpecularMaskTex ("Specular Mask Tex", 2D) = "white" {}
_SpecularMaskFac ("Specular Mask Fac", Float) = 1.0
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
_SpecularGloss ("Specular Gloss", Range(8.0, 256)) = 20
}

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // 主颜色
sampler2D _MainTex; // 主纹理
float4 _MainTex_ST; // 主纹理系数
sampler2D _NormalTex; // 法线纹理
float _NormalFac; // 法线纹理系数
sampler2D _SpecularMaskTex; // 高光遮罩纹理
float _SpecularMaskFac; // 高光遮罩系数
fixed4 _SpecularColor; // 高光颜色
float _SpecularGloss; // 高光光泽系数

struct a2v {
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
float4 tangent : TANGENT; // 顶点切线
float4 texcoord : TEXCOORD0; // uv
};

struct v2f {
float4 pos : SV_POSITION; // 裁剪坐标
float2 uv : TEXCOORD0; // 纹理uv
float3 lightDir: TEXCOORD1; // 切线空间光照方向
float3 viewDir : TEXCOORD2; // 切线空间视角方向
};

v2f vert(a2v v) {
v2f o;

// 计算裁剪坐标
o.pos = UnityObjectToClipPos(v.vertex);

// 计算UV
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

// 计算切线空间变换矩阵
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(_NormalTex, i.uv));
tangentNormal.xy *= _NormalFac;
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 * max(0, dot(tangentNormal, tangentLightDir));

// 计算高光半角向量
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);

// 计算光照遮罩系数
fixed specularMask = tex2D(_SpecularMaskTex, i.uv).r * _SpecularMaskFac;

// 计算高光
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfDir)), _SpecularGloss) * specularMask;

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

ENDCG
}
}

FallBack "Specular"
}

遮罩纹理

遮罩纹理