
纹理类型中需要声明一个float4类型的变量_MainTex_ST,其中_MainTex_ST的名字不是任意起的,在unity中,需要使用纹理名_ST的方式来声明某个纹理的属性。
ST是缩放(scale)和平移(translation)的缩写
_MainTex_ST可以让我们得到该纹理的缩放和平移(偏移)值
_MainTex_ST.xy存储的是缩放值,_MainTex_ST.zw存储的是偏移值
这些值可以在材质面板的纹理属性中调节
效果如下:

全部代码:
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
-
- Shader "Shader/SingleTexture"
- {
- Properties
- {
- _MainTex ("Texture", 2D) = "white" {}
- _Color ("Color", Color) = (1,1,1,1)
- _Specular ("Specular", Color) = (1,1,1,1)
- _Gloss ("Gloss", Range(8.0, 256)) = 20
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
-
- Pass
- {
- Tags{"LightMode"="ForwardBase"}//LightMode标签是Pass标签中的一种,用于定义该Pass在unity的光照流水线中的角色
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float2 uv : TEXCOORD0;
- };
-
- struct v2f
- {
- float4 pos : SV_POSITION;
- float3 worldNormal : TEXCOORD0;
- float3 worldPos : TEXCOORD1;
- float2 uv : TEXCOORD2;
- };
-
- sampler2D _MainTex;
- float4 _MainTex_ST;
- fixed4 _Color;
- fixed4 _Specular;
- float _Gloss;
-
- v2f vert (appdata v)
- {
- v2f o;
- o.pos = UnityObjectToClipPos(v.vertex);
- o.worldNormal = UnityObjectToWorldNormal(v.normal);
- o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
- o.uv = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
-
- return o;
- }
-
- fixed4 frag (v2f i) : SV_Target
- {
- fixed3 worldNormal = normalize(i.worldNormal);
- fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
-
- fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
-
- fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
-
- fixed3 diffuse = _LightColor0.rgb * albedo *
- max(0, dot(worldNormal, worldLightDir));
-
- fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
- fixed3 halfDir = normalize(worldLightDir + viewDir);
- fixed3 specular = _LightColor0.rgb * _Specular.rgb *
- pow(max(0, dot(worldNormal, halfDir)), _Gloss);
-
- return fixed4(ambient + diffuse + specular, 1.0);
- }
- ENDCG
- }
- }
- Fallback "Specular"
- }
我们向unity中导入一张纹理资源,其材质面板上的参数如下,我们可以调节他们

Texture Type 纹理类型:选择合适的纹理类型,才能让unity知道我们的意图,传递正确的纹理,或对纹理进行优化
Wrap Mode:决定了当纹理坐标超过[0,1]范围后如何被平铺。
Wrap Mode有两种模式:
Repeat,如果纹理超过1,那么它的整数部分将会被舍弃,直接使用小数部分采样。这样的结果是纹理将会不断重复。
Clamp,如果纹理大于1,那么将会截取到1,如果小于0,那么就会截取到0

Filter Mode:决定了当纹理由于变化而产生拉伸时将会采用哪种滤波模式
Filter Mode有三种模式:Point、Bilinear、Trilinear。它们得到的图片滤波效果依次提升,但耗费的性能也依次增大。纹理滤波会影响放大或缩小纹理时得到的图片质量。
如:把64x64大小的纹理贴在512x512大小的平面上时,就需要放大纹理

mip map:多级渐远纹理:在一个很小的空间中有许多东西
多级渐远纹理技术将原纹理提前用滤波处理来得到很多更小的图像,形成了一个图像金字塔,每一层都是对上一层图像采样的结果。这样实时运行,就可以快速得到结果像素。
如当物体原理相机时,就用较小的纹理
缺点是需要一定空间存储,通常会多占用33%的内存空间

一般情况下选择Bilinear模式,注意,有时我们不希望纹理看起来是模糊的,例如对于一些类似棋盘的纹理,这是就希望是像素风,那么就用Point模式
不同平台发布游戏时,需要考虑目标平台的纹理和质量问题。
如果导入的纹理超出最大尺寸,那么unity会把该纹理缩放为这个最大分辨率。
理想情况下导入的纹理可以是非正方形的,但长宽的大小应该是2的幂。2、4、8、16、32、64
如果使用非2的幂大小(NPOT),那么纹理会占用跟多的内存空间,而且GPU读取该纹理的速度也会有所下降
一些平台不支持NPOT纹理,unity会在内部将它缩放为2的幂,为了性能,请尽量使用2的幂大小
Format:决定了unity内部用哪种格式存储该纹理。使用的纹理格式精度越高,占用的内存空间越大,得到的效果越好。
在最下方可以查看存储该纹理需要占用的内存空间(若开启多级渐远纹理技术,也会增加纹理的内存占用)。
凹凸纹理:使用一种纹理来修改模型表面的法线,以便为模型提供更多细节。
并不会真的改变模型顶点位置,只是让模型看起来凹凸不平,但在模型的轮廓处可以看出破绽
使用一张高度纹理(height map)来模拟表面位移,然后得到一个修改后的法线值,这种方法也被称为高度映射。

高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。
颜色越浅,越向外凸起,颜色越深,越向内凹陷
好处:直观,可以从高度图中明确地知道每个模型的表面凹凸情况
缺点:计算更复杂,在实时计算时不能直接得到表面法线,而需要灰度值计算而得到,需要消耗更多的性能
高度图一般与法线映射一起使用,用于给出表面凹凸的额外信息。我们通常会用法线映射案例修改光照
使用一张法线纹理来直接存储表面法线,也被称为法线映射。
法线纹理中存储的就是表面的发现方向,由于法线方向的分量范围在[-1,1],而像素的分量范围为[0,1],因此我们需要做一个映射,通常的映射方法:
在shader中对纹理法线进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射即是映射函数的逆函数:
模型顶点自带的法线,它们定义子啊模型空间中,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为模型空间的法线纹理。
切线空间:模型的每个顶点都有属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,称为负切线空间(b)。
如果选择切线空间,则要把法线纹理中得到的法线方向的切线空间转换到世界空间(或其他空间)中
使用模型空间存储法线的优点:
使用切线空间的优点:
切线空间很多情况下都优于模型空间,而且可以节省美术人员的工作
在计算光照模型时,要统一各个方向矢量所在的坐标空间。由于法线纹理中存储的法线是切线空间下的方向,因此有两种选择:
效率上,第一种更优:可以在顶点着色器中就完成对光照方向和视角方向的变换,第二种要先对法线纹理进行采样,所以变换过程必须在片元着色器,必须在片元着色器中进行一次矩阵操作。
通用性,第二种更优:要在世界空间下计算。如果在采样的同时进行法线映射,就需要把法线方向变换到世界空间下。
也可以选择其他坐标空间计算,如模型空间等,但切线空间和世界空间是最常见的
在切线空间下:
效果如下:

代码如下:
- Shader "Shader/NormalMap"
- {
- Properties
- {
- _Color ("Color", Color) = (1,1,1,1)
- _MainTex ("Texture", 2D) = "white" {}
- _BumpMap ("Normal Map", 2D) = "bump" {}//unity内置法线纹理bump
- _BumpScale ("Bump Scale", Float) = 1//控制凹凸程度
- _Specular ("Sprcular", Color) = (1,1,1,1)
- _Gloss ("Gloss", Range(8.0, 256)) = 20
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
-
- Pass
- {
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 tangent : TANGENT;
- float4 uv : TEXCOORD0;
-
- };
-
- struct v2f
- {
- float4 pos : SV_POSITION;
- float4 uv : TEXCOORD0;
- float3 LightDir : TEXCOORD1;
- float3 ViewDir : TEXCOORD2;
- };
-
- fixed4 _Color;
- sampler2D _MainTex;
- float4 _MainTex_ST;
- sampler2D _BumpMap;
- float4 _BumpMap_ST;
- float _BumpScale;
- fixed4 _Specular;
- float _Gloss;
-
- v2f vert (appdata v)
- {
- v2f o;
- o.pos = UnityObjectToClipPos(v.vertex);
-
- o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
- o.uv.zw = v.uv.xy * _BumpMap_ST.xy + _BumpMap_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 tlDir = normalize(i.LightDir);
- fixed3 tvDir = normalize(i.ViewDir);
-
- fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
- fixed3 tangentNormal;
-
- tangentNormal = UnpackNormal(packedNormal);
- 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 *
- max(0, dot(tangentNormal, tlDir));
-
- fixed3 halfDir = normalize(tlDir + tvDir);
- fixed3 specular = _LightColor0.rgb * _Specular.rgb *
- pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
-
- return fixed4(ambient + diffuse + specular, 1.0);
- }
- ENDCG
- }
- }
- Fallback "Specular"
- }
在世界空间下:

- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
-
- Shader "Shader/NormalMap"
- {
- Properties
- {
- _Color ("Color", Color) = (1,1,1,1)
- _MainTex ("Texture", 2D) = "white" {}
- _BumpMap ("Normal Map", 2D) = "bump" {}//unity内置法线纹理bump
- _BumpScale ("Bump Scale", Float) = 1//控制凹凸程度
- _Specular ("Sprcular", Color) = (1,1,1,1)
- _Gloss ("Gloss", Range(8.0, 256)) = 20
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
-
- Pass
- {
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 tangent : TANGENT;
- float4 uv : TEXCOORD0;
-
- };
-
- struct v2f
- {
- float4 pos : SV_POSITION;
- float4 uv : TEXCOORD0;
- float4 TtoW0 : TEXCOORD1;
- float4 TtoW1 : TEXCOORD2;
- float4 TtoW2 : TEXCOORD3;
- };
-
- fixed4 _Color;
- sampler2D _MainTex;
- float4 _MainTex_ST;
- sampler2D _BumpMap;
- float4 _BumpMap_ST;
- float _BumpScale;
- fixed4 _Specular;
- float _Gloss;
-
- v2f vert (appdata v)
- {
- v2f o;
- o.pos = UnityObjectToClipPos(v.vertex);
-
- o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
- o.uv.zw = v.uv.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
-
- 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 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 *
- max(0, dot(bump, lightDir));
-
- fixed3 halfDir = normalize(lightDir + viewDir);
- fixed3 specular = _LightColor0.rgb * _Specular.rgb *
- pow(max(0, dot(bump, halfDir)), _Gloss);
-
- return fixed4(ambient + diffuse + specular, 1.0);
- }
- ENDCG
- }
- }
- Fallback "Specular"
- }
把法线纹理的纹理类型标识成Normal map时,可以使用Unity的内置函数UnpackNormal来得到正确的法线方向

当我们使用那些包含了法线映射的内置的UnityShader时,必须把使用的法线纹理按上面的方式标识成Normal map,才能得到正确结果
纹理可以用于储存任何表面属性。如使用渐变纹理来控制漫反射光照的结果。这样可以更灵活的控制光照结果
基于冷暖色调的着色技术,这种技术可以保证物体的轮廓线相比于之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。

- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
-
- Shader "Shader/RampTexture"
- {
- Properties
- {
- _Color ("Color", Color) = (1,1,1,1)
- _RampTex ("Ramp Tex", 2D) = "white" {}
- _Specular ("Sprcular", Color) = (1,1,1,1)
- _Gloss ("Gloss", Range(8.0, 256)) = 20
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
-
- Pass
- {
- Tags { "LightMode"="ForwardBase" }
-
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 uv : TEXCOORD0;
- };
-
- struct v2f
- {
- float4 pos : SV_POSITION;
- float3 worldNormal : TEXCOORD0;
- float3 worldPos : TEXCOORD1;
- float2 uv : TEXCOORD2;
- };
-
- fixed4 _Color;
- sampler2D _RampTex;
- float4 _RampTex_ST;
- fixed4 _Specular;
- float _Gloss;
-
-
- v2f vert (appdata 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.uv, _RampTex);
-
- 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
- * _Color.rgb;
-
- fixed3 diffuse = _LightColor0.rgb * diffuseColor;
-
- fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
- fixed3 halfDir = normalize(worldNormal + worldLightDir);
- fixed3 specular = _LightColor0.rgb * _Specular.rgb *
- pow(max(0, dot(worldNormal, halfDir)), _Gloss);
-
- return fixed4(ambient + diffuse + specular, 1.0);
- }
- ENDCG
- }
- }
- }

注意:要把渐变纹理的Warp设为Clamp,以防止对纹理进行采样时由于浮点数精度而造成的问题

遮罩允许我们可以保护某些区域,使他们免于某些修改。更细腻的去控制光照。制作地形材质时混合多张图片,使用遮罩可以控制任何混合这些纹理。
使用遮罩的流程:
总之,使用遮罩可以让美术人员更加精准(像素级别)地控制模型表面的各种性质


- Shader "Shader/MaskTexture"
- {
- Properties
- {
- _Color ("Color", Color) = (1,1,1,1)
- _MainTex ("Texture", 2D) = "white" {}
- _BumpMap ("Normal Map", 2D) = "bump" {}//unity内置法线纹理bump
- _BumpScale ("Bump Scale", Float) = 1//控制凹凸程度
- _SpecularMask ("Specular Mask", 2D) = "white" {}//高光反射遮罩纹理
- _SpecularScale ("Specular Scale", Float) = 1.0//控制遮罩影响度的系数
- _Specular ("Sprcular", Color) = (1,1,1,1)
- _Gloss ("Gloss", Range(8.0, 256)) = 20
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
-
- Pass
- {
- Tags { "LightMode"="ForwardBase" }
-
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 tangent : TANGENT;
- float4 uv : TEXCOORD0;
- };
-
- struct v2f
- {
- float4 pos : SV_POSITION;
- float2 uv : TEXCOORD0;
- float3 lightDir : TEXCOORD1;
- float3 viewDir : TEXCOORD2;
- };
-
- fixed4 _Color;
- sampler2D _MainTex;
- float4 _MainTex_ST;
- sampler2D _BumpMap;
- float _BumpScale;
- sampler2D _SpecularMask;
- float _SpecularScale;
- fixed4 _Specular;
- float _Gloss;
-
- v2f vert (appdata v)
- {
- v2f o;
- o.pos = UnityObjectToClipPos(v.vertex);
-
- o.uv.xy = v.uv.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(_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;
-
- fixed3 diffuse = _LightColor0.rgb * albedo *
- max(0, dot(tangentNormal, tangentLightDir));
-
- fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
-
- fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
-
- fixed specular = _LightColor0.rgb * _Specular.rgb * pow(max(
- 0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
-
- return fixed4(ambient + diffuse + specular, 1.0);
- }
- ENDCG
- }
- }
- }
遮罩纹理不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。
通常我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。
例如,把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射指数部分存储在B通道,最后把自发光存储在A通道