纹理映射管线
Texturing Pipeline
纹理通过修改着色方程中使用的值(颜色、粗糙度、法线等)来高效表现表面材质的变化。通用纹理管线是:空间位置 → 投影函数得到纹理坐标 → 对应函数变换到纹理空间 → 取值 → 值变换 → 用于着色。本页还整理纹理坐标约定、纹理表示方式、程序纹理、纹理动画、材质贴图、alpha 贴图和纹理光源。
通用纹理管线
flowchart LR A["物体空间位置"] -->|"投影函数"| B["参数空间坐标 u,v"] B -->|"对应函数"| C["纹理空间位置"] C -->|"取值"| D["纹理值"] D -->|"值变换函数"| E["变换后的纹理值 → 修改材质或法线"]
每一步都给用户提供一种控制,但不必每一步都启用。书中砖墙例子:物体空间位置 (−2.3, 7.1, 88.2) 经正交投影得 (0.32, 0.29);纹理 256×256,乘以 256 得 (81.92, 74.24),取整得纹素 (81, 74),颜色 (0.9, 0.8, 0.7) 是 sRGB,转到线性得 (0.787, 0.604, 0.448)。起点通常用模型自身坐标系,使纹理随模型移动。
投影函数
- 建模软件让美术逐顶点定义 (u, v),可由球面、柱面、平面投影或网格展开 (unwrap) 初始化,并像编辑顶点一样编辑。实时渲染中投影通常在建模阶段完成并存在顶点里;有时在顶点或像素着色器里做,可提高精度并实现动画;环境贴图等有自己的逐像素投影。
- 柱面投影适合有自然轴的物体(旋转体),接近垂直于轴的表面会扭曲;平面投影像 X 光平行投射,适合贴花;与投影方向接近平行的表面严重扭曲,所以常把模型分解成近似平面的块,或用法线在六个平面投影方向中选择(接缝处需要混合)。
- 展开的目标是让每个多边形分到公平的纹理面积,同时尽量保持网格连通性——纹理分离处的边会出现采样伪影。
- 纹理坐标也可以是三维 或带齐次分量的 (像投影仪,用于投射舞台图案 gobo);方向型参数化最常见的是立方体贴图。一维纹理也有用:按海拔着色地形、用长线条加半透明纹理渲染雨、查找表。
对应函数
- 选取纹理的一部分;在顶点或像素着色器中用矩阵平移、旋转、缩放、切变或投影纹理。注意纹理变换的顺序与直觉相反,因为变换的是决定图像位置的空间,而非图像本身。
- 寻址模式(OpenGL 叫 wrapping mode,DirectX 叫 texture addressing mode),可按轴分别设置:
| 模式 | 行为 | 用途 |
|---|---|---|
| wrap / repeat / tile | 丢弃整数部分,重复 | 材质平铺,通常是默认 |
| mirror | 每隔一次翻转 | 边缘连续 |
| clamp (to edge) | 夹到 [0,1],边缘纹素延伸 | 避免双线性插值时采到对边 |
| border | 超出范围用边框色 | 单色表面上的贴花 |
| mirror once (DirectX) | 沿 0 镜像一次再 clamp | 对称贴花 |
- 平铺约三次重复后人眼就能看出图案。对策:与不平铺的纹理组合(如按地形类型、海拔、坡度组合多张纹理的商业地形系统);在着色器中随机重组图块(Wang tiles 等)。
- 最后一个隐式对应函数是乘以图像分辨率;用 [0,1] 坐标的好处是换不同分辨率的纹理无需改顶点数据。
纹理坐标约定与 API 差异
- DirectX 纹理左上角为 (0, 0),OpenGL 为左下角(y 翻转)。DirectX 10 起与 OpenGL 一致:纹素中心在 (0.5, 0.5)(取整式 flooring)。
- 依赖纹理读取有两种定义:移动端指像素着色器修改了传入的纹理坐标(哪怕只交换 u、v),不支持 OpenGL ES 3.0 的旧移动 GPU 在没有依赖读取时能预取、更快;旧桌面 GPU 指一张纹理的坐标依赖另一张纹理的结果。
- 纹理通常为 2 的幂 (POT);现代 GPU 支持任意尺寸 (NPOT),但一些旧移动 GPU 不支持 NPOT 的 mipmap。DirectX 12 最大 16384² 纹素。
纹理类型与表示
- 体积纹理: 访问,GPU 支持其 mipmap(层间是四线性插值,平均 16 个纹素,可能需要更高精度)。优点是跳过二维参数化,避免扭曲和接缝,能表现木头、大理石等"雕刻出来"的材质;缺点是存储大、滤波贵,用于表面纹理时大部分样本浪费,可用稀疏八叉树存储。
- 立方体贴图:六个面,用三维方向访问。取绝对值最大的分量选面,例如 (−3.2, 5.1, −8.4) 选 −z 面,另两个分量除以 8.4 再映射到 [0,1] 得 ≈ (0.31, 0.80)。主要用于环境贴图。
- 纹理图集:多张图合成一张以减少状态切换。问题:wrap/mirror 只作用于整张图;mipmap 时子图互相渗色——可先对每个子图单独生成 mipmap 并用 2 的幂尺寸。Ptex 让细分曲面每个四边形有独立小纹理,避免 UV 和接缝问题。
- 纹理数组:同尺寸、格式、mipmap、MSAA 设置,用索引访问,完全避免图集的 mipmap 和寻址问题,可比逐个绑定快 5 倍。
- 无绑定纹理:每张纹理由 64 位句柄引用,数量无上限,可通过 uniform、varying、其他纹理或 SSBO 传递;应用需保证纹理驻留 GPU;避免驱动的绑定开销。
程序纹理
- 由函数求值而非查图。实时渲染中图像纹理远更常见,因为纹理硬件极其高效;但 GPU 发展趋势是计算更便宜、访存相对更贵,程序纹理用得越来越多。
- 体积纹理存储代价高,特别适合程序生成。最常见的是噪声函数:在 2 的幂频率(octave)上采样并加权求和,称为 turbulence 函数。也有着色器中无查表计算经典噪声和 simplex 噪声的方法、避免混叠的 wavelet 噪声。
- cellular 纹理:计算到散布特征点的距离,可得细胞、石板、蜥蜴皮等图案。物理模拟(水波、裂纹扩展)也可作为程序纹理。
- 抗锯齿:无法用 mipmap 预计算;但作者了解内容,例如叠加噪声时已知各频率,可以直接去掉会混叠的频率,反而更省。
纹理动画与材质贴图
- 视频可作纹理;逐帧改变纹理坐标,例如瀑布沿 v 方向流动,每帧从 v 中减去一个量;对纹理坐标施加矩阵实现缩放、旋转、切变;混合纹理(大理石雕像渐变为血肉)。
- 材质贴图:纹理可替换、乘以或以其他方式修改任何材质参数,最常见的是反照率/漫反射颜色贴图。纹理也可控制着色流程,例如用一张锈迹贴图决定在哪些区域执行"锈"的着色代码。
- 颜色这类输入与输出呈线性关系,用标准过滤即可;粗糙度、凹凸等非线性输入需要更小心(9.13 节)。
Alpha 贴图
- 贴花:alpha 为 0 的纹素不影响表面,通常用 clamp 与透明边框只贴一份。镂空:在矩形上画出复杂轮廓的灌木;两个互相垂直的矩形组成"十字树",从地面看效果不错,从上方看会穿帮。
- alpha 混合需要从后往前画,而十字树根本不存在正确顺序,成千上万的草叶也不可能逐个排序。
- alpha 测试:
if (texture.a < alphaThreshold) discard;,可任意顺序渲染。至少应丢弃 alpha 为 0 的片元(省着色与合并开销,避免错误写深度)。镂空常用 0.5 或更高的阈值并完全不混合,但只有两级透明度。也可以分两遍:实心部分写深度,半透明部分不写。 - mipmap 让 alpha 测试物体变瘦:一维 alpha (0, 1, 1, 0) 平均后得 (0.5, 0.5),阈值 0.75 时下一级全部被丢弃。对策:
- Castaño:为每级 mip 计算覆盖率 ,二分查找阈值 使 等于 ,再把该级 alpha 乘以 (NVIDIA 纹理工具支持;《The Witness》中使用);Golus 的变体不改 mipmap,在着色器中随 mip 级别放大 alpha。
- Wyman & McGuire:
if (texture.a < random()) discard;,平均意义上正确,实际用哈希函数代替随机数以避免噪声,近处淡出该效果,最好配合 TAA。
- alpha 测试在放大时会出现波纹,可把 alpha 预计算为距离场。
- alpha to coverage:把 alpha 转成像素内被覆盖的样本数,例如 75% 不透明覆盖 4 个样本中的 3 个;适合重叠的草叶,无需排序。但两个相同 alpha 的片元用相同子样本图案,会完全遮挡而不是混合。
- 双线性插值与预乘:(255,0,0,255) 与 (0,255,0,2) 的中点直接插值得 (127,127,0,128),相当于预乘后的黄色;先预乘为 (0,2,0,2) 再插值得 (127,1,0,128) 才正确。PNG 等格式不预乘、GPU 又在着色器之前做双线性插值,所以默认会出现黑边。常用对策:预处理镂空图像,把透明纹素填成附近不透明纹素的颜色(所有透明区域都要填,mip 级别才不出问题);生成带 alpha 的 mipmap 时用预乘值。
纹理光源 (RTR4 6.9)
- 限定在锥体或视锥内的光可用投影纹理调制强度,实现形状聚光、图案光、"幻灯片投影",称为 gobo 或 cookie 光。
- 全方向的光用立方体贴图调制;一维纹理定义任意距离衰减,配合二维角度衰减图可得复杂体积光图案;三维纹理控制衰减可得任意作用体积(如光束),三轴对称时镜像到八个象限可省 8 倍内存。
- 美术只需编辑纹理即可控制照明。
GEA 的视角:纹理的用途、坐标与寻址(卷 II 11.3.14.1–11.3.14.3、11.4.9.1)
纹理的用途
- 三角形很大时逐顶点指定表面属性太粗,线性插值也不总是想要的效果,于是用纹理。纹理的像素叫纹素 (texel);纹理不一定贴到网格上,也可以当独立的数据表用(卷 II 11.3.14)。
- 最常见的是漫反射贴图(反照率贴图);参与光照的还有法线贴图、光泽/高光贴图、环境贴图、光照贴图、阴影贴图等。纹理也可编码几何细节(凹凸贴图、地形高度图)或额外数据(如 Perlin 噪声)(卷 II 11.3.14.1)。
- 运行时生成:顽皮狗引擎用粒子系统模拟汗珠或血迹在角色身上流下,把轨迹渲染到离屏纹理,再当遮罩显示皮肤上的湿润或血迹。
- 一维与三维纹理:一维纹理可存复杂函数的采样、颜色映射表等查找表 (LUT),例如做调色:用帧缓冲颜色作索引查新颜色,实现《辛德勒的名单》红衣女孩式的局部彩色或漂白效果(见 色调映射)。三维纹理像一叠二维纹理,例如大理石球被任意平面切开时,切面纹理处处连续。
纹理坐标与寻址模式
- 纹理空间中坐标 归一化,书中写道它"总是从左下角的 (0,0) 到右上角的 (1,1)",这样同一套坐标与纹理尺寸无关;每个顶点指定一对 就把三角形映射到纹理平面上(卷 II 11.3.14.2)。
- 坐标可以超出 [0,1],由寻址模式处理:Wrap(各方向重复)、Mirror(奇数倍时镜像)、Clamp(延伸边缘纹素颜色)、Border color(用户指定颜色)(卷 II 11.3.14.3)。寻址模式、边框颜色、过滤方式和 LOD 偏移都属于纹理采样器的配置(卷 II 11.4.9.2)。
- 注意:书中对 Wrap 的表述是" 与 等价(、 为任意整数)",按字面是乘法,与同一条里"纹理在各方向重复"的描述对不上(重复对应的是整数平移 )。
原书问题(11.3.14.3):Wrap 的正确表述是 与 等价,即只取坐标的小数部分;D3D 对
D3D11_TEXTURE_ADDRESS_WRAP的定义就是"在每个整数 (u,v) 交界处平铺纹理"(MS Learn "D3D11_TEXTURE_ADDRESS_MODE enumeration")。
与 RTR4 的分歧
| 话题 | RTR4(本页「纹理坐标约定与 API 差异」) | GEA 卷 II |
|---|---|---|
| 纹理原点 | DirectX 左上角为 (0,0),OpenGL 为左下角 | 纹理坐标"总是"从左下角 (0,0) 到右上角 (1,1),未区分 API(11.3.14.2) |
| 纹理尺寸 | 通常为 2 的幂,但现代 GPU 支持任意尺寸 (NPOT),一些旧移动 GPU 不支持 NPOT 的 mipmap | 纹理尺寸"在大多数图形硬件上被限制为 2 的幂"(11.3.14),带 mipmap 的二维纹理尺寸"被限制为 2 的幂"(11.4.9.1) |
| 最大尺寸 | DirectX 12 最大 16384² | 某些 GPU 和 DirectX 特性级别支持到 16,384 × 16,384;9_3 为 4,096²、10_1 为 8,192²(11.3.14、11.3.14.8) |
前两项说法不同,并列记录;最大尺寸两书一致。
原书问题(11.3.14、11.3.14.2):这两项上 GEA 的说法与 API 规范不符,RTR4 正确。① 非 2 的幂 (NPOT) 纹理:Direct3D 特性级别 10_0 起完全支持任意尺寸(含 mipmap 和 wrap),只有 9_x 级别是"有条件支持"(MS Learn "Direct3D feature levels" 特性表中 Nonpowers-of-2 一行);OpenGL 2.0 起核心也支持 NPOT。16,384² 也不是"某些"级别才有,而是特性级别 11_0 及以上的必备上限。② 纹理坐标原点:Direct3D 的纹理空间 (0,0) 在左上角、v 向下(MS Learn "Texture coordinate system (Direct3D 9)" / D3D11 同),OpenGL 的 (0,0) 对应纹理数据的第一行,习惯上画在左下角,并非"总是"左下。
PBRT 的视角:纹理坐标生成与纹理组合(PBRT 4e 10.2–10.4)
映射 (Mapping)
pbrt 用 表示纹理坐标,以区别于表面自身的 参数化。TextureMapping2D::Map() 只接收精简的 TextureEvalContext(第 4 版新增,为了 GPU 只读需要的字段),返回 及其对屏幕 的导数(用于过滤,见 光线微分与纹理足迹)。导数一律用链式法则:
| 映射 | 公式 | 导数 / 备注 |
|---|---|---|
| UV | 等 | |
| 球面 | ,,;附带变换可任意摆放映射球 | |
| 柱面 | 不限于 ; | |
| 平面 | ||
3D(PointTransformMapping) | 对渲染空间点做线性变换(常变回物体空间) | 导数同样变换 |
自行验证:对球面映射逐项求偏导,与书中式 (10.10) 一致(球面坐标见 球面几何与方向参数化)。
纹理接口与组合(10.3)
- 只有两类纹理:
FloatTexture(14 种实现)和SpectrumTexture;后者不返回完整光谱,而是只在当前采样的几个波长上求值(见 光谱渲染)。 - 常量纹理让所有材质参数都能是纹理,免去"有纹理/无纹理"两套材质。
- Scale:两纹理相乘,scale 为 0 时跳过另一纹理。两个带限函数的积仍带限,但最高频率可能是两者之和,所以各自抗锯齿后乘积仍可能走样;最常见的用法是乘常数,此时无碍。
- Mix:按
amt线性插值(不强制在 ,可外插),只在需要时求值;DirectionMix 按法线与给定方向的点积混合(如杂志封面和书脊用不同平面投影)。组合纹理都不额外做抗锯齿。
图像纹理(10.4.1–10.4.2)
- RGB 转光谱要区分是否为反射率(无界 RGB 与反射率 RGB 的转换不同);无色彩空间的图像假定各通道相同。可选
scale和invert( 后ClampZero),虽然用 Scale/Mix 纹理也能实现,但内建在 GPU 上更快。 - 坐标翻转:pbrt 的
Image以左上角为 ,图像纹理以左下角为 ,所以查表前翻转 。
与上文(RTR4、GEA)的关系
- RTR4 的"投影函数 → 对应函数"管线与 pbrt 的 Mapping 一一对应;实时渲染多在建模时把投影烘焙进顶点 UV,pbrt 则在着色时按需计算并给出导数。
- 坐标原点:上文 GEA 说纹理坐标"总是"左下角为 ,RTR4 指出 DirectX 是左上角。pbrt 同时存在两种约定(图像存储左上、纹理坐标左下),在查表处显式翻转,与 RTR4 "两种约定都存在"的说法一致。
原书问题(10.2.2,已解码网页 SVG)
- 式 (10.8) 球面映射的 写作 。分母应为 ;写成平方和的"范数"等于 ,除非 否则 的参数不对。紧接着的式 (10.10) 的导数是按正确的 求出的。
- 10.2.4 说平面映射取"从点到原点的向量"与 点积,公式中的 是从原点指向点(措辞问题)。
出处
- realtime_rendering_4ed.pdf 第 6 章 Texturing
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.3.14.1–11.3.14.3、11.4.9 节
- PBRT 4e source excerpt: chapter 10 (Textures and Materials)
- Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)第 10 章 10.1–10.5 节,pbr-book.org/4ed/Textures_and_Materials