【DX12龙书】第九章 纹理贴图
本章是 Direct3D 12 实时渲染的核心章节,系统讲解纹理映射(Texture Mapping)技术—— 通过将 2D 图像数据映射到 3D 三角形表面,实现逐像素的材质细节控制,彻底突破了逐物体 / 逐顶点材质的细节限制,大幅提升 3D 场景的真实感与丰富度。本章内容覆盖从纹理基础概念、坐标系统、资源格式、D3D12 资源创建与管线绑定、纹理过滤与寻址模式、采样器配置、HLSL 着色器采样实现,到纹理变换、纹理动画、完整工程 Demo 的全流程,是上一章光照系统的关键延伸,也是后续法线贴图、PBR 渲染等高级技术的基础。
各小节内容概况
9.1 纹理与资源的回顾
-
纹理的通用本质:纹理是基于
ID3D12Resource接口的通用数据容器,并非仅用于存储颜色图像;可分为 1D/2D/3D 纹理,分别对应一维数组、二维图像、三维体数据,除了颜色,还可存储法线向量、深度值等任意 GPU 可读取的数据。 -
纹理与缓冲区的核心差异:纹理支持 mipmap 链、GPU 硬件过滤、多重采样等特殊操作,因此必须使用 DXGI_FORMAT 限定数据格式;而缓冲区可存储任意格式数据,无上述硬件特性。
-
常用纹理 DXGI 格式分类:
-
无压缩格式:
DXGI_FORMAT_R8G8B8A8_UNORM(最常用的 8 位 RGBA 格式)、DXGI_FORMAT_R16G16B16A16_FLOAT(HDR 格式)、DXGI_FORMAT_R32G32B32_FLOAT(高精度向量存储); -
无类型格式:如
DXGI_FORMAT_R8G8B8A8_TYPELESS,仅预留内存,可在创建视图时重新指定数据类型,实现同一块内存的多用途类型重解释。但硬件无法优化运行时访问,性能非最优;
-
-
纹理的多管线绑定能力:同一块纹理资源可创建不同类型的视图(RTV 渲染目标视图、SRV 着色器资源视图、DSV 深度模板视图),实现渲染到纹理(Render-to-Texture)等高级效果,但不可同时作为渲染目标和着色器资源使用。
9.2 纹理坐标
-
纹理坐标系统规范:
-
采用 (u, v) 二维坐标,左上角为原点,u 轴沿纹理水平向右,v 轴沿纹理垂直向下;
-
坐标范围归一化到
[0, 1],与纹理分辨率无关,例如 (0.5, 0.5) 永远对应纹理中心,无论纹理是 256x256 还是 2048x2048。
-
-
纹理 - 模型映射规则:为 3D 三角形的每个顶点指定对应的纹理坐标,光栅化阶段,会用与顶点位置相同的重心坐标 (s, t),对顶点纹理坐标进行线性插值,得到纹理坐标\((u,v)=q=q_0+s(q_1-q_0)+t(q_2-q_0)\)。
-
纹理映射的注意事项:3D 三角形与纹理三角形的形状差异过大会导致纹理拉伸、畸变,需尽量最小化畸变;并非必须将整张纹理映射到模型,可仅映射纹理的局部区域。
-
纹理图集(Texture Atlas):将多张无关的子纹理合并到一张大纹理中,通过不同的纹理坐标映射到不同模型,减少绘制调用次数,提升渲染性能。
9.3 纹理数据源
-
DDS(DirectDraw Surface format) 格式的核心优势:专为 GPU 与 3D 图形设计,是实时渲染的首选格式,原生支持 mipmap、GPU 硬件压缩格式、纹理数组、立方体贴图、体积纹理等 3D 图形必备特性,可直接被 GPU 识别,无需运行时格式转换。
常用像素格式:
-
低动态范围(LDR):
DXGI_FORMAT_B8G8R8A8_UNORM、DXGI_FORMAT_B8G8R8X8_UNORM; -
高动态范围(HDR):
DXGI_FORMAT_R16G16B16A16_FLOAT。
-
-
BC系列压缩:为了缓解显存紧张,Direct3D支持BC1~7的压缩格式,这些格式,能大幅降低纹理的显存占用与硬盘占用,且可被 GPU 原生实时解压,无 CPU 解压开销
压缩格式使用限制:
-
压缩纹理仅可作为着色器阶段的输入,不可作为渲染目标;
-
块压缩以 4x4 像素为单位处理,因此压缩纹理的宽高必须是 4 的整数倍。
-
-
DDS 相关工具:
-
NVIDIA Photoshop 插件:支持直接将 PS 工程导出为 DDS 格式,可自定义 DXGI 格式、生成 mipmap 链;
-
微软 texconv 命令行工具:支持批量转换图片、调整尺寸、修改像素格式、生成 mipmap 链,示例命令:
texconv -m 10 -f BC3_UNORM bricks.bmp
生成 10 级 mipmap,BC3 压缩格式 -
微软 texassemble 工具:用于创建存储纹理数组、立方体贴图、体积纹理的 DDS 文件;
-
Visual Studio 2015 及以上版本:内置 DDS 图像编辑器,支持查看 mipmap 层级、修改 DDS 格式、查看颜色通道。
-
9.4 创建以及启用纹理
纹理作为SRV(Shader Resource View),供着色器使用(采样)。纹理使用的流程:加载 DDS 文件创建纹理资源 → 创建 SRV 描述符堆 → 生成纹理的 SRV 描述符 → 绘制时将 SRV 绑定到根签名 → 着色器中采样纹理。
-
加载DDS创建纹理:使用
DirectX::CreateDDSTextureFromFile12函数,从硬盘加载 DDS 文件,自动创建纹理资源与上传堆,完成纹理数据从 CPU 到 GPU 的拷贝。
封装 Texture 结构体,包含纹理名称、文件路径、纹理资源指针、上传堆指针。
通过无序映射表统一管理所有纹理,避免重复加载。 -
SRV描述符堆创建:通过
ID3D12Device::CreateDescriptorHeap创建描述符堆。堆类型为D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;堆可被着色器访,堆标志为D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE -
创建SRV描述符:通过填充
D3D12_SHADER_RESOURCE_VIEW_DESC结构体,其的核心参数包括:-
Format:纹理数据格式,非无类型格式需与纹理资源创建时的格式一致;无类型格式需在此指定具体的解读格式; -
ViewDimension:纹理维度,2D 纹理指定为D3D12_SRV_DIMENSION_TEXTURE2D; -
Shader4ComponentMapping:纹理采样时的分量重映射,默认使用D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,不修改分量顺序,保持RGBA; -
Texture2D.MostDetailedMip:可访问的最精细 mipmap 层级索引,通常为 0; -
Texture2D.MipLevels:可访问的 mipmap 层级数量,-1 表示访问所有层级; -
ResourceMinLODClamp:限制可访问的最小 mipmap 层级,0.0 表示无限制。
调用
ID3D12Device::CreateShaderResourceView创建 SRV,写入描述符堆。通过
CD3DX12_CPU_DESCRIPTOR_HANDLE::Offset方法,按描述符步长偏移句柄,依次填充多个纹理的 SRV 到堆中。 -
-
将纹理绑定到管线:结合龙书代码现有框架,通过材质系统来使用纹理。具体为:
-
在 Material 结构体中新增
int DiffuseSrvHeapIndex成员,存储该材质对应的纹理 SRV 在描述符堆中的索引,将材质与纹理的关联起来; -
在像素着色器中,将纹理采样得到的漫反射反照率,与材质常量缓冲区中的漫反射反照率做分量级乘法得出材质最终的漫反射率;
-
纹理资源的描述符绑定到管线(App::DrawRenderItems):
-
绘制前,通过 SRV 索引计算出对应描述符的 GPU 虚拟地址;
-
调用
SetGraphicsRootDescriptorTable,将 SRV 描述符表绑定到根签名对应的槽位(SRV是绑定为根描述符);
之后,管线绘制时,像素着色器即可通过寄存器(通过根签名关联根参数索引与寄存器编号)访问纹理资源;
-
-
纹理图集(Texture Altas)性能优化:纹理图集可将多个模型的纹理合并到一张大纹理中,实现多个模型的合批绘制,减少绘制调用开销。
-
9.5 过滤器
使用纹理着色时,需要对纹理采样。这时会遇到一个问题:需要着色的像素对应的纹理坐标,与纹理上的纹素(Texel)中心不重合时,它的采样就需要通过过滤算法(插值)计算出该坐标对应的颜色值,以解决纹理放大 / 缩小带来的锯齿、模糊、闪烁问题。
产生上述问题的根源在于,纹理的元素是连续图像的离散颜色采样点,而非有面积的矩形,插值是纹理采样的必备环节。
-
放大:当相机近距离观察表面,屏幕分辨率远高于纹理分辨率时,纹理采样需要放大。有两种算法
-
点过滤(Point Filtering / 最近邻采样):直接取纹理坐标最近的纹素颜色作为采样结果,无插值;效果为块化、锯齿感强,开销最低。
-
线性过滤(Linear Filtering / 双线性过滤):2D 场景下,取纹理坐标周围的 4 个相邻纹素,先在 u 方向做两次 1D 线性插值,再在 v 方向做一次 1D 线性插值,得到最终颜色;效果平滑,无明显锯齿,是最常用的基础过滤算法,开销略高于点过滤。
-
-
缩小:当相机远距离观察表面,屏幕分辨率远低于纹理分辨率时,纹理采用需要缩小。这时需要使用mipmap技术,选取mipmap链上分辨率与屏幕匹配的层级进行采样:
-
纹理创建时预先生成 mipmap 链 —— 每一级 mipmap 都是上一级的 1/2 尺寸,通过下采样平均生成,直到 1x1 尺寸;运行时,GPU 自动选择与屏幕投影分辨率最匹配的 mipmap 层级进行采样,大幅降低缩小场景的锯齿、闪烁与采样开销。
-
内存开销上,完整的 mipmap 链仅比原始纹理多占用 1/3 的显存
-
为选取与屏幕匹配的 mipmap 层级,有两种过滤方法:
-
点过滤(Point Mip Filtering):选择与屏幕分辨率最匹配的单个 mipmap 层级,在该层级内做纹理放大的点 / 线性过滤;
-
线性过滤(Linear Mip Filtering / 三线性过滤):选择最匹配的两个相邻 mipmap 层级,分别在两个层级内做双线性过滤,再对两个结果做线性插值;效果更平滑,mipmap 层级切换无明显断层,开销略高于点 mip 过滤。
-
-
-
各向异性:当多边形表面法线与相机视线夹角很大时(例如观察地面、墙面的远处区域),纹理在屏幕上的投影发生拉伸畸变,线性 / 三线性过滤会导致严重的模糊,需要各向异性过滤修复这些畸变。
-
最大各向异性值可设置为 1-16,值越大质量越高,开销越大。
-
适用于存在大角度观察的表面,例如地面、水面、远处的墙面。
-
9.6 寻址模式
当纹理坐标超出 [0,1] 范围时,Direct3D 提供了 4 种寻址模式,以处理这些范围外的纹理采样。
| 寻址模式 | 枚举 | 核心效果 | 适用场景 |
|---|---|---|---|
| Wrap(重复) | D3D12_TEXTURE_ADDRESS_MODE_WRAP | 每到整数边界就重复整张纹理,是默认寻址模式 | 纹理平铺、地砖 / 墙面 / 草地等无缝纹理的重复铺设 |
| Mirror(镜像) | D3D12_TEXTURE_ADDRESS_MODE_MIRROR | 每到整数边界就镜像翻转纹理再重复 | 减少平铺纹理的重复感,对称纹理的平铺 |
| Clamp(钳位) | D3D12_TEXTURE_ADDRESS_MODE_CLAMP | 超出 [0,1] 的坐标,采样纹理最近边缘的颜色 | UI 纹理、避免纹理边缘的采样瑕疵 |
| Border Color(边框色) | D3D12_TEXTURE_ADDRESS_MODE_BORDER | 超出 [0,1] 的坐标,采用用户自定义的固定边框色 | 特殊渲染效果、阴影贴图 |
| Mirror Once(单次镜像) | D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE | 仅在 0 坐标处镜像一次,超出后钳位 | 特殊渲染场景 |
9.7 采样器对象
采样器对象封装了纹理采样的两个核心配置 —— 纹理过滤模式、纹理寻址模式,同时包含 mipmap 偏移、各向异性等级、边框色等辅助配置;一个应用通常需要多个采样器,实现不同的采样效果。
-
采样器对象由
D3D12_SAMPLER_DESC定义,它的参数包括:-
Filter:过滤模式,D3D12_FILTER枚举值,常用值包括:-
D3D12_FILTER_MIN_MAG_MIP_POINT:放大 / 缩小 /mipmap 均使用点过滤; -
D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT:放大 / 缩小使用双线性过滤,mipmap 使用点过滤; -
D3D12_FILTER_MIN_MAG_MIP_LINEAR:放大 / 缩小使用双线性过滤,mipmap 使用线性过滤(三线性过滤); -
D3D12_FILTER_ANISOTROPIC:各向异性过滤,放大 / 缩小 /mipmap 均使用各向异性算法。
-
-
AddressU/AddressV/AddressW:u/v/w 轴的寻址模式; -
MipLODBias:mipmap 层级的偏移值,正值会使用更低分辨率的 mipmap,负值会使用更高分辨率的 mipmap,通常设为 0.0f; -
MaxAnisotropy:最大各向异性等级,1-16,仅对各向异性过滤有效; -
ComparisonFunc:比较函数,用于阴影贴图等高级技术,通常设为D3D12_COMPARISON_FUNC_ALWAYS; -
BorderColor:边框色,仅对 Border Color 寻址模式有效; -
MinLOD/MaxLOD:限制可访问的 mipmap 层级范围,通常设为 0 和D3D12_FLOAT32_MAX。
-
-
采样器的创建方式有两种
-
采样器对象绑定描述符表
采样器与描述符表中的一个描述符范围(
D3D12_DESCRIPTOR_RANGE)绑定,需要:-
描述符表创建一个描述符范围,RangeType是
D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER。通过这个DescriptorRange,将采样器对象与寄存器关联起来。 -
通过
ID3D12Device::CreateDescriptorHeap创建一个采样器堆(Type为D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,Flag为SHADER_VISIBLE) -
填充
D3D12_SAMPLER_DESC,结合采样器堆的CPU句柄,调用ID3D12Device::CreateSampler,将采样器对象写入采样器堆 -
绘制时通过向
SetGraphicsRootDescriptorTable传入采样器的GPU句柄,将采样器描述符表绑定到根签名,供着色器使用。
-
-
静态采样器
Direct3D 提供的快捷方案,可直接在根签名中定义采样器,无需创建采样器堆、无需管理采样器描述符,配置简单、效率更高,完全满足绝大多数应用的需求。
可以在根签名初始化时,传入静态采样器描述(
D3D12_SAMPLER_DESC)的数组,即可完成全局配置,着色器中可直接通过寄存器访问。静态采样器的限制:
-
最多可定义 2032 个静态采样器,远超常规应用的需求;
-
边框色仅支持预定义的枚举值(不透明黑、透明黑、不透明白),不可自定义任意颜色;
-
需在定义时指定着色器寄存器、寄存器空间、着色器可见性。
本书 Demo 的静态采样器配置:预定义了 6 个常用静态采样器,覆盖所有常用的过滤 + 寻址组合,寄存器 s0-s5 分别对应:
-
s0:pointWrap(点过滤 + 重复寻址)
-
s1:pointClamp(点过滤 + 钳位寻址)
-
s2:linearWrap(线性过滤 + 重复寻址)
-
s3:linearClamp(线性过滤 + 钳位寻址)
-
s4:anisotropicWrap(各向异性过滤 + 重复寻址)
-
s5:anisotropicClamp(各向异性过滤 + 钳位寻址)
-
-
9.8 着色器中对纹理进行采样
-
HLSL 中纹理与采样器的定义:
-
纹理(SRV)对象:
Texture2D gDiffuseMap : register(t0);,纹理使用t开头的纹理寄存器,数字为寄存器槽位; -
采样器对象:
SamplerState gsamAnisotropicWrap : register(s4);,采样器使用s开头的采样器寄存器,数字为寄存器槽位。
-
-
纹理采样核心方法:
Texture2D::Sample,语法:gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC);-
第一个参数:采样器对象,决定采样的过滤与寻址行为;
-
第二个参数:像素的纹理坐标 (u, v),由顶点着色器输出、光栅化插值得到;
-
返回值:采样得到的 RGBA 颜色值,类型为 float4。
-
9.9 板条箱 DEMO
通过木箱立方体 Demo,演示了纹理映射的实现,从纹理坐标指定、纹理资源加载、SRV 创建,到 HLSL 着色器修改、管线绑定的全流程。
-
纹理坐标指定:每个顶点的纹理坐标与位置、法线一同存储,随顶点数据传入输入装配阶段。
-
纹理资源加载:应用初始化时,调用
CreateDDSTextureFromFile12加载木箱 DDS 纹理,存入纹理管理Map;遍历所有纹理,为每个纹理创建 SRV,填充到 SRV 描述符堆中;为材质设置DiffuseSrvHeapIndex,指向纹理 SRV 在堆中的索引。 -
设置SRV:遍历每个渲染项,通过材质的
DiffuseSrvHeapIndex,计算出对应 SRV 的 GPU 句柄,调用SetGraphicsRootDescriptorTable绑定到根签名,再执行绘制调用。 -
更新HLSL代码
-
顶点着色器修改:
-
顶点输入结构体新增
float2 TexC : TEXCOORD;成员; -
顶点输出结构体新增
float2 TexC : TEXCOORD;成员; -
新增纹理坐标变换逻辑,将输入纹理坐标经过纹理矩阵变换后,输出到像素着色器。
-
-
像素着色器修改:
-
采样纹理得到漫反射反照率,与常量缓冲区的漫反射反照率相乘;
-
将最终的漫反射反照率代入环境光、直接光的光照计算;
-
从纹理采样结果中提取 Alpha 通道,作为最终像素的 Alpha 值。
-
-
常量缓冲区新增:新增
gTexTransform和gMatTransform两个 4x4 矩阵,用于纹理坐标的变换。
-
9.10 纹理变换
纹理坐标是 2D 平面上的点,可像 3D 顶点一样,通过 4x4 矩阵进行平移、旋转、缩放变换,实现纹理的平铺、旋转、滚动效果。
-
变换方式:在顶点着色器中,将输入的 2D 纹理坐标扩充为 4D 向量
float4(vin.TexC, 0.0f, 1.0f),与纹理变换矩阵相乘,取结果的 xy 分量作为最终的纹理坐标。 -
代码设计:
-
gTexTransform:物体级别的纹理变换,例如为整个网格的纹理做统一缩放; -
gMatTransform:材质级别的纹理变换,例如水材质的纹理滚动动画,与物体无关。
-
-
纹理变换的常见应用场景:
-
缩放:将纹理坐标缩放 n 倍,使纹理在表面平铺 n×n 次,解决大表面的纹理放大问题;
-
平移:随时间逐帧平移纹理坐标,实现云、水、火焰的滚动动画;
-
旋转:随时间旋转纹理坐标,实现粒子特效、旋转贴图的效果。
-
9.11 带纹理的山川 DEMO
Demo 核心目标:为地形网格平铺草地纹理,解决大表面的纹理分辨率不足问题;为水面网格实现纹理随时间滚动的动画,提升水面的真实感。
-
生成栅格的纹理坐标:栅格作为mesh的坐标原点是在中心,纹理坐标原点是在左上角,所以对于 m×n 的网格,第 ij 个顶点的纹理坐标为\(u_{ij}=j·△u\)、\(v_{ij}=i·△v\),其中\(△u = \frac{1}{n-1}\)、\(△v = \frac{1}{m-1}\),实现将整张纹理完整映射到整个网格表面。
-
纹理铺设(Tiling):通过重复寻址模式将纹理铺设到更大的mesh上。即通过纹理变换矩阵,将纹理坐标缩放5倍,使纹理坐标范围从[0,1]变为[0,5]并配合 Wrap 寻址模式,实现草地纹理在地形上5×5次平铺。具体实现是在渲染项中设置
TexTransform为 X 轴 Y 轴缩放5倍的矩阵,顶点着色器中对纹理坐标做变换。 -
纹理动画:每帧更新水材质的纹理变换矩阵,在 u 轴和 v 轴分别叠加随时间增长的平移偏移量,配合 Wrap 寻址模式和无缝纹理,实现水面纹理的平滑滚动效果;当偏移量超过1.0时,重置为0,避免数值溢出。
-
代码实现:在
AnimateMaterials方法中,每帧根据时间增量更新平移偏移量,修改材质的MatTransform矩阵,更新材质常量缓冲区。 -
动画优化:每帧的平移增量很小,保证动画平滑;使用 Wrap 寻址模式,实现无缝循环滚动。
-
9.12 总结
-
纹理坐标用于定义与 3D 三角形对应的纹理三角形,实现纹理到模型的映射;
-
游戏开发中优先使用 DDS 格式,支持 GPU 原生压缩、mipmap 等 3D 图形特性,可通过 Photoshop 插件、texconv 工具生成;
-
通过
CreateDDSTextureFromFile12函数从硬盘加载 DDS 文件,创建纹理资源; -
纹理放大 / 缩小会导致采样畸变,通过 mipmap 技术与点、线性、各向异性三种过滤算法解决;
-
寻址模式定义了纹理坐标超出 [0,1] 范围时的采样行为,包括重复、镜像、钳位、边框色四种核心模式;
-
纹理坐标可通过矩阵进行缩放、旋转、平移变换,实现纹理平铺与动画效果。
9.13 EXERCISES
-
练习 1:修改木箱 Demo 的纹理坐标、寻址模式、过滤选项,复现本章的示例效果;
-
练习 2:手动创建每个 mipmap 层级颜色不同的 DDS 纹理,通过相机缩放观察 mipmap 层级切换,对比点 / 线性 mip 过滤的效果;
-
练习 3:实现多纹理混合,在像素着色器中对两张纹理做分量级乘法,实现火球特效;
-
练习 4:为火球纹理添加随时间旋转的动画效果;
-
练习 5:纹理坐标的数学推导,通过纹理坐标反推重心坐标,推导纹理空间的偏导数与几何意义;
-
练习 6:为上一章的立柱场景添加纹理,实现带纹理的立柱、地面、球体渲染。
核心知识点列表
-
基础概念:纹理是通用 GPU 数据容器,uv 坐标归一化 [0,1],v 轴向下,纹素是纹理的离散采样点。
-
资源格式:DDS 是实时渲染首选格式,BC 系列压缩可大幅降低显存占用,不同 BC 格式对应不同适用场景。
-
工程流程:加载 DDS→创建纹理资源→创建 SRV→配置采样器→绑定到根签名→HLSL 采样→融入光照计算。
-
纹理过滤:点过滤(最快、锯齿)、双线性过滤(平滑、常用)、三线性过滤(mipmap 无断层)、各向异性过滤(大角度无模糊、质量最高)。
-
寻址模式:Wrap(平铺)、Mirror(镜像)、Clamp(钳位)、Border(边框色),Wrap 是默认模式,用于无缝纹理平铺。
-
采样器:封装过滤 + 寻址模式,静态采样器无需堆管理,配置简单、效率高,是首选方案。
-
纹理变换:通过矩阵对 uv 坐标做缩放、平移、旋转,实现纹理平铺、滚动动画、旋转特效。
-
mipmap 技术:预生成逐级缩小的纹理链,解决纹理缩小的闪烁、锯齿问题,仅增加 1/3 显存开销,是必备优化手段。
-
着色器实现:HLSL 中通过
Texture2D定义纹理、SamplerState定义采样器,Sample方法执行采样,采样结果作为漫反射反照率代入光照方程。 -
性能优化:纹理图集减少绘制调用、BC 压缩降低显存占用、mipmap 降低采样开销、静态采样器提升管线绑定效率。
难点笔记
关于各向异性
学习9.5时,对各向异性过滤不理解,书中内容也比较简略,所以特意查了一些资料加深认识。
各向异性是 GPU 硬件原生实现的高级纹理采样技术,专门解决大角度观察表面时,纹理投影畸变导致的模糊、锯齿、闪烁问题。
1. 各向异性的定义与来源
要理解各向异性,首先要明确纹理采样的核心概念:像素的纹理 Footprint,一个屏幕像素,对应到纹理空间中覆盖的区域,就是这个像素的纹理 Footprint。
-
各向同性场景:当视线垂直于物体表面时,像素的 Footprint 是近似正方形,纹理在 u、v 两个方向的拉伸比例完全一致。此时双线性/三线性过滤(各项同性过滤)完全适配,采样效果完美。
-
各向异性场景:当视线与物体表面法线夹角很大时(比如低头看远处的地面、斜着看木箱顶面),像素的 Footprint 会被拉伸成细长的、倾斜的四边形—— 纹理在一个方向被极度拉伸,另一个方向被压缩,u、v 方向的拉伸比例严重不一致,这就是「各向异性」。
传统过滤的致命缺陷
双线性 / 三线性过滤的采样核是固定的正方形,只能适配正方形的 Footprint:
-
为了适配拉伸的长轴,只能选择低分辨率的 mipmap 层级,导致画面严重模糊;
-
为了保留细节选择高分辨率 mipmap,又会因为采样范围不足,出现锯齿、纹理闪烁(走样)。
而各向异性过滤的核心,就是抛弃正方形采样核,完全适配畸变的、非正方形的 Footprint,在保留细节的同时消除走样。
2. 各向异性过滤的分步计算流程
AF是 GPU 硬件固定管线实现的,整个计算过程完全由硬件自动执行,应用层仅需开启功能、设置最大各向异性等级。完整计算分为 5 个核心步骤,是Direct3D的硬件规范:
步骤 1:计算像素的纹理 Footprint,确定各向异性比率
通过纹理坐标的屏幕空间梯度,算出 Footprint 的形状和拉伸比例。
-
GPU 会自动计算当前像素的纹理坐标
(u,v),相对于屏幕空间 x、y 轴的偏导数(梯度):-
ddx(u)、ddx(v):屏幕 x 轴方向移动 1 个像素,纹理坐标 u、v 的变化量; -
ddy(u)、ddy(v):屏幕 y 轴方向移动 1 个像素,纹理坐标 u、v 的变化量。(HLSL 中的ddx/ddy函数,就是直接暴露了这个硬件计算能力)
-
-
这两个梯度向量,会构成当前像素在纹理空间的 Footprint 的两条边,从而确定 Footprint 的完整形状、长轴(拉伸方向)、短轴(压缩方向)。
-
计算各向异性比率 N:长轴的长度 ÷ 短轴的长度,该值就是Footprint的拉伸倍数。
-
比如,长轴长度是短轴的12倍,各向异性比率就是12;
-
最终使用的比率,会被钳位到你在采样器中设置的
MaxAnisotropy(D3D12 支持1~16)。比如设置了 8x AF,就算算出比率是12,也只会用8。
-

步骤 2:选择适配的 mipmap 层级
和三线性过滤的「取平均尺寸选 mipmap」不同,AF会选择短轴对应的最精细 mipmap 层级(分辨率最高的层级)。
-
例:Footprint 长轴拉伸 16 倍(对应低分辨率 mipmap),短轴压缩 1 倍(对应最高分辨率 mipmap),三线性会取中间的 8 倍平均层级,而 AF 直接选最高分辨率的 1 倍层级,从根源上保留纹理细节,避免模糊。
-
如果开启了 mipmap 线性过滤(三线性模式),会同时选择最匹配的两个相邻 mipmap 层级,分别执行 AF 采样,最后再做线性插值。

步骤 3:沿拉伸长轴分布采样点,执行多次双线性采样
这是 AF 最核心的计算步骤,也是和传统过滤的本质区别。
-
GPU 会沿着 Footprint 的长轴(拉伸方向),均匀分布
N个采样点(N 就是步骤 1 算出的各向异性比率),让采样点完整覆盖整个拉伸的 Footprint,不会丢失任何方向的纹理信息。 -
每个采样点,都会在选中的 mipmap 层级上,执行一次标准的双线性过滤(采样该点周围 4 个相邻纹素,做双线性插值)。
-
例:16x AF,算出的各向异性比率是12,就会沿长轴分布12个采样点,执行12次双线性采样,累计采样48个纹素;
-
关键特性:AF 的采样次数是动态的,和实际拉伸比例成正比,不是开了16x AF 就一定会采样16次。如果 Footprint 只有2倍拉伸,就算开了16x AF,也只会采样2次,开销会动态降低。
-

步骤 4:对所有采样结果做加权平均
把步骤 3 得到的 N 个双线性采样结果,做加权平均(硬件默认等权重,或按采样点在 Footprint 中的位置做高斯加权),得到这个像素在当前 mipmap 层级的最终采样颜色。
步骤 5:(可选)mipmap 层级间的线性插值
如果开启了三线性过滤,GPU 会在两个相邻的 mipmap 层级上,分别执行步骤 3~4 的 AF 计算,再对两个层级的结果做一次线性插值,得到最终的像素颜色,消除 mipmap 层级切换的断层。
3. D3D12 中各向异性过滤的工程配置
完全对应你书中第 9 章的采样器配置,开启 AF 只需要修改两个核心参数:
-
Filter 字段:必须设置为
D3D12_FILTER_ANISOTROPIC,只有这个枚举值会开启各向异性过滤,其他过滤模式会忽略 AF 相关参数。 -
MaxAnisotropy 字段:设置最大各向异性比率,取值范围 1~16。
-
1:关闭 AF,退化为各项同性过滤;
-
2/4/8/16:常用等级,数值越大质量越高,开销上限越高;
-
D3D12 功能级别 11_0 及以上,强制支持最大 16x AF,现代显卡均无压力。
-
书中 Demo 的 AF 采样器配置示例
D3D12_SAMPLER_DESC anisotropicWrapDesc = {};
anisotropicWrapDesc.Filter = D3D12_FILTER_ANISOTROPIC;// 开启各向异性过滤
anisotropicWrapDesc.MaxAnisotropy = 8;// 最大各向异性比率
anisotropicWrapDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
anisotropicWrapDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
anisotropicWrapDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
anisotropicWrapDesc.MipLODBias = 0.0f;
anisotropicWrapDesc.MinLOD = 0;
anisotropicWrapDesc.MaxLOD = D3D12_FLOAT32_MAX;
anisotropicWrapDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
md3dDevice->CreateSampler(&anisotropicWrapDesc, hDescriptor);// 创建采样器
4. 各向异性过滤 vs 传统过滤的对比
| 过滤类型 | 采样核心 |
最大纹素采样数 | 适配场景 | 核心问题 |
|---|---|---|---|---|
| 点过滤 | 最近 1 个纹素 | 1 | 像素风格、UI | 锯齿、块化严重 |
| 双线性过滤 | 1 次双线性采样 | 4 | 垂直观察表面的放大场景 | 大角度观察严重模糊 |
| 三线性过滤 | 2 次双线性采样(2 个 mipmap 层级) | 8 | 垂直观察表面的缩小场景 | 大角度观察仍有模糊 / 闪烁 |
| 16x 各向异性过滤 | 最多 16 次双线性采样 | 64 | 所有场景,尤其大角度观察的缩小场景 | 仅开销上限更高,无本质缺陷 |
5. 常见误区
-
误区 1:16x AF 是采样 16 个纹素正解:16x AF 是最多执行 16 次双线性采样,每次双线性采样 4 个纹素,最大采样数是 64 个纹素,而非 16 个。
-
误区 2:开 16x AF 就一定会有 16 倍的性能开销正解:AF 的开销是动态的,仅和实际的各向异性比率成正比。只有当画面中存在大量 16 倍拉伸的 Footprint 时,才会达到最大开销;常规场景中,8x 和 16x AF 的性能差异极小。
-
误区 3:AF 需要特殊的纹理格式或 mipmap正解:AF 完全复用普通的 mipmap 链,不需要预生成任何特殊纹理资源,仅修改采样器配置即可生效。
-
误区 4:AF 只对纹理缩小有效正解:AF 主要优化缩小场景,但对放大场景的各向异性畸变(比如斜着拉伸的 UI 纹理)同样有优化效果。
采样器对象?采样器配置
在看到9.7中关于采样器对象,我又产生了各疑问🤔:
采样器对象,被多个着色器程序并行使用,不会出现问题吗?
查询相关信息后,我发现:
采样器(Sampler)在D3D12里属于只读全局状态(Global State)。可以将它理解为指导管线如何采样贴图的只读配置。
不管你是顶点着色器、像素着色器、几何着色器 GS、多个不同的着色器包(Shader Pack)、多个材质、多个物体、多轮绘制。一旦创建并绑定到渲染管线,所有正在使用的 HLSL 着色器都能同时访问、同时使用。
它的生命周期是:
-
CPU 创建采样器 → 存入采样器堆
-
调用
SetGraphicsRootDescriptorTable绑定到管线 -
绑定之后,整个管线全局可见
-
任何正在运行的 HLSL 都能通过
register(sX)访问它
也就是说:采样器属于管线,不属于某个着色器。不同着色器使用同一个采样器, 全部都能正常工作✅,不会冲突,不会覆盖。因为:
-
采样器在管线里只有一份实例
-
所有着色器只是读取它
-
采样器是只读状态,不会被修改

浙公网安备 33010602011771号