仅供个人学习使用,请勿转载。谢谢!
9、纹理贴图
学习目标
- 学习如何将局部纹理映射到网格三角形中
- 探究如何创建和启用纹理
- 学会如何通过纹理过滤来创建更加平滑的图像
- 探索如何使用寻址模式来进行多次贴图
- 探究如何将多个纹理进行组合,从而创建出新的纹理和特效
- 学习如何通过纹理动画来创建一些基本效果
9.5、过滤器
9.5.1、放大
假设玩家慢慢接近了场景中的一堵墙壁,则墙壁将会被逐渐放大并占据整个屏幕,假设显示器的分辨率为1024x1024,而墙壁纹理的分辨率为256x256。那么这将会产生纹理放大(因为我们试图使用少量纹素来覆盖大量的像素)。为了解决纹理和像素分辨率不匹配的问题,我们一般会对纹素之间的颜色数据进行插值运算,从而获得指定纹素处的颜色信息。图形硬件常用的插值方法有线性插值和常数插值两种方法。其中线性插值比较常用。
补充小知识:在纹理这一语境中,通过常数插值求取纹素之间纹理坐标处的纹理数据也称为点过滤,通过线性插值求取纹素之间的纹理坐标处的纹理数据也被称为线性过滤。点过滤和线性过滤是Direct3D中常用的术语。
9.5.2、缩小
纹理缩小是纹理放大的逆运算,在缩小的过程中,大量纹素将会被映射到少数纹理之中。比如墙壁的分辨率为256x256,当玩家逐渐远离墙壁的时候,墙壁在显示器上会越来越小,它所覆盖的像素区域也将逐渐变小,这是便会产生纹理缩小(因为我们试图使用大量纹素来覆盖少量像素)。
为了解决纹理缩小的问题,我们同样可以采用点过滤和线性过滤的方法。从直观上来说,我们可以通过平均下采样(average downsampling)使256x256像素减少到64x64像素。在初始化期间,我们可以通过对图像下采样来创建mipmap链便可以制作出缩小版的纹理(提前制作出不同规格的纹理)。图像硬件将会根据程序元的设定,从以下两种执行方案中任选一种进行操作:
1、在纹理贴图的时候,选择和待投影到屏幕上的几何体分辨率最为匹配的mipmap层级别,并且根据需求选用常数插值或者线性插值,这种技术成为i针对mipmap的点过滤
2、在纹理贴图的时候,选区和待投影到屏幕上的几何体分辨率最为匹配的两个邻接的mipmap成绩,然后对这两种mipamp层级分别应用常量过滤和线性过滤,以生成他们各自对应的纹理颜色, 最后对这两种插值文件之间再次进行颜色的插值计算,这种技术称为mipmap的线性过滤
9.5.3、各向异性过滤
各向异性过滤器可以缓解当多边形法向量和摄像机观察向量的夹角过大时导致的失真,但是这种过滤器的开销是最大的,同时它所带来的效果也是最好的。(原理不进行介绍)
9.6、寻址模式
我们可以将经过常数插值或者线性插值处理的纹理定义为一个函数T,即给定一个uv纹理坐标,经过上述的纹理函数T之后将会返回颜色(r,g,b,a);Direct3D允许我们采用4种不同的方式(即寻址模式)来扩充函数T的定义域,分别为重复寻址模式、边框颜色寻址模式、钳位寻址模式、镜像寻址模式。
寻址模式 | 实现方法 |
---|---|
重复寻址模式 | 通过在坐标的每个整数点处重复绘制图像来扩充纹理函数 |
边框颜色寻址模式 | 通过将每个不在[0,1]范围内的坐标(u,v)映射为程序员指定的颜色来扩充纹理函数 |
钳位寻址模式 | 通过将[0,1]范围外的每个坐标(u,v)映射为颜色T(u0,v0)来扩充纹理函数,其中(u0,v0)为范围[0,1]内距离(u,v)最近的点 |
镜像寻址模式 | 通过在坐标的每一个整数点处绘制图像的镜像来扩充纹理函数 |
在程序中,我们一定要指定一种寻址模式(默认为重复寻址模式),所以[0,1]范围外的纹理坐标必定有定义。而重复寻址模式是最常用的寻址模式,它可以让我们将纹理反复平铺到某一表面。
在Direct3D中,寻址模式由枚举类型D3D12_TEXTURE_ADDRESS_MODE来表示
typedef enum D3D12_TEXTURE_ADDRESS_MODE
{
//重复寻址模式
D3D12_TEXTURE_ADDRESS_MODE_WRAP = 1,
//镜像寻址模式
D3D12_TEXTURE_ADDRESS_MODE_MIRROR = 2,
//钳位寻址模式
D3D12_TEXTURE_ADDRESS_MODE_CLAMP = 3,
//边框颜色寻址模式
D3D12_TEXTURE_ADDRESS_MODE_BORDER = 4,
D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE = 5
}D3D12_TEXTURE_ADDRESS_MODE;
9.7、采样器对象
在运用纹理的过程中,除了纹理数据本身之外,还有另外两个相关的重要概念,即纹理过滤和寻址模式。采集纹理资源的时候所使用的过滤器和寻址模式都是由采样器对象来定义的,一个应用程序一般会有多个采用器对象以不同的方式来采集纹理资源
9.7.1、创建采样器
采样器将会被着色器所使用,所以我们需要给采样器对象绑定一个描述符,这样采样器才可以绑定到着色器上供着色器使用。
下面的代码展示了一个根签名示例,该根签名的第二个槽位获取了一个描述符表,在这个表中有1个和采样器寄存器槽0相互绑定的采样器描述符
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
//创建根参数
CD3DX12_ROOT_PARAMETER rootParameters[3];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[2].InitAsDescriptorTable(1, &descRange[2], D3D12_SHADER_VISIBILITY_ALL);
//将根参数绑定到根签名上
CD3DX12_ROOT_SIGNATURE_DESC descRootSignature;
descRootSignature.Init(1, rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
如果要设置采样器描述符,我们需要先创建一个采样器堆,因此我们要填写D3D12_DESCRIPTOR_HEAP_DESC结构体实例一个堆并将堆的类型指定为D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLE;
//填写描述符堆结构体
D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = {};
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
descHeapSampler.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
//创建采样器堆
ComPtr<ID3D12DescriptorHeap> mSamplerDescriptorHeap;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&descHeapSampler,
__uuidof(ID3D12DescriptorHeap), (void**)&mSamplerDescriptorHeap));
有了采样器堆之后,我们便可以创建采样器描述符了。
typedef struct D3D12_SAMPLE_DESC
{
//指定采集纹理时所使用的过滤方式
D3D12_FILTER Filter;
//纹理在水平u轴方向上使用的寻址模式
D3D12_TEXTURE_ADDRESS_MODE AddressU;
//纹理在垂直v轴方向上使用的寻址模式
D3D12_TEXTURE_ADDRESS_MODE AddressV;
//纹理在深度w轴方向上使用的寻址模式
D3D12_TEXTURE_ADDRESS_MODE AddressW;
//设置mipmap层级的偏置值
FLOAT MipLODBias;
//最大各向异性值(此参数的区间为[1,16],只有Filter为各项异性该参数才会生效)
UINT MaxAnisottropy;
//用于实现像阴影贴图这类特殊应用的高级选项
D3D12_COMPARISON_FUNC ComparisonFunc;
//用于指定边框颜色寻址模式的填充颜色(只有寻址模式为边框寻址模式时该参数才会生效)
FLOAT BorderColor[4];
//可供选择的最小miamap层级
FLOAT MinLOD;
//可供选择的最大mipmap层级
FLOAT MaxLOD;
}D3D12_SAMPLER_DESC;
以下实例将会展示如何在描述符堆中为采样器创建出对应的描述符,该采样器使用线性过滤和重复寻址模式,其他参数保持默认值。
//填写采样器描述符结构体
D3D12_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
//为采样器创建对应的描述符
md3dDevice->CreateSampler(&samplerDesc, mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
//将采样器描述符绑定到预定的根签名参数槽
commandList->SetGraphicsRootDescriptorTable{1,mSamplerDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart()};
9.7.2、静态采样器
一般来说,图形应用程序不会使用太多的采样器,所以Direct3D有一种特殊的方法来定义采样器数组,使用户可以在不创建采样器堆的情况下也能对采样器进行配置。我们通过结构体D3D12_STATIC_SAMPLER_DESC来描述静态采样器,D3D12_STATIC_SAMPLER_DESC和D3D12_SAMPLER_DESC比较相似,但是存在一点区别:
- 边框颜色存在一些限制,即静态采样器的边框颜色必须是D3D12_STATIC_BOROER_COLOR的成员之一:
- 含有额外的字段来指定着色器寄存器、寄存器空间以及着色器的可见性。
- 用户只能定义2032个静态采样器
enum D3D12_STATIC_BOROER_COLOR{
D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK = 0,
D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK = ( D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK + 1 ) ,
D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE = ( D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK + 1 )
}D3D12_STATIC_BOROER_COLOR;
在本章的演示程序中,我们会使用静态采样器,尽管我们不会使用到所有定义的静态采样器,但是我们会保留他们的定义,因为这样在需要使用的时候比较方便。
9.8、在着色器中对纹理进行采样
通过下列的HLSL语法来定义纹理对象,并将其分配给特定的纹理寄存器
//纹理寄存器由tn标定
Texture2D gDiffuseMap : register(t0);
因为根签名的定义指定了槽位参数和着色器寄存器的映射关系,这便是SRV可以绑定到着色器中特定的Texture2D对象的原因
类似的,下列HLSL语法定义了多个采样器对象,并将其分配到特定的采样器寄存器中
//采样器寄存器由sn标定
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
现在我们在像素着色器中为每一个像素指定其对应的纹理坐标(u,v),并通过Texture2D::Sample方法进行正式采样
//纹理寄存器由tn标定
Texture2D gDiffuseMap : register(t0);
//采样器寄存器由sn标定
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
};
float4 PS(vertexOut pin) : SV_Target
{
//第一个参数为采样器对象类型,第二个参数为纹理坐标(u,v)
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap,pin.TexC*gDiffuseAlbedo);
……
}
我们通过向Sample方法的第一个参数传递SamplerState对象来描述如何对纹理进行采样,然后给第二个参数传递像素的纹理坐标(u,v)。这个方法将利用SamplerState对象所指定的过滤方法,返回纹理图在坐标点(u,v)处的插值颜色。