1.纹理与资源的回顾

我们其实很早就接触过纹理了,之前的深度缓冲区与后台缓冲区,它们都是通过ID3D12Resource接口表示,并以D3D12_RESOURCE_DESC::Dimension成员中D3D12_RESOURCE_DIMENSION_TEXTURE2D类型来描述2D纹理对象。

2D纹理是一种由特定数据元素所构成的矩阵,或者说存有纹理数据元素的2D数组,同理还有1D和3D纹理,就是1D数组和3D数组。

纹理不同于缓冲区资源,因为缓冲区资源仅存储数据数组,而纹理却可以具有多个mipmap层级,GPU会基于这个层级进行相应的特殊操作,比如使用过滤器以及多重采样。支持这些特殊操作的纹理资源会被限定为一些特定的数据格式(缓冲区则没有这些限制,可以存储任意类型)。

纹理所支持的数据格式枚举类型DXGI_FORMAT来表示

RGBA表示各分量,但不一定要存储颜色信息。

还有无类型格式,DXGI_FORMAT_R8G8B8A8_TYPELESS,只有在不得已的情况下才使用。

DirectX 11 SDK 文档中提到,以某种具体类型创建的资源,其格式是不能改变的。这将使该资源在运行时的访问得以优化。

一个纹理可以绑定到渲染流水线的不同阶段,一个常见的例子是将一个纹理用作渲染目标[RTV](D3D中的渲染到纹理技术),也可以充当着色器资源[SRV],但是不能身兼数职。将数据渲染到一个纹理后,再用它作为着色器资源,这种方法称为渲染到纹理。-- 需要纹理扮演者两种角色,需要创建对应的RTV和SRV

// 绑定为渲染目标
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = ...;
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = ...;
cmdList->OMSetRenderTargets(1, &rtv, true, &dsv);// 以着色器输入的名义绑定到根参数
CD3DX12_GPU_DESCRIPTOR_HANDLE = tex = ...;
cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, tex);

2.纹理坐标

Direct3D所采用的纹理坐标系,是由指向图像水平正方向的u轴与指向图像垂直正方向的v轴所组成的。取值范围为0≤u,v≤1的坐标(u,v)标定的是一种称为纹素(texel) -- 采用归一化坐标区间[0,1]

注意:纹理坐标系的v轴正方向是从上往下!

对于每个3D三角形来说,我们希望在将要映射于其上的纹理中定义出与之对应的三角形。设p0、p1、p2为3D三角形的3个顶点,它们分别对应于纹理坐标q0、q1、q2。针对3D三角形上任意一点(x,y,z)处的纹理坐标(u,v),我们都可以通过与3D三角形坐标插值所用的相同参数s、t,对顶点纹理坐标进行线性插值来求得:

当s≥0,t≥0,s+t≤1时,那么:

更新顶点结构体:

struct Vertex{DirectX::XMFLOAT3 Pos;DirectX::XMFLOAT3 Normal;DirectX::XMFLOAT2 TexC; // tex coordinate 纹理坐标 (u, v)
};// 对应输入布局
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout =
{{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
};

我们可以选择性的映射部分纹理,也可以将几张毫无关联的纹理合为一个大的纹理图(被称为:纹理图集,texture atlas),再将它应用于若干不同的物体。

3.纹理数据源

贴图师通常会借助Photoshop或一些其他的图像编辑器为游戏制作纹理,最后再将他们保存为某种格式的图像文件,比如BMP、DDS、TGA或PNG等。随后,游戏应用程序会在加载期间将图像文件载入ID3D12Resource对线。对于实时图形应用程序来说,DDS图像文件格式(DirectDraw图面格式,DirectDraw Surface format, DDS)是一种尚佳的选择:除了支持GPU可原生处理的各种图像格式,他还支持一些GPU自身可解压的压缩图像格式。

贴图师们不宜将DDS格式当作工作过程中所用的图像格式,而是应当用他们认为更顺手的格式来保存工作进程。待纹理最终完成后,再为游戏应用程序把它导出为DDS格式。

①DDS格式概述:

DDS对于3D图形来说是一种理想的格式,支持专用于3D图形的特殊格式以及纹理类型。从本质上来讲,它是一种针对GPU而专门设计的图像格式。DDS纹理满足于3D图形开发的以下特征:

  • mipmap
  • GPU能自行解压的压缩格式
  • 纹理数组
  • 立方体图(cube map, 或立方体贴图)
  • 体纹理(volumn texture, 体积纹理或立体纹理等)

DDS格式能够支援不同的像素格式。像素格式由枚举类型DXGI_FORMAT中的成员来表示,但是并非所有的格式都适用于DDS纹理。非压缩图像数据一般采取下列格式:

  • DXGI_FORMAT_B8G8R8A8_UNORM 或 DXGI_FORMAT_B8G8R8X8_UNORM:适用于低动态范围(low-dynamic-range)图像
  • DXGI_FORMAT_R16G16B16A16_FLOAT:适用于高动态范围(HDR)图像

随着虚拟场景中纹理数量的大幅增长,对GPU端显存的需求也迅速增加(所有纹理都位于GPU显存中)。为了缓解这些内存的需求压力,D3D支持下列几种压缩纹理格式(也称为块压缩, block compression):

②创建DDS文件:

4.创建以及启用纹理

①加载DDS文件:

微软公司提供了一组用来加载DDS文件的轻量级源代码,龙书编写时,这段轻量级代码只支持DX11,所以龙书作者在Common文件夹中修改了DDSTextureLoader.h/.cpp

HRESULT DirectX::CreateDDSTextureFromFile12(_In_ ID3D12Device* device, // 指向用于创建纹理资源的D3D设备的指针_In_ ID3D12GraphicsCommandList* cmdList, _In_z_ const wchar_t* szFileName, // 欲加载图像文件名_Out_ ComPtr<ID3D12Resource>& texture, // 返回载有图像数据的纹理资源_Out_ ComPtr<ID3D12Resource>& textureUploadHeap, // 返回的纹理资源,在此,将它当作一个上传堆,用于将图像数据复制到默认堆中的纹理资源。GPU执行复制命令前,上传堆不能摧毁_In_ size_t maxsize,_Out_opt_ DDS_ALPHA_MODE* alphaMode
);

使用该函数:

struct Texture
{// Unique material name for lookup.std::string Name;std::wstring Filename;Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
};void CrateApp::LoadTextures()
{auto woodCrateTex = std::make_unique<Texture>();woodCrateTex->Name = "woodCrateTex";woodCrateTex->Filename = L"../../Textures/WoodCrate01.dds";ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),mCommandList.Get(), woodCrateTex->Filename.c_str(),woodCrateTex->Resource, woodCrateTex->UploadHeap));mTextures[woodCrateTex->Name] = std::move(woodCrateTex);
}

②着色器资源视图堆:srv heap

创建纹理资源后,还需要创建SRV描述符,并将其设置到一个根签名参数槽(root signature parameter slot),以供着色器程序使用。

D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 3; // 该堆能存储3个描述符(CBV/SRV/UAV)
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));

③创建着色器资源视图描述符:srv

typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC{DXGI_FORMAT Format; // 资源格式 DXGI_FORMAT -- 如果之前指定的typeless,则在此处必须指定具体类型D3D12_SRV_DIMENSION ViewDimension; // 资源的维数UINT Shader4ComponentMapping; // 对纹理进行采样得到的结果中分量进行重新排序(比如R、G分量互换) -- D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING(不改变分量顺序)union // 我们重点关心union联合体{D3D12_BUFFER_SRV Buffer;D3D12_TEX1D_SRV Texture1D;D3D12_TEX1D_ARRAY_SRV Texture1DArray;D3D12_TEX2D_SRV Texture2D;D3D12_TEX2D_ARRAY_SRV Texture2DArray;D3D12_TEX2DMS_SRV Texture2DMS;D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;D3D12_TEX3D_SRV Texture3D;D3D12_TEXCUBE_SRV TextureCube;D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;D3D12_RAYTRACING_ACCELERATION_STRUCTURE_SRV RaytracingAccelerationStructure;}   ;}  D3D12_SHADER_RESOURCE_VIEW_DESC;// 2D纹理:
typedef struct D3D12_TEX2D_SRV{UINT MostDetailedMip; // 指出此视图中图像细节最详尽的Mipmap层级的索引 -- 取值范围0~MipLevels-1UINT MipLevels; // 自MostDetailedMip算起,待创建视图的mipmap层级数量 -- 另外,可以设置为-1表示自MostDetailedMip开始到最后一个Mipmap层级之间所有的mipmap层级UINT PlaneSlice; // 平面切片的索引FLOAT ResourceMinLODClamp; // 指定可以访问的最小mipmap层级 -- 设置为0.0表示可以访问所有mipmap层级}   D3D12_TEX2D_SRV;--------------------------------------------------------------------------------------
// ViewDimension:
常见资源维数:①D3D12_SRV_DIMENSION_TEXTURE2D②当然还有1D、3D③D3D12_SRV_DIMENSION_TEXTURECUBE 立方体纹理(cube texture)// MostDetailedMip、MipLevels:
通过这两个成员,可以指定此视图mipmap层级的连续子范围

示例,创建三个srv:

// 获取指向描述符堆的起始处句柄:
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());// auto woodCrateTex = mTextures["woodCrateTex"]->Resource;
// 或者假设有3个纹理资源: brickTex stoneTex tileTexD3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = brickTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = woodCrateTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;md3dDevice->CreateShaderResourceView(brickTex.Get(), &srvDesc, hDescriptor);// 偏移到下一个描述符处:
hDescriptor.Offset(1, mCbvSrvDescriptorSize);srvDesc.Format = stoneTex->GetDesc().Format();
srvDesc.Texture2D.MipLevels = stoneTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(), &srvDesc, hDescriptor);// 偏移到下一个描述符处:
hDescriptor.Offset(1, mCbvSrvDescriptorSize);srvDesc.Format = tileTex->GetDesc().Format();
srvDesc.Texture2D.MipLevels = tileTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(), &srvDesc, hDescriptor);

④将纹理绑定到流水线:

我们之前绘制调用使用的材质,都是通过程序的材质常量缓冲区来进行更新的,这对程序是一种极大的限制。纹理映射技术的想法是用纹理图来去带材质常量缓冲区以获取材质数据

比如,我们将添加漫反射反照率纹理图(diffuse albedo texture map),以此来给出材质的漫反射反照率分量。影响材质的两个数据gFresnelR0与gRoughness仍然将每次绘制调用时由材质常量缓冲区来指定。第十九章将借助纹理在像素层级指定粗糙度。

注意,使用纹理贴图时,我们仍然需要在材质常量缓冲区中保留gDiffuseAlbedo分量。我们通常将常量缓冲区中gDiffuseAlbedo设置为(1,1,1,1),然而,可以通过对该数据进行调整以避免制作新的纹理

// PS:像素着色器中,将纹理漫反射反照率数据与DiffuseAlbedo相组合
float4 texDiffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC);// 纹理样本与常量缓冲区中的反照率相乘
float4 diffuseAlbedo = texDiffuseAlbedo * gDiffuseAlbedo; 

更新Material:

struct Material
{...int DiffuseSrvHeapIndex = -1; // diffuseAlbedo纹理 -- 对应SRV在heap中的索引
};

接下来,根签名处,比如创建一个描述符表,将SRV都放置在表中,通过句柄和偏移找到SRV位置,再通过SetGraphicsRootDescriptorTable来向根签名填入实参。

事实上,纹理资源可以运用于任何着色器,但此处我们之将其作用于像素着色器其实,纹理在本质上就是一种支持GPU特殊操作的特别数组。

纹理图集可以在一次绘制调用中渲染多个几何体,可以通过将所有几何体放置在一个渲染项中,降低渲染过程的开销。

5.过滤器

①放大:

我们可以将纹理图中的元素看作从连续图像中采集的离散颜色样本,但并不应认为它们是有着特定面积大小的矩形。所以,当前的疑问是:如果在纹理坐标(u,v)处没有与之对应的纹素点究竟会发生什么?

比如,当玩家慢慢靠近场景中的一堵墙壁时,便产生了纹理放大的概念,用少量纹素来覆盖大量的像素。对三角形中的顶点纹理进行插值时,会得到特定的纹理坐标,但该纹理坐标处可能没有对应的纹素。对此,我们可以对纹素之间的颜色数据进行插值,有线性插值和常数插值等方法。线性插值更为普遍。在纹理这一语境中,常数插值求得纹理数据称为点过滤,而采用线性插值则称为线性过滤

1D插值 -- 最近邻点采样:通过最近的点构建分段常量函数来求出纹素点之间的某处的近似值

2D插值(又称为双线性插值 bilinear interpolation):给出临近的4个点,先对水平方向u上进行两次1D插值,再对垂直方向上进行一次1D内插。-- 在uv平面上进行插值

回顾:插值公式

1D插值:

纹理放大是一个无法避免的问题,当与目标保持特定距离时,纹理可能看上去还不错,但随着观察点逐渐接近目标,其效果就开始惨不忍睹了。解决方案:①对虚拟视角过于接近物体表面的行为进行了限制,避免过度放大处理②通过更高分辨率的纹理。

②缩小:

在缩小的过程中,大量纹素将被映射到少数纹理之上。考虑这种情况,当游戏人物远离墙壁时,墙会越来越小。同理,也会出现纹理坐标没有对应的纹素的情况,所以也需要常熟插值过滤器与线性插值过滤器。然而,执行纹理缩小操作还有更多的工作要做mipmap技术

在初始化期间,通过对图像下采样来创建mipmap链便可制作出缩小版的纹理。

在运行时,图形硬件将根据程序员的设定,从以下两种不同的执行方案中择一而行:

  • 在纹理贴图时,选择与待投影到屏幕上的几何体分辨率最为匹配的mipmap层级,并根据具体需求选择常数或线性插值。 -- mipmap的点过滤:我们仅选择与目标分辨率最邻近的那个mipmap层级进行纹理贴图
  • mipmap的线性过滤:选择与几何体分辨率最为匹配的两个邻近的mipmap层级,一个略大一个略小于几何体的分辨率。接下来对两个层级分别应用常量过滤或线性过滤,以生成它们各自相应的纹理颜色。在对这两种插值纹理之间再次进行颜色的插值。 -- 类似于上面所说的2D插值(双线性插值)

PS或texconv程序可以基于原始的图像数据,创建mipmap:利用采样算法来生成更低的mipmap层级图像。但有时候,这些算法不能保留所希望的图像细节,贴图师还需要亲手编辑更低mipmap级别的图像。

texconv下载与使用方法:

texconv下载以及使用命令 - HONT - 博客园 (cnblogs.com)

③各向异性过滤:anisotropic filtering

各向异性过滤器有助于缓解当多边形法向量与摄影机观察向量之间夹角过大所导致的失真现象 -- 比如当多边形正交于观察窗口。这种过滤器开销最大,但是其矫正失真的效果对得起它所消耗的资源。

6.寻址模式

可以将经过常数插值或线性插值的纹理定义为一个返回向量值的函数,即给定纹理坐标,D3D允许我们采用下列四种不同的方式(即寻址模式,address mode)来扩充此函数的定义域

  • [默认的寻址模式:]重复寻址模式 wrap:在坐标的每个整数点处重复绘制图像来拓充纹理函数
  • 边框颜色寻址模式 border color:范围外的坐标处的颜色由程序员单独额外指定
  • 钳位寻址模式 clamp:范围外的坐标处的颜色由最近的​​​​​属于​​范围内的点指定 -- 相当于对范围外的点做“钳制”操作
  • 镜像寻址模式 mirror:在坐标的整数点处绘制图像的镜像来扩充纹理函数

通过寻址模式可以提升纹理的分辨率。执行平铺的时候,纹理是否是无缝衔接的也是一个重点。

在D3D中,寻址模式由枚举类型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;

7.采样器对象

前面两个小节中讨论的纹理过滤以及寻址模式,都是由采样器对象来定义的

①创建采样器:

采样器会被着色器使用,为了将采样器绑定到着色器上,需要为采样器对象绑定描述符。

// 设置根签名:
// 根签名第二个槽位是一个描述符表,放置一个采样器描述符
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLE, 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(3, rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

采样器描述符需要一个采样器堆

// 描述符堆:存放采样器描述符
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(mDevice->CreateDescriptorHeap(&descHeapSampler, __uuidof(ID3D12DescriptorHeap), (void**)&mSamplerDescriptorHeap));

创建采样器描述符,填写D3D12_SAMPLER_DESC对象:

typedef struct D3D12_SAMPLER_DESC
{D3D12_FILTER Filter;D3D12_TEXTURE_ADDRESS_MODE AddressU;D3D12_TEXTURE_ADDRESS_MODE AddressV;D3D12_TEXTURE_ADDRESS_MODE AddressW;FLOAT MipLODBias;UINT MaxAnisotropy;D3D12_COMPARISON_FUNC ComparisonFunc;FLOAT BorderColor[4];FLOAT MinLOD;FLOAT MaxLOD;
} D3D12_SAMPLE_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层级都进行(双)线性过滤 -- 这也被称为3线性过滤
D3D12_FILTER_ANISOTROPIC:对于纹理的放大、缩小以及mipmap均采用向异性过滤// AddressU/V/W:
纹理在水平u轴/垂直v轴/深度w轴方向上的寻址模式 -- 深度w轴仅限于3D纹理// MipLODBias:
设置mipmap层级的偏置值,如果设置为0.0,则mipmap层级保持不变;如果设置为2,而mipmap层级设置为3,那么将按照3+2进行采样// MaxAnisotropy:
最大各项异性值,该参数的取值区间为[1,16],设置越大的值,渲染效果越好。
只有将Filter设置为D3D12_FILTER_ANISOTROPIC或D3D12_FILTER_COMPARISON_ANISOTROPIC之后该项才生效// ComparisonFunc:
用于实现像阴影贴图(shadow mapping)这样一类特殊应用的高级选项,未解除阴影贴图这一章节前,暂时设置为D3D12_COMPARISON_FUNC_ALWAYS// BorderColor:
用于指定D3D12_TEXTURE_ADDRESS_MODE_BORDER寻址模式下的边框颜色// MinLOD/MaxLOD:
可供选择的最小/大mipmap层级

示例:创建一个采样器描述符,该采样器使用线性过滤与重复寻址模式,其他参数保留默认值

D3D12_SAMNPLER_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());

②静态采样器:

事实证明,图形应用程序通常不会使用过多的采样器。为此,D3D专门提供了一种特殊的方式来定义采样器数组,使用户可以在不创建采样器堆的情况下也能对它们进行配置。CD3DX12_ROOT_SIGNATURE_DESC类由两种参数不同的Init函数,用户可以借此为应用程序定义所用的静态采样器数组静态采样器使用结构体D3D12_STATIC_SAMPLER_DESC来描述,与之前采样器结构略有区别:

  • 边框颜色存在一些限制,静态采样器的边框颜色必须为下列成员之一:
enum D3D12_STATIC_BORDER_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_BORDER_COLOR;
  • 含有额外的字段用来指定着色器寄存器、寄存器空间以及着色器的可见性,这些其实都是配置采样堆的相关参数。另外,用户只能定义2032个静态采样器。

我们的演示程序预先创建6种静态采样器,即使不用将其预留在此也行:

使用CD3DX12_STATIC_SAMPLER_DESC结构快捷构建采样器结构

std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> CrateApp::GetStaticSamplers()
{const CD3DX12_STATIC_SAMPLER_DESC pointWrap(0, // shaderRegisterD3D12_FILTER_MIN_MAG_MIP_POINT, // filterD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressWconst CD3DX12_STATIC_SAMPLER_DESC pointClamp(1, // shaderRegisterD3D12_FILTER_MIN_MAG_MIP_POINT, // filterD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressWconst CD3DX12_STATIC_SAMPLER_DESC linearWrap(2, // shaderRegisterD3D12_FILTER_MIN_MAG_MIP_LINEAR, // filterD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressWconst CD3DX12_STATIC_SAMPLER_DESC linearClamp(3, // shaderRegisterD3D12_FILTER_MIN_MAG_MIP_LINEAR, // filterD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressWconst CD3DX12_STATIC_SAMPLER_DESC anisotropicWrap(4, // shaderRegisterD3D12_FILTER_ANISOTROPIC, // filterD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressW0.0f,                             // mipLODBias8);                               // maxAnisotropyconst CD3DX12_STATIC_SAMPLER_DESC anisotropicClamp(5, // shaderRegisterD3D12_FILTER_ANISOTROPIC, // filterD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressUD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressVD3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressW0.0f,                              // mipLODBias8);                                // maxAnisotropyreturn { pointWrap, pointClamp,linearWrap, linearClamp, anisotropicWrap, anisotropicClamp };
}

创建根签名时:

void CrateApp::BuildRootSignature()
{CD3DX12_DESCRIPTOR_RANGE texTable;texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);// Perfomance TIP: Order from most frequent to least frequent.slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);slotRootParameter[1].InitAsConstantBufferView(0);slotRootParameter[2].InitAsConstantBufferView(1);slotRootParameter[3].InitAsConstantBufferView(2);// ---------------------------------------------------------------------// ⭐:不同之处:这里创建采样器,并填入根签名结构中auto staticSamplers = GetStaticSamplers();CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,(UINT)staticSamplers.size(), staticSamplers.data(),D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);// ---------------------------------------------------------------------ComPtr<ID3DBlob> serializedRootSig = nullptr;ComPtr<ID3DBlob> errorBlob = nullptr;HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());if(errorBlob != nullptr){::OutputDebugStringA((char*)errorBlob->GetBufferPointer());}ThrowIfFailed(hr);ThrowIfFailed(md3dDevice->CreateRootSignature(0,serializedRootSig->GetBufferPointer(),serializedRootSig->GetBufferSize(),IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

8.在着色器中对纹理进行采样

HLSL语法:定义纹理对象,并分配给指定的纹理寄存器

Texture2D gDiffuseMap : register(t0);

HLSL语法:定义多个采样器对象,并分配到特定的采样器寄存器

SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

对纹理进行采样:Texture2D::Sample方法

Texture2D gDiffuseMap : register(t0);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
{// Sample方法返回的就是纹理图在点(u,v)处的插值颜色float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo; ...
}

9.板条箱演示程序

设置纹理:纹理被创建后,SRV存于描述符堆中,那么我们只要把对应的SRV设置到根签名坑位处即可绑定到渲染流水线

CD3DX12_GPU_DESCRIPTOR_HANDLE tex(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
);
tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);...cmdList->SetGraphicsRootDescriptorTable(0, tex);

Default.hlsl文件:

#ifndef NUM_DIR_LIGHTS#define NUM_DIR_LIGHTS 3
#endif#ifndef NUM_POINT_LIGHTS#define NUM_POINT_LIGHTS 0
#endif#ifndef NUM_SPOT_LIGHTS#define NUM_SPOT_LIGHTS 0
#endif#include "LightingUtil.hlsl"Texture2D    gDiffuseMap : register(t0);
SamplerState gsamLinear  : register(s0);// 每一帧都有变化的常量数据
cbuffer cbPerObject : register(b0)
{float4x4 gWorld;float4x4 gTexTransform; // 纹理变换
};cbuffer cbPass : register(b1)
{float4x4 gView;float4x4 gInvView;float4x4 gProj;float4x4 gInvProj;float4x4 gViewProj;float4x4 gInvViewProj;float3 gEyePosW;float cbPerObjectPad1;float2 gRenderTargetSize;float2 gInvRenderTargetSize;float gNearZ;float gFarZ;float gTotalTime;float gDeltaTime;float4 gAmbientLight;Light gLights[MaxLights];
};cbuffer cbMaterial : register(b2)
{float4 gDiffuseAlbedo;float3 gFresnelR0;float  gRoughness;float4x4 gMatTransform; // 材质变换
};struct VertexIn
{float3 PosL    : POSITION;float3 NormalL : NORMAL;float2 TexC    : TEXCOORD; // 纹理坐标
};struct VertexOut
{float4 PosH    : SV_POSITION;float3 PosW    : POSITION;float3 NormalW : NORMAL;float2 TexC    : TEXCOORD; // 纹理坐标
};VertexOut VS(VertexIn vin)
{VertexOut vout = (VertexOut)0.0f;float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);vout.PosW = posW.xyz;vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);vout.PosH = mul(posW, gViewProj);// 为了对三角形进行插值操作而输出的顶点属性// 纹理坐标 * gTexTransform * gMatTransformfloat4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);vout.TexC = mul(texC, gMatTransform).xy;return vout;
}float4 PS(VertexOut pin) : SV_Target
{// 纹理采样float4 diffuseAlbedo = gDiffuseMap.Sample(gsamLinear, pin.TexC) * gDiffuseAlbedo;pin.NormalW = normalize(pin.NormalW);float3 toEyeW = normalize(gEyePosW - pin.PosW);float4 ambient = gAmbientLight*diffuseAlbedo;const float shininess = 1.0f - gRoughness;Material mat = { diffuseAlbedo, gFresnelR0, shininess };float3 shadowFactor = 1.0f;float4 directLight = ComputeLighting(gLights, mat, pin.PosW,pin.NormalW, toEyeW, shadowFactor);float4 litColor = ambient + directLight;litColor.a = diffuseAlbedo.a;return litColor;
}

10.纹理变换

上述程序中常量缓冲区变量gTexTransform与gMatTransform还未进行讨论。纹理坐标表示的是纹理平面中的2D点,也就是说这些点也能进行缩放、平移与旋转。比如:先对纹理进行拉伸后再贴在模型上,或者天空的纹理按时间平移变换,或一些特效比如火球纹理旋转

gTexTransform:关于材质的纹理变换(针对像水那样的动态材质)

gMatTransform:关于物体属性的纹理变换

我们只关心前两个坐标轴的变换情况,所以令z为0.0(使z坐标平移与否不影响结果),令w令为1.0(保证平移操作)

⭐课后习题:火球纹理旋转:

XMMATRIX trans = XMMatrixRotationZ(gt.TotalTime());
XMStoreFloat4x4(&mat->MatTransform, trans);

遇到的问题:纹理图案是按照原点进行旋转的,而不是火球的中心点。现在的贴图的旋转中心是(0, 0)点,我们需要将(0, 0)点设置到贴图中心,即UV取值范围改为(-0.5, 0.5)之间。我们先将UV取值范围改为(-0.5, 0.5),最后再平移图像,使得火球在正中间

参考文章:DX12纹理篇:习题 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/165481502

VertexOut VS(VertexIn vin)
{............vin.TexCoord -= float2(0.5f, 0.5f);//将UV取值范围改为(-0.5, 0.5)//计算UV坐标的静态偏移(相当于MAX中编辑UV)float4 texCoord = mul(float4(vin.TexCoord, 0.0f, 1.0f), gTexTransform);//配合时间函数计算UV坐标的动态偏移(UV动画)vout.UV = mul(texCoord, gMatTransform).xy;vout.UV += float2(0.5f, 0.5f);//u和v方向各平移0.5个单位return vout;
}

通过修改uv坐标实现纹理的平移,而不用考虑当前mipmap层级中图像的分辨率!

uv限制在归一化区间[0,1]^2的操作可以让程序员忽略图像分辨率的具体大小。

dx12 龙书第九章学习笔记 -- 纹理贴图相关推荐

  1. dx12 龙书第二十章学习笔记 -- 阴影贴图

    对于龙书这本入门级别的书籍来说,我们仅关注于基本的阴影贴图算法.而像级联阴影贴图(cascading shadow map)[Engel06]这种效果更佳却也更为复杂的阴影技术,实则都是由这基本的阴影 ...

  2. OpenGL蓝宝书第九章学习笔记:片段着色器和帧缓存

    前言 本篇在讲什么 OpenGL蓝宝书第九章学习笔记之片段着色器和帧缓存 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C++语法有简单认知 对OpenGL有简单认知 最好是有OpenGL ...

  3. dx12 龙书第八章学习笔记 -- 光照

    1.光照与材质的交互 局部光照模型(local illumination model):每个物体的光照皆独立于其他物体,我们也就可以在处理光照的过程中仅考虑光源直接发出的光,而忽略来自场景中其他物体所 ...

  4. dx12 龙书附录C学习笔记 -- 解析几何学选讲

    本附录中,我们将以向量与点为基石来构建出更为复杂的几何图形. 1.射线.直线以及线段 一条直线可以用线上一点以及与此线平行的向量u来表示,所以,此直线的向量直线方程(vector line equat ...

  5. DirectX 9.0c游戏开发手记之“龙书”第二版学习笔记之1: 开场白

    在开场白之前的说明: 这是"DirectX 9.0c游戏开发手记"的第一部分,叫做"'龙书'第二版学习笔记",讲的是我做"龙书"第二版(原名 ...

  6. 工程伦理第九章学习笔记2020最新

    工程伦理第九章学习笔记2020最新 继续更新

  7. OpenGL学习笔记——纹理贴图

    简单地说,纹理就是矩形的数据数组.例如,颜色数据.亮度数据.颜色和alpha数据.纹理数组中的单个值常常称为纹理单元(texel).纹理贴图之所以复杂,是因为矩形的纹理可以映射到非矩形的区域,并且必须 ...

  8. WebGL three.js学习笔记 纹理贴图模拟太阳系运转

    纹理贴图的应用以及实现一个太阳系的自转公转 点击查看demo演示 demo地址:https://nsytsqdtn.github.io/demo/solar/solar three.js中的纹理 纹理 ...

  9. dx12 龙书第十九章学习笔记 -- 法线贴图

    本章需要掌握的内容:①法线贴图②TBN坐标系 1.使用法线贴图的动机 左图是使用法线贴图后的结果,右图是没有使用法线贴图(使用法线插值)的结果.可以明显感知到,高光在右图中是分布在一条直线上的,并没有 ...

最新文章

  1. SAP PP CS01使用ECR去创建BOM主数据,报错:System status: ECR is not yet approved.
  2. 经典动态规划之过河卒【洛谷 P1002】
  3. matlab滤波仿真
  4. 输入法问题_「图」KB4515384再爆新问题:OOBE时中文输入法阻止创建本地账户
  5. 一文读懂机器学习库graphLab
  6. 一个基于POP3协议进行邮箱账号验证的类
  7. Java中使用MongoTemplate写聚合函数样例
  8. Maven与Ant使用reportNG代替testng
  9. 浅谈PageHelper插件分页实现原理及大数据量下SQL查询效率问题解决
  10. 蜂鸣器基本介绍及实现程序
  11. 揭秘百度新治理结构:马东敏的谣言与李彦宏的用人观
  12. 通信协议:CAN总线
  13. 加入洛谷OJ,开通洛谷博客
  14. C/C++编程学习 - 第22周 ② 非负数的和
  15. 浓缩就是精华——21行python实现输入法自动提示(带过程举例,附录也精彩)
  16. eap方法 华为手机怎么连wifi_怎样为WLAN选择最佳的EAP?
  17. Linux基础——makefile编写
  18. Xcode8 支持 iOS7及以下版本
  19. 关于mybatis 的一些实验
  20. 【转】程序语言不是工具

热门文章

  1. 【妄言之言】西南游记
  2. 李开复万字长文科普人工智能:AI是什么 将带我们去哪儿?
  3. 西林电桥测量法的基本原理中电容Cx计算公式详解
  4. matlab小点轨迹仿真,无碳小车Matlab轨迹仿真及路径图
  5. Android平台签名证书(.keystore)生成
  6. pymatgen正确安装姿势
  7. 2d加速 stm32_emWin做人机用户界面显示刷屏慢? 试试带2D图形加速的GUI图形屏
  8. 英文简历模板计算机专业,计算机专业个人英文简历模板
  9. 微博 - 如何修改微博昵称?
  10. 服务器双机热备软件是什么?有什么作用?有哪些?