Unity Shader 之 基础纹理

基础纹理

纹理的目的就是使用一张图片来控制模型的外观。使用纹理映射(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 }

(完)

posted @ 2017-11-30 14:41  Sooda  阅读(2968)  评论(2编辑  收藏  举报