Unity3D Shader UV 与纹理采样

Unity3D Shader UV 与纹理采样

Unity3D Shader UV 与纹理采样

前言

前面我们已经知道:

  • 顶点着色器负责处理顶点
  • 片段着色器负责决定像素颜色

但如果一个 Shader 只能返回纯色,那能做的效果其实非常有限。

真正让材质丰富起来的关键之一,就是:

纹理采样(Texture Sampling)

而纹理采样能成立的前提,就是 UV 坐标

这一篇,我们就来彻底搞清楚:

  • UV 是什么
  • Shader 中如何采样贴图
  • _MainTex_ST 是什么
  • TRANSFORM_TEX 为什么这么常见

本文示例基于 Unity Built-in Render Pipeline

什么是 UV

UV 可以理解为:

模型表面上每个点,对应纹理哪一个位置。

它本质上是一组二维坐标:

1
(u, v)

通常范围在:

1
0 ~ 1

例如:

  • (0,0) 表示纹理左下角
  • (1,1) 表示纹理右上角

也就是说:

模型不是直接“知道”自己要贴哪张图,
而是通过 UV 告诉 Shader:

我身上的这个点,应该去纹理的哪个位置取颜色。

为什么叫 UV,不叫 XY

因为:

  • x y z 已经常用来表示空间坐标
  • 所以纹理坐标通常用 u v 区分

也可以简单理解为:

  • x y:模型在空间中的位置
  • u v:模型在贴图上的位置

Mesh 中的 UV 数据在哪里

和顶点位置、法线一样,UV 也是 Mesh 自带的数据之一。

在 Shader 中,通常这样接收:

1
2
3
4
5
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

这里的:

1
float2 uv : TEXCOORD0;

就表示从 Mesh 中读取第一套 UV。

最基础的纹理采样流程

一个最基础的贴图 Shader,通常分三步:

  1. Properties 里声明纹理属性
  2. 在顶点着色器中传递 UV
  3. 在片段着色器中用 UV 去采样纹理

最小示例如下:

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
Shader "Custom/SimpleTexture"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
}

SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

sampler2D _MainTex;

v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}

fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}

代码解读

1 声明一张 2D 纹理

1
_MainTex ("Main Texture", 2D) = "white" {}

含义是:

  • _MainTex:Shader 中使用的变量名
  • "Main Texture":Inspector 面板显示名称
  • 2D:二维纹理
  • "white" {}:默认给一张白色贴图

在 CG/HLSL 中要声明成:

1
sampler2D _MainTex;

2 顶点着色器传递 UV

1
o.uv = v.uv;

顶点着色器把 Mesh 里的 UV 带到 v2f
再传给片段着色器。

3 片段着色器采样纹理

1
tex2D(_MainTex, i.uv)

这行代码的意思是:

_MainTex 这张纹理上,用 i.uv 这个坐标取一个颜色。

sampler2Dtex2D 是什么关系

可以简单这样理解:

  • sampler2D _MainTex:表示“有一张可采样的 2D 纹理”
  • tex2D(_MainTex, uv):表示“从这张纹理的 uv 位置取样”

就像:

  • _MainTex 是一本地图
  • uv 是地图上的位置
  • tex2D 是去这个位置读颜色

为什么有的贴图会重复平铺

如果 UV 超出了 0~1 的范围,纹理如何显示,就取决于纹理的 Wrap Mode。

最常见的是:

  • Repeat:重复平铺
  • Clamp:超过边界后固定在边缘颜色

例如:

1
float2 uv = i.uv * 2;

这就意味着:

  • U 方向走了两遍
  • V 方向走了两遍

结果通常就是贴图被平铺了 2 次。

_MainTex_ST 是什么

经常会在 Unity Shader 里看到:

1
float4 _MainTex_ST;

这是 Unity 自动为 2D 纹理属性生成的一个变量。

它保存了材质面板里:

  • Tiling(平铺)
  • Offset(偏移)

的数据。

格式可以理解为:

1
2
_MainTex_ST.xy  // Tiling
_MainTex_ST.zw // Offset

也就是说:

1
uv * tiling + offset

TRANSFORM_TEX 做了什么

Unity 中最常见的写法:

1
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

它本质上等价于:

1
o.uv = v.uv * _MainTex_ST.xy + _MainTex_ST.zw;

也就是:

  • 先乘平铺
  • 再加偏移

所以如果希望材质面板中的 Tiling / Offset 生效,
就不要直接写:

1
o.uv = v.uv;

而应该写:

1
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

一个更完整的贴图着色 Shader

下面我们加一个颜色叠乘:

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
Shader "Custom/TextureTint"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Tint ("Tint Color", Color) = (1,1,1,1)
}

SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Tint;

v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col * _Tint;
}
ENDCG
}
}
}

这个 Shader 做了什么

  • 使用 UV 对纹理进行采样
  • 支持材质面板中的 Tiling / Offset
  • 最后把贴图颜色和 _Tint 相乘

所以可以:

  • 换不同纹理
  • 调整贴图平铺
  • 给贴图整体染色

这就是最常用的基础贴图 Shader 模板。

UV 不只是拿来贴图

很多初学者会以为 UV 只能用于采样贴图。

其实不是。

UV 还是一个非常方便的二维参数,可以拿来做很多效果:

  • 流光滚动
  • 边缘渐变
  • 遮罩控制
  • 溶解范围
  • 水波扰动

例如:

1
float glow = i.uv.x;

这就相当于:

让模型从左到右有一个 0~1 的渐变值。

常见问题

1 为什么贴图显示不对

先检查这几个点:

  • Mesh 有没有正确 UV
  • 顶点着色器有没有把 UV 传给 v2f
  • 片段着色器是否用正确 UV 采样
  • 是否忘了写 _MainTex_ST
  • 是否忘了使用 TRANSFORM_TEX

2 为什么材质面板调 Tiling / Offset 没反应

通常是因为写了:

1
o.uv = v.uv;

而没有使用:

1
2
float4 _MainTex_ST;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

3 为什么贴图看起来被拉伸了

这通常不是 Shader 问题,而是:

  • 模型 UV 展开不合理
  • 模型比例变化太大

Shader 只是按 UV 去取颜色,
真正决定“纹理如何铺在模型表面”的,是建模时的 UV 展开。

总结

这一篇最重要的几个点是:

  • UV 是模型表面到纹理位置的映射
  • sampler2D 用来声明 2D 纹理
  • tex2D 用 UV 从纹理中采样颜色
  • _MainTex_ST 保存 Tiling 和 Offset
  • TRANSFORM_TEX 会自动应用平铺与偏移

可以先记住下面这套最常用模板:

1
2
3
4
5
float2 uv : TEXCOORD0;
sampler2D _MainTex;
float4 _MainTex_ST;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
fixed4 col = tex2D(_MainTex, i.uv);

后面写:

  • 流动贴图
  • 溶解效果
  • 遮罩混合
  • 扭曲特效

基本都离不开这套东西。


Unity3D Shader UV 与纹理采样
http://weikunou.github.io/2026/04/26/unity-shader-uv-texture-sampling/
作者
Awake
发布于
2026年4月26日
许可协议