5.1 纹理、采样器与 UV 坐标


5.1 纹理、采样器与 UV 坐标

本节摘要:纹理采样是一套三方协作——纹理对象存数据,采样器定规则,UV 坐标当地址。本节把这条链路上的每个角色讲清楚,走通从顶点 UV 属性到片元取色的完整旅程,并交代环绕模式应对越界坐标的四种策略。

学习目标

读完本节,你应当能够:说出纹理、采样器、UV 三者各自的职责;写出标准的片元采样代码;解释 UV 归一化设计与图像坐标系的差异;为不同场景选择正确的环绕模式。

纹理不是贴图:采样才是核心动作

先校准一个观念:纹理的本质是GPU 里的二维数组,图片只是它最常见的内容。数组元素可以是颜色、方向(法线贴图)、深度(阴影贴图)、甚至任意查表数据。访问这个数组的动作叫采样——输入坐标,输出插值后的数据值。着色器代码里它就是一行:

#version 330 core in vec2 vUv; // 插值来的纹理坐标 out vec4 FragColor; uniform sampler2D uTex; // 采样器:纹理与规则的打包入口 void main() { vec4 texColor = texture(uTex, vUv); // 核心:按 UV 取色 FragColor = texColor; }

sampler2D 这个类型名容易让人误会它就是纹理。准确说,采样器是"纹理对象 + 读取规则"的组合:读哪张纹理由绑定关系决定,怎么读(放大缩小用什么插值、坐标越界怎么办)由采样器状态决定。着色器代码只管喊一声"按 vUv 取色",规则细节全部外置——这意味着同一份着色器,换一套采样器状态就能得到完全不同的视觉效果,而不用重新编译。

图:UV 坐标与纹理单元的对应关系

图:UV 坐标与纹理单元的对应关系

UV 从顶点到片元的旅程

UV 坐标的设计哲学是归一化:无论纹理是 256 见方还是 4K 巨图,合法坐标永远是 0 到 1。这样 UV 与分辨率彻底解耦,换贴图不用改模型。建模软件里展开 UV 的过程(俗称分 UV),本质就是给模型表面每一点指定它该去贴图的哪个位置取色。

数据路径与普通属性别无二致:CPU 把每个顶点的 UV 作为属性上传,顶点着色器 out,插值器按片元位置加权平均,片元着色器 in 接收后用于采样。第 3 章的插值机制在这里全力运转——UV 平滑的三角形上采样结果自然连续,UV 突变的 UV 接缝处则可能出现一条细缝,这是贴图展开的固有代价,不是 bug。

两处坐标系陷阱提前排掉。其一,GLSL 的 UV 原点在左下,而绝大多数图片格式第一行在顶部,直接采样会出现上下颠倒,CPU 侧加载时翻转或着色器里用 vec2(1.0 - vUv.x, vUv.y) 类似手段矫正(横竖按需)。其二,UV 的横纵比例与屏幕像素比例无关:一张 1024x512 的贴图,UV 的 0.5 永远指向宽高中点,不是像素中点。

环绕模式:越界坐标的四种脾气

UV 超出 0 到 1 时怎么办?环绕模式(wrap mode)接管,四种策略各有用武之地:

模式 行为 典型场景
REPEAT 坐标取小数部分,图案无限平铺 砖墙、地板、水面
MIRRORED_REPEAT 越界部分镜像折叠,无缝感更强 需要对称平铺的图案
CLAMP_TO_EDGE 钳到边界,边缘像素向外拉伸 UI、不希望重复的贴图
CLAMP_TO_BORDER 越界取指定边框色 阴影贴图、调试可视化

注意模式配平铺的化学反应:REPEAT 加上 5.2 节要讲的 UV 缩放,一行代码就是无缝瓷砖墙;而 CLAMP_TO_EDGE 配 UV 缩放会拖出长长的边缘拉伸条纹——这不是错误,是模式的本性。采样器状态在 CPU 侧配置一次即可,着色器代码零改动,这也再次印证了"规则外置"的设计。

⚠️ 常见坑:纹理尺寸不是 2 的幂时,部分 OpenGL ES 版本会禁用 REPEAT 环绕与 mipmap。移动端项目给贴图定尺寸时,请优先选 256、512、1024、2048 这类 2 的幂。

把数据当纹理用

"纹理即数组"这个视角再推一步:任何适合按坐标查表的数据都能烘进纹理。几个真实用法帮你打开思路。

颜色查找表(LUT):把"输入灰度到输出颜色"的映射画成一条 256 像素的一维色带,片元程序按亮度值采样——调色、渐变映射、风格化色板全走这条路,代价是一次采样,收益是不用任何颜色公式。

曲线函数烘焙:噪声的分形叠加、非线性衰减、菲涅尔近似曲线,运行时算不如预烘焙——把函数值逐像素存进一张小图,运行时采样即是查表。经典的调色盘渐变、高光曲线、雾密度曲线都这么干。

数据打包:一张 RGBA 图可以同时装四路数据——RGB 孔雀色、A 通道装高光强度,是老游戏时代的经典节省术;现代引擎的金属粗糙度贴图同样是"一张图多通道复用"(第 6 章会用到)。

// LUT 调色:用亮度在一条色带上取色 float lum = dot(texture(uScene, vUv).rgb, vec3(0.2126, 0.7152, 0.0722)); vec3 tinted = texture(uRamp, vec2(lum, 0.5)).rgb; // 横轴是亮度,纵轴随便取中

这套思路的通用公式是:运行时算不起的,预计算成纹理。它也是游戏工业"烘焙"文化的起点——光照贴图、环境光遮蔽图,本质都是把昂贵的计算提前烤进纹理,运行时只做便宜采样。

三问三答

问:采样必须在片元着色器里做吗?顶点着色器能采样吗? 能,但有限制。顶点阶段采样没有 mip 选层所需的邻域信息,要手动指定层级(SampleLevel 类接口),且传统硬件对顶点采样的吞吐支持较弱。位移贴图(顶点级采样挪动位置)是少数正当场景,常规颜色采样都留在片元侧。

问:为什么有时候贴图采样出来是模糊的一团? 顺着链路排查:mip 选层过高(UV 变化率被误判,常见于极坐标类变换)、采样分辨率目标本身低、或者贴图导入时被压缩算法糊掉。第 5.3 节的层级可视化手法能直接看到选层情况。

问:一张模型可以同时绑多少张纹理? 硬件上限很宽裕(几十张),实际限制来自绑定方案与带宽。材质系统的设计课题之一就是"合并哪些贴图、哪些数据走常量"——这不是着色器语法问题,而是第 8 章的资源架构问题。

本节要点回顾

  • 三方协作:纹理存数据、采样器定规则、UV 当地址,着色器一行 texture 调用串联全部;
  • 归一化设计:UV 永远 0 到 1,与分辨率解耦;原点在左下,与图片行序相反;
  • 旅程即插值:UV 以顶点属性身份经历标准插值流程,接缝处的突变是展开的固有代价;
  • 环绕四模式:平铺选 REPEAT、对称选 MIRROR、拉伸选 CLAMP_EDGE、定制选 BORDER;
  • 尺寸守则:贴图尽量取 2 的幂,绕开移动端的功能限制。

链路打通了。下一节把 UV 当橡皮泥:平铺、滚动、旋转、极坐标,代码改造出四类常用动效。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U