1. 纹理映射
每一张纹理可以看作拥有一个属于自己的2D坐标空间,其横轴用U表示,纵轴用V表示,因此也称为UV坐标空间。
UV空间的坐标范围为[0,0]到[1,1],在Unity中,UV空间也是从左下到右上,即纹理的左下角对应的UV坐标为[0,0],纹理右上角对应的UV坐标为[1,1]
在美术导出模型资源时,会通过UV展开将模型每个顶点对应的UV坐标存储到顶点信息中。渲染时通过顶点(或片元)的UV坐标映射到纹理上的某一点,这样就可以通过采样每一个顶点(或片元)对应的纹理上的颜色将纹理显示出来。
2. 纹理基础设置
以Unity 2021为例,一张纹理的Inspector面板大致如下
其中红框部分是本节主要关注的部分
-
Generate Mip Maps
- 勾选后会提前进行滤波,生成不同采样等级的Mip Map纹理
- 渲染时根据屏幕像素对应的纹理范围从对应等级的Mip Map纹理中进行采样
- 由于生成了新的纹理,因此会占用更多的内存空间
-
Wrap Mode
UV坐标空间自身的范围虽然只有0到1,但实际采样时,顶点映射的UV坐标可能超过这个范围,WrapMode属性决定了采样的UV坐标超过[0, 1]范围时的处理方式- Repeat:重复采样,即超过范围时舍弃整数部分只按小数部分采样,比如(1.1, 1.2)实际会按照(0.1, 0.2)采样
- Clamp:超过[0, 1]的部分延用相应边缘的值(0或者1)
-
Filter Mode
决定纹理采样的滤波方式- Point:就近点采样
- Bilinear:双线性插值
- Trilinear:三线性插值
关于MipMap和滤波的原理可以参照【Unity Shader入门精要 第7章】基础纹理补充内容:MipMap原理
3. 在Shader中使用纹理
- 创建Chapter_7_BaseTexture_Mat 作为测试材质
- 创建Chapter_7_BaseTexture作为测试shader,并赋给Chapter_7_BaseTexture_Mat 材质
- 场景中创建胶囊体,将Chapter_7_BaseTexture_Mat 材质赋给胶囊体
- 场景中添加一盏平行光,并调整平行光角度
在Shader中,我们通过对纹理采样得到的颜色来代替之前固定的漫反射颜色,并在片元着色器中进行逐像素的光照计算,得到最终的效果:
- 在Properties中添加属性 _MainTex(“MainTex”, 2D) = “white”{} 用于设置纹理,此时选中胶囊体可在Inspector面板中看到如下纹理设置选项
- Tiling:平铺数量,可以理解为反向的缩放度,值越大单位空间平铺数量越多,相当于对纹理缩小,反之相当与放大
- Offset:偏移
- 在CG代码段中添加与属性同名的变量 _MainTex来使用面板中设置的纹理,其类型为 sampler2D
- 顶点着色器中输入的uv坐标是模型导出时顶点对应的uv坐标,为了能够得到正确的纹理采样结果,除了考虑顶点自身的uv坐标外,还需要考虑面板中对要采样的纹理设置的缩放和偏移,这部分信息可通过在Shader中声明 XXX_ST 的变量获得,其中XXX为纹理属性的名字,当前Shader中也就是 _MainTex_ST,该变量类型为float4,其中xy表示缩放,zw表示平移
- 通过 input.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw 即可得到实际采样使用的uv坐标,也可以通过内置的TRANSFORM_TEX(_MainTex, input.uv)方法获得,此时引擎会自己使用名为 _MainTex_ST 的变量进行上述计算,因此即使使用内置方法计算uv也需要声明_ST变量
- 最后在着色器中通过 tex2D(_MainTex, i.uv) 方法即可对纹理进行采样获得颜色,我们以该颜色作为物体自身的漫反射颜色系数带入漫反射光照计算
Shader如下:
Shader "MyShader/Chapter_7/Chapter_7_BaseTexture"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(1.0, 256.0)) = 10
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
half _Gloss;
v2f vert(a2v i)
{
v2f o;
o.vertex = UnityObjectToClipPos(i.vertex);
o.worldPos = mul(unity_ObjectToWorld, i.vertex);
o.worldNormal = mul(i.normal, (float3x3)unity_WorldToObject);
o.uv = TRANSFORM_TEX(i.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
float3 _worldNormal = normalize(i.worldNormal);
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed4 _albedo = tex2D(_MainTex, i.uv);
fixed3 _diffuse = _LightColor0.rgb * _albedo.rgb * saturate(dot(_worldNormal, _worldLight));
float3 _worldRefl = normalize(reflect(-_worldLight, _worldNormal));
float3 _worldView = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
fixed3 _specular = _LightColor0.rgb * _Specular.xyz * pow( saturate(dot(_worldRefl, _worldView)),_Gloss);
return fixed4(_ambient + _diffuse + _specular, 1);
}
ENDCG
}
}
}
效果如下: