基础纹理
纹理的目的就是使用一张图片来控制模型的外观。使用纹理映射(texture mapping)技术,我们可以把一张图“粘”在模型表面,逐纹素(texel)地控制模型的颜色。
建模软件中利用纹理展开技术把纹理映射坐标(texture-mapping coordinates)存储在每个顶点上。纹理映射坐标(UV坐标,u为横向坐标,v为纵向坐标)定义了该顶点在纹理中对应的2D坐标。
UV坐标通常都会被归一化到[0,1]范围内。而Unity中用的坐标为下图,与OpenGL中的坐标相同,而DirectX的坐标原点在左上角。
单张纹理
我们通常会用一张纹理来代替物体的漫反射颜色。
1 Shader "Unity My Shader/Diffuse Texture"
2 {
3 Properties
4 {
5 _Color("Color", Color) = (1,1,1,1)
6 _MainTex ("Main Tex", 2D) = "white" {}
7 _Specular ("Specular", Color) = (1,1,1,1)
8 _Gloss("Gloss", Range(8.0, 256)) = 20
9 }
10 SubShader
11 {
12 Pass
13 {
14 Tags{"LightMode"="ForwardBase"}
15
16 CGPROGRAM
17 #pragma vertex vert
18 #pragma fragment frag
19
20 #include "UnityCG.cginc"
21 #include "Lighting.cginc"
22
23 fixed4 _Color;
24 sampler2D _MainTex;
25 float4 _MainTex_ST;
26 fixed4 _Specular;
27 float _Gloss;
28
29 struct a2v
30 {
31 float4 vertex : POSITION;
32 float3 normal : NORMAL;
33 // 将模型的第一组纹理坐标存储到该变量中
34 float3 texcoord : TEXCOORD0;
35 };
36
37 struct v2f
38 {
39 float4 pos : SV_POSITION;
40 float3 worldPos : TEXCOORD0;
41 float3 worldNormal : TEXCOORD1;
42 float2 uv : TEXCOORD2;
43 };
44
45 v2f vert (a2v v)
46 {
47 v2f o;
48
49 o.pos = UnityObjectToClipPos(v.vertex);
50 // 模型坐标顶点转换世界坐标顶点
51 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
52 // 模型坐标法线转换世界坐标法线
53 o.worldNormal = UnityObjectToWorldNormal(v.normal);
54 // 对顶点纹理坐标进行变换,最终得到uv坐标。
55 // 方法原理 o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
56 //_MainTex_ST 是纹理的属性值,写法是固定的为 纹理名+_ST
57 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
58
59 return o;
60 }
61
62 fixed4 frag (v2f i) : SV_Target
63 {
64 // 法线方向
65 fixed3 worldNormal = normalize(i.worldNormal);
66 // 光照方向
67 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
68 // 视角方向
69 fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
70 // 对纹理进行采样,返回为计算得到的纹素值,与_Color的乘积作为反射率
71 fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
72 // 环境光
73 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
74 // 漫反射
75 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
76 // Blinn模型 计算
77 fixed3 halfDir = normalize(worldViewDir + worldLightDir);
78 // 高光反射
79 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
80 // 相加后输出颜色
81 return fixed4(ambient + diffuse + specular, 1);
82 }
83 ENDCG
84 }
85 }
86 }
凹凸映射
目的:使用一张纹理来修改模型表面的法线,以便为模型提供更多细节。不会真的改变模型的顶点位置,只是让模型看起来好像“凹凸不平”。
实现凹凸映射的两种方法:高度映射(height mapping)和法线映射(normal mapping)
- 高度映射:使用高度纹理(height map)模拟表面位移(displacement),然后得到一个修改后的法线值。
- 法线映射:使用法线纹理(normal map)来直接存储表面法线。
高度纹理
高度图:存储强度值(intensity),表示模型表面局部的海拔高度。颜色越浅表明该位置的表明越向外凸起,越深越向内凹陷。
缺点:计算时不能直接得到表面法线,得同过灰度值计算得到,消耗性能。
法线纹理
法线纹理中存储的就是表面的法线方向。由于法线方向的分量范围在[-1,1],而像素的分量范围是[0,1],所以需要映射一下: pixel = (normal + 1) / 2
反之: normal = pixel * 2 - 1
效果如下:
不开启凹凸效果 开启凹凸效果(切线空间)
开启凹凸效果(世界空间)
Shader代码如下:
在切线空间下的凹凸纹理实现
1 Shader "Unity My Shader/aotu Texture"
2 {
3 Properties
4 {
5 _Color("Color", Color) = (1,1,1,1)
6 _MainTex ("Main Tex", 2D) = "white" {}
7 _BumpTex ("Bump Tex", 2D) = "Bump" {}
8 _BumpScale ("Bump Scale", Float) = 1
9 _Specular ("Specular", Color) = (1,1,1,1)
10 _Gloss("Gloss", Range(8.0, 256)) = 20
11 }
12 SubShader
13 {
14 Pass
15 {
16 Tags{"LightMode"="ForwardBase"}
17
18 CGPROGRAM
19 #pragma vertex vert
20 #pragma fragment frag
21
22 #include "UnityCG.cginc"
23 #include "Lighting.cginc"
24
25 fixed4 _Color;
26 sampler2D _MainTex;
27 float4 _MainTex_ST;
28 sampler2D _BumpTex;
29 float4 _BumpTex_ST;
30 float _BumpScale;
31 fixed4 _Specular;
32 float _Gloss;
33
34 struct a2v
35 {
36 float4 vertex : POSITION;
37 // 将模型的法线方向存储到变量中
38 float3 normal : NORMAL;
39 // 将模型的第一组纹理坐标存储到变量中
40 float3 texcoord : TEXCOORD0;
41 // 将模型的顶点切线方向存储到变量中,float4的原因是用w来决定切线空间的第三个坐标轴——福切线的方向性。
42 float4 tangent : TANGENT;
43 };
44
45 struct v2f
46 {
47 float4 pos : SV_POSITION;
48 float3 lightDir : TEXCOORD0;
49 fixed3 viewDir : TEXCOORD1;
50 // 存储两个uv坐标 _MainTex 与 _BumpTex
51 float4 uv : TEXCOORD2;
52 };
53
54 v2f vert (a2v v)
55 {
56 v2f o;
57
58 o.pos = UnityObjectToClipPos(v.vertex);
59
60 // 计算福切线方向 叉乘获得与垂直于法线和切线平面的福切线方向,v.tangent.w 用来抉择福切线的方向(因为有两个方向)
61 // 方法原理 float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
62 // 模型空间到切线空间的变换矩阵
63 // 方法原理 float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
64 TANGENT_SPACE_ROTATION;
65
66 // ObjSpaceLightDir(模型空间的光照方向),转换成切线空间的光照方向。
67 o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
68 // 同上,转换的是视角方向
69 o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
70
71 // 对顶点纹理坐标进行变换,最终得到uv坐标。
72 // 方法原理 o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
73 //_MainTex_ST 是纹理的属性值,写法是固定的为 纹理名+_ST
74 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
75 o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpTex);
76
77 return o;
78 }
79
80 fixed4 frag (v2f i) : SV_Target
81 {
82 // 切线空间的光照方向
83 fixed3 tangentLightDir = normalize(i.lightDir);
84 // 切线空间的视角方向
85 fixed3 tangentViewDir = normalize(i.viewDir);
86
87 // 对法线纹理进行采样
88 fixed4 packedNormal = tex2D(_BumpTex, i.uv.zw);
89 // 转换映射
90 // 方法原理 tangentNormal.xy = (packedNormal.xy * 2 - 1);
91 fixed3 tangentNormal = UnpackNormal(packedNormal);
92 tangentNormal.xy *= _BumpScale;
93 tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
94
95 // 对纹理进行采样,返回为计算得到的纹素值,与_Color的乘积作为反射率
96 fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
97 // 环境光
98 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
99 // 漫反射
100 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
101 // Blinn模型 计算
102 fixed3 halfDir = normalize(tangentViewDir + tangentLightDir);
103 // 高光反射
104 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
105 // 相加后输出颜色
106 return fixed4(ambient + diffuse + specular, 1);
107 }
108 ENDCG
109 }
110 }
111 }
在世界空间下计算
1 _BumpTex ("Bump Tex", 2D) = "Bump" {}
2 _BumpScale ("Bump Scale", Float) = 1
3 _Specular ("Specular", Color) = (1,1,1,1)
4 _Gloss("Gloss", Range(8.0, 256)) = 20
5 }
6 SubShader
7 {
8 Pass
9 {
10 Tags{"LightMode"="ForwardBase"}
11
12 CGPROGRAM
13 #pragma vertex vert
14 #pragma fragment frag
15
16 #include "UnityCG.cginc"
17 #include "Lighting.cginc"
18
19 fixed4 _Color;
20 sampler2D _MainTex;
21 float4 _MainTex_ST;
22 sampler2D _BumpTex;
23 float4 _BumpTex_ST;
24 float _BumpScale;
25 fixed4 _Specular;
26 float _Gloss;
27
28 struct a2v
29 {
30 float4 vertex : POSITION;
31 // 将模型的法线方向存储到变量中
32 float3 normal : NORMAL;
33 // 将模型的第一组纹理坐标存储到变量中
34 float3 texcoord : TEXCOORD0;
35 // 将模型的顶点切线方向存储到变量中,float4的原因是用w来决定切线空间的第三个坐标轴——福切线的方向性。
36 float4 tangent : TANGENT;
37 };
38
39 struct v2f
40 {
41 float4 pos : SV_POSITION;
42 float4 TtoW0 : TEXCOORD0;
43 float4 TtoW1 : TEXCOORD1;
44 float4 TtoW2 : TEXCOORD2;
45 float4 uv : TEXCOORD3;
46 };
47
48 v2f vert (a2v v)
49 {
50 v2f o;
51
52 o.pos = UnityObjectToClipPos(v.vertex);
53
54 // 世界空间顶点坐标
55 float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
56 // 世界空间法线方向
57 float3 worldNormal = UnityObjectToWorldNormal(v.normal);
58 // 世界空间切线方向
59 float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
60 // 世界空间福切线方向
61 float3 worldBinormal = cross(worldTangent, worldNormal) * v.tangent.w;
62
63 // 对顶点纹理坐标进行变换,最终得到uv坐标。
64 // 方法原理 o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
65 //_MainTex_ST 是纹理的属性值,写法是固定的为 纹理名+_ST
66 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
67 o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpTex);
68
69 // 类矩阵形式保存
70 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
71 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
72 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
73
74 return o;
75 }
76
77 fixed4 frag (v2f i) : SV_Target
78 {
79 // 构建世界空间的顶点坐标
80 float3 worldPos = fixed3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
81 // 世界空间的光照方向
82 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
83 // 世界空间的视角方向
84 fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
85
86 // 对法线纹理进行采样
87 fixed4 packedNormal = tex2D(_BumpTex, i.uv.zw);
88 // 转换映射
89 // 方法原理 tangentNormal.xy = (packedNormal.xy * 2 - 1);
90 fixed3 bump = UnpackNormal(packedNormal);
91 bump.xy *= _BumpScale;
92 bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
93 // 把法线变换到世界空间下(法线原为切线空间),通过使用点乘操作来实现矩阵的每一行和法线相乘得到
94 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
95
96 // 对纹理进行采样,返回为计算得到的纹素值,与_Color的乘积作为反射率
97 fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
98 // 环境光
99 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
100 // 漫反射
101 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, worldLightDir));
102 // Blinn模型 计算
103 fixed3 halfDir = normalize(worldViewDir + worldLightDir);
104 // 高光反射
105 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
106 // 相加后输出颜色
107 return fixed4(ambient + diffuse + specular, 1);
108 }
109 ENDCG
110 }
111 }
112 }
遮罩纹理
作用:遮罩允许我们可以保护某些区域,使它们免于某些修改
流程:采样得到遮罩纹理的纹素值,使用其中摸个(或某几个)通道的值来与某种表面的属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性影响
漫反射+高光反射+凹凸纹理+遮罩
Shader如下
1 Shader "Unity My Shader/aotu Texture"
2 {
3 Properties
4 {
5 _Color("Color", Color) = (1,1,1,1)
6 _MainTex ("Main Tex", 2D) = "white" {}
7 _BumpTex ("Bump Tex", 2D) = "Bump" {}
8 _BumpScale ("Bump Scale", Float) = 1
9 _SpecularMask ("Specular Mask Tex", 2D) = "white" {}
10 _SpecularScale ("Specular Scale", Float) = 1
11 _Specular ("Specular", Color) = (1,1,1,1)
12 _Gloss("Gloss", Range(8.0, 256)) = 20
13 }
14 SubShader
15 {
16 Pass
17 {
18 Tags{"LightMode"="ForwardBase"}
19
20 CGPROGRAM
21 #pragma vertex vert
22 #pragma fragment frag
23
24 #include "UnityCG.cginc"
25 #include "Lighting.cginc"
26
27 fixed4 _Color;
28 sampler2D _MainTex;
29 float4 _MainTex_ST;
30 sampler2D _BumpTex;
31 float4 _BumpTex_ST;
32 float _BumpScale;
33 sampler2D _SpecularMask;
34 float4 _SpecularMask_ST;
35 float _SpecularScale;
36 fixed4 _Specular;
37 float _Gloss;
38
39 struct a2v
40 {
41 float4 vertex : POSITION;
42 // 将模型的法线方向存储到变量中
43 float3 normal : NORMAL;
44 // 将模型的第一组纹理坐标存储到变量中
45 float3 texcoord : TEXCOORD0;
46 // 将模型的顶点切线方向存储到变量中,float4的原因是用w来决定切线空间的第三个坐标轴——福切线的方向性。
47 float4 tangent : TANGENT;
48 };
49
50 struct v2f
51 {
52 float4 pos : SV_POSITION;
53 float2 uv : TEXCOORD0;
54 float3 lightDir : TEXCOORD1;
55 float3 viewDir : TEXCOORD2;
56 };
57
58 v2f vert (a2v v)
59 {
60 v2f o;
61
62 o.pos = UnityObjectToClipPos(v.vertex);
63
64 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
65
66 TANGENT_SPACE_ROTATION;
67
68 o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
69 o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
70
71 return o;
72 }
73
74 fixed4 frag (v2f i) : SV_Target
75 {
76 // 世界空间的光照方向
77 fixed3 tangentLightDir = normalize(i.lightDir);
78 // 世界空间的视角方向
79 fixed3 tangentViewDir = normalize(i.viewDir);
80
81 // 对法线纹理进行采样
82 fixed4 packedNormal = tex2D(_BumpTex, i.uv);
83 fixed3 tangentNormal = UnpackNormal(packedNormal);
84 tangentNormal.xy *= _BumpScale;
85 tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
86
87 // 对纹理进行采样,返回为计算得到的纹素值,与_Color的乘积作为反射率
88 fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
89 // 环境光
90 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
91 // 漫反射
92 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
93 // Blinn模型 计算
94 fixed3 halfDir = normalize(tangentViewDir + tangentLightDir);
95 // 对高光反射的r通道计算掩码值
96 fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
97 // 高光反射 添加遮罩
98 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
99 // 相加后输出颜色
100 return fixed4(ambient + diffuse + specular, 1);
101 }
102 ENDCG
103 }
104 }
105 }
(完)