[C++]DirectX 12 3D游戏开发实战—第9章 学习笔记02 2019.5.3

仅个人学习用,请勿转载

词汇

漫反射反照率纹理图:diffuse albedo texture map 过滤器:fliter
纹理放大:magnification 常数插值:constant interpolation
线性插值:linear interpolation 最近邻点采样:nearest neighbor point sampling
纹理缩小:minification 三线性过滤:trilinear flitering

内容

9.4.1加载DDS文件

  • 微软提供了一组加载DDS文件的轻量级源代码,但是在这本书撰写的时候,改功能仅支持到DX11,因此书中的配套源代码专门提供了可以读取DDS文件且支持DX12的方法,在common文件夹中DDSTextureLoader:
HRESULT DirectX::CreateDDSTextureFromFile(
_In_ ID3D12Device* device,
_In_ ID3D12GraphicsCommandList* cmdList,
_In_z_ const wchar_t* szFileName,
_Out_Microsoft::WRL::ComPtr& texture,
_Out_Microsoft::WRL::ComPtr& textureUploadHeap);
  1. device:用于创建纹理资源的D3D设备的指针
  2. cmdList:提交GPU命令(如将纹理数据从上传堆复制到默认堆的命令)的命令列表
  3. szFileName:预加载的图像文件名
  4. texture:返回载有图像数据的纹理资源
  5. textureUploadHeap:返回的纹理资源,在此将它当作一个上传堆用于将图像数据复制到默认堆中的纹理资源。在GPU完成命令之前,不能销毁该资源

为用名为WoodCreate01.dds的图像来创建一个对应的纹理,应按照如下方式编写代码:

struct Texture
{
std::string Name;
std::wstring Filename;

Microsoft::WRL::ComPtr Resource = nullptr;
Microsoft::WRL::ComPtr UploadHeap =nullptr;
}
auto woodCreateTex = std::make_unique();
woodCreateTex->Name="woodCreateTex";
woodCreateTex->FileName=L"Textures/WoodCreate01.dds";
ThrowIfFaild(DirectX::CreateDDSTextureFromFile12(
md3dDevice.Get(),mCommandList.Get(),
woodCreateTex->Filename.c_str(),
woodCreateTex->Resource,woodCreateTex->UploadHeap));

9.4.2着色器资源视图堆

  • 创建纹理资源后,还要再为它创建一个SRV(着色器资源视图)描述符,并将其设置到一个根签名参数槽(root signature parameter slot)以供着色器使用,先用ID3D12Device::CreateDescriptorHeap创建描述符堆,借此存储SRV描述符,以下代买创建了可以容纳3个类型为CBV、SRV或UAV描述符的描述符堆,并使之在着色器中可见。
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 3;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIABLE;
ThrowIfFaild(md3dDevice->CreateDescriptorHeap(
	&srvHeapDesc,IID_PPV_ARGS(&mSrvDescriptorHeap)));

9.4.3创建着色器资源视图描述符

  • 创建了SRV堆后便可创建真正的描述符,通过填写D3D12_SHADER_RESOURCE_VIEW_DESC对象来描述SRV描述符,该结构体详述了资源的类型以及其它的信息,如格式、维数、mipmap数量等
typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC
{
	DXGI_FORMAT Format;
	D3D12_SRV_DIMENSION ViewDimension;
	UINT Shader4ComponentMapping;
	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_SHADER_RESOURCE_VIEW_DESC;
typedef struct D3D12_TEX2D_SRV
{
	UINT MostDetailedMip;
	UINT MipLevels;
	UINT PlaneSlice;
	FLOAT ResourceMinLODClamp;
}D3D12_TEX2D_SRV;

对于2D纹理只需关心联合体中D3D12_TEX2D_SRV部分。
1. Format:视图格式。如果待创建的视图有具体格式,就用此资源的DXGI_FORMAT格式来填写此参数,如果是通过无类型格式来创建的该资源,则必须在此为识图填写具体类型。
2. ViewDimension:资源的维数。目前只使用2D纹理,所以该参数指定为D3D12_SRV_DIMENSION_TEXTURE2D。以下是几种常见维数:
(a)D3D12_SRV_DIMENSION_TEXTURE1D
(b)D3D12_SRV_DIMENSION_TEXTURE3D
(c)D3D12_SRV_DIMENSION_TEXTURECUBE
3. Shader4ComponentMapping:在着色器中对纹理进行采样时,它会返回特定纹理坐标处的纹理数据向量。这个字段提供了一种方法:可以将采样时返回的纹理向量中的分量进行重新排序,常用于一些特殊场合,目前不涉及这些情景,所以将它指定为D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING即可。
4. MostDetailedMip:指定此视图中图像细节最详尽的mipmap层级索引,此取值范围在0到MipLevels-1之间
5. MipLevels:此视图的mipmap层级数量,以MostDetailedMip作为起始值。通过这个字段和MostDetailedMip结合起来,就能够指定此视图mipmap层级的一段连续范子范围。可以设置为-1来标识自MostDetailedMip开始到最后一个mipmap层级之间的所有mipmap级别。
6. PlaneSlice:平面切片的索引
7. ResourceMinLODClamp:指定可以访问的最小mipmap层级,设置为0.0即为可以访问所有mipmap层级,如果指定3.0,则为3.0到MIPCount-1的mipmap层级。

  • 构建3个资源描述符来填充上一小节中创建的描述符堆
//假设已创建下列3个纹理资源
//ID3D12Resource* bricktex;
//ID3D12Resource* stoneTex;
//ID3D12Resource* titleTex;
//获取指向描述符堆起始处的指针
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(
	mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = brickTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTUER2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(birckTex.Get(),&srvDesc,hDescriptor);
//偏移到堆中的下一个描述符
hDescriptor.Offset(1,mCbvSrvDescriptorSzie);
srvDesc.Format=stoneTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels=stoneTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(),&srvDesc,hDescriptor);
//偏移到堆中的下一个描述符
hDEscriptor.Offset(1,mCbvSrvDescriptorSize);
srvDesc.Format=titleTex->GetDesc().Format;
srv.Desc.Texture2D.MipLevels = titleTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(titleTex.Get(),&srvDesc,hDescriptor)

9.4.4 将纹理绑定到流水线

  • 目前为止在每次绘制调用时所指定的材质都是由材质常量缓冲区进行更新的,这就意味着在绘制调用中所有几何体都使用同一种材质,这对程序来说是一种极大的限制,纹理映射技术就是用纹理贴图来取代材质常量缓冲区以获取材质数据,这将使每个像素的数据都是灵活可变化的,从而为场景增加更丰富的细节和真实感。
  • 本节中将添加漫反射反照率纹理图以此来给出材质的漫反射反照率分量,影响材质的两个数值gFresnelR0与gRoughness仍在每次绘制调用时由材质常量缓冲区来指定。在使用纹理贴图时,仍需在材质缓冲区中保留gDiffuseAlbedo这个分量。我们在像素着色器中会以下列方式令纹理漫反射反照率与DiffuseAlbedo相组合
//从纹理中提取此像素的漫反射反照率
float4 texDiffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap,pin.TexC);
//将纹理样本与常量缓冲区中的反照率相乘
float4 diffuseAlbedo = texDiffuseAlbedo*gDiffuseAlbedo;

我们通常设DiffuseAlbedo=(1,1,1,1)以使texDiffuseAlbedo不会发生改变,但是有时候对DiffuseAlbedo进行适当调整却可以避免制作新的纹理,假设有一个砖块纹理,贴图师只希望让它的色调略显偏蓝,就可以通过设置DiffuseAlbedo=(0.9,0.9,1,1)削减其中的红色与绿色成分来达到目的。

  • 我们向材质添加了一个索引,借此引用了与此材质相关的纹理描述堆中的一个SRV:
struct Material
{
	...
	//漫反射纹理在SRV堆中的索引
	int DiffuseSrvHeapIndex = -1;
	...
}
  • 假设跟签名被定义为需要把由着色器资源视图构成的描述符绑定至第0个槽处,便可以通过下列代码来使用纹理绘制渲染项:
void CrateApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector& ritems)
{
    UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
    UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
 
	auto objectCB = mCurrFrameResource->ObjectCB->Resource();
	auto matCB = mCurrFrameResource->MaterialCB->Resource();

    // For each render item...
	//对于每个渲染项
    for(size_t i = 0; i < ritems.size(); ++i)
    {
        auto ri = ritems[i];

        cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
        cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
        cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

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

        D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex*objCBByteSize;
		D3D12_GPU_VIRTUAL_ADDRESS matCBAddress = matCB->GetGPUVirtualAddress() + ri->Mat->MatCBIndex*matCBByteSize;

		cmdList->SetGraphicsRootDescriptorTable(0, tex);
        cmdList->SetGraphicsRootConstantBufferView(1, objCBAddress);
        cmdList->SetGraphicsRootConstantBufferView(3, matCBAddress);

        cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
    }
}

  • 纹理资源可以运用于任何着色器
  • 纹理图集可以在以此绘制调用中渲染出多个几何体,因此可以用于优化性能

9.5过滤器(fliter)

9.5.1放大

  • 可以将纹理图中的元素看作从连续图像中采集的离散颜色样本,但并不应该认为它们是特定面积大小的矩形
  • 纹理放大:用少量纹素来覆盖大量像素。
  • 为了给没有纹素点与纹理坐标处的像素对应的情况,对纹素之间的颜色数据进行插值估算从而获得指定纹素处的颜色信息。图形硬件一般都会支持常数插值和线性插值,线性插值更为普遍。
    [C++]DirectX 12 3D游戏开发实战—第9章 学习笔记02 2019.5.3_第1张图片
    图a中,若给出了纹素点,就可以通过构建分段常量函数来求出纹素点之间某处的近似值,由于得到的近似值为最近纹素点的取值,所以这种方法也称为最近邻点采样。图b中通过构建分段线性函数的方法来求出纹素之间某处的近似值。
  • 2D线性插值又称为双线性插值(bilinear interpolation),其处理流程为:给出4个纹素之间的一个纹理坐标,先在水平方向u上进行两次1D线性插值,再在垂直方向上v再进行1此1D内插。
    [C++]DirectX 12 3D游戏开发实战—第9章 学习笔记02 2019.5.3_第2张图片
    [C++]DirectX 12 3D游戏开发实战—第9章 学习笔记02 2019.5.3_第3张图片
  • 在以虚拟视角可以自由移动和探索的交互式3D程序中,纹理放大是一个无法回避的问题
  • 纹理语境中,通过常数插值求出纹素之间纹理坐标处的纹理数据也称为点过滤(pointer flitering),点过滤和线性过滤是DirectX3D中常用的术语

9.5.2缩小

  • 纹理缩小是纹理放大的逆运算。纹理缩小过程中大量的纹素将被映射到少数纹理之上(像素的纹理坐标处没有与之对应的纹理纹素)。
  • 通过平均下采样可以使大的分辨率减少到小的分辨率。mipmap技术则以占用一些额外内存的代价来实现相似功能。
  • 在初期化期间,通过对图像进行下采样来创建mipmap链便可以制作出缩小版的纹理。对mipmap大小进行预计算。
  • mipmap的两种执行方案:
    1. 在纹理贴图时,选择与投影到屏幕上的几何体分辨率最为匹配的mipmap层级,并根据具体需求进行插值计算,这就是针对mipmap的点过滤
    2. 选取待投影到屏幕上的集合分辨率最接近的两个临近mipmap层级,然后对着两种mipmap层级进行插值计算,称为mipmap的线性过滤
      通过从mipmap链中选取恰当的纹理细节级别可以大大减少纹理缩小的运算次数

9.5.3 各项异性过滤

  • 各项异性过滤(anisotropic flitering):该项过滤有助于缓解当多边形法向量与摄像机观察向量之间夹角过大(比如多边形正交于观察窗口)时所导致的失真现象。这种过滤器的开销最大,但是效果非常好
    [C++]DirectX 12 3D游戏开发实战—第9章 学习笔记02 2019.5.3_第4张图片

9.6寻址模式

可将经过插值的纹理定义为一个返回向量值的函数T(u,v)=(r,g,b,a),。Direct3D允许我们采用下列4中不同方式(寻址模式:address mode)来扩充此函数的定义域(解决输入值超出定义域这一问题)

  • 重复寻址模式(wrap):通过在坐标的每个整数点(integer junction)处重复绘制图像来扩充纹理函数
  • 边框颜色寻址模式(border color):通过将每个不在范围[0,1]2内的坐标(u,v)都映射为程序员指定的颜色而扩充纹理函数
  • 钳位寻址模式(clamp):通过将范围[0,1]2外的每个坐标都映射为颜色 T ( u 0 , v 0 ) T(u_0,v_0) T(u0,v0)来扩充纹理函数
  • 镜像寻址模式(mirror):通过在坐标的每个整数点处绘制图像的景象来扩充纹理函数
    在程序中总是要指定一种寻址模式的(默认为重复寻址),因此在范围[0,1]之外的纹理坐标也必然有定义
    重复寻址也许是最常用的一种模式,它允许我们将一种纹理反复平铺到某个表面上。这种寻址模式在不提供额外数据的情况下,也可以提升纹理的分辨率。
    寻址模式由枚举类型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_TEXUTRE_ADDRESS_MODO_MIRROR_ONCE =5;
}D3D12_TEXTURE_ADDRESS_MODE;

9.7采样器对象

采集纹理资源时所用的过滤器和寻址模式都是采样器对象定义的,一个应用程序通常要通过若干个采样器对象以不同的方式来采集纹理

9.7.1创建采样器

为了将采样器绑定到着色器工期使用,就要为采样器对象绑定描述符

CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV,1,0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER);
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV,1,0);

CD3DX12_ROOT_PARAMATER rootParameters[3];
rootParameter[0].InitAsDescriptorTable(1,&descRange[0],
	D3D12_SHADER_VISIBILITY_PIXEL);
rootParameter[1].InitAsDescriptorTable(1,&descRange[1],
	D3D12_SHADER_VISIBILITY_PIXEL);
rootParameter[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结构体实例并将堆类型指定为D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER:

D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = {};
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
descHeapSampler.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIABLE;

ComPtr mSamplerDescriptorHeap;
ThrowIfFaild(mDevice->CreateDescriptorHeap(descHeapSampler,
	_uuidof(ID3D12DescriptorHeap),
	(void**)&mSamplerDescriptorHeap));

有了采样器堆之后就能创建采样器描述符了,此时,我们再通过填写D3D12_SAMPLER_DESC对象来指定寻址模式、过滤器类型以及其它一些参数。

typedef struct D3D12_SAMPLER_DESC
{
	D3D12_FLITER Fliter;
	D3D12_TEXTURE_ADDRESS_MODE AddressU;
	D3D12_TEXTURE_ADDRESS_MODE AddressV;
	D3D12_TEXTURE_ADDRESS_MODE_AddressW;
	FLOAT MipLODBias;
	UINT MaxAnistropy;
	D3D12_COMPARISON_FUNC ComparisonFunc;
	FLOAT BorderColor[4];
	FLOAT MinLOD;
	FLOAT MaxLOD;
}D3D12_SAMPLER_DESC;

1.Flitter:D3D12_FLITTER 枚举类型的成员之一,用来指定采样纹理时所用的过滤方式
2.AddressU:纹理在水平U轴上所使用的寻址方式
3.AddressV:纹理在垂直V轴上采用的寻址方式
4.AddressW:纹理在深度W轴方向上所用的寻址方式(仅限3D纹理)
5.MipLODBias:设置mipmap层级的偏置值。如果将此值指定为0.0,则表示mipmap层级保持不变,如果将这个值设为2,mipmap层级设为3,则将按层级3+2进行采样。
6.MaxAnisotropy:最大各向异性值,此参数的取值区间为[1,16]。只有将Fliter设置为D3D12_FLITER_ANISOTROPIC或D3D12_FLITER_COMPARISON_ANISOTROPIC之后该项才能生效,此数值越大开销越大,但是用户会获得更好的渲染效果。
7.ComparisonFunction:用于实现引用贴图这样异类特殊应用的高级选项,目前只设置为D3D12_COMPARISON_FUNC_ALWAYS。
8.BorderColor:用于指定在D3D12_TEXTURE_ADDRESS_MODE_BOREDER 寻址模式下的边框颜色。
9.MinLOD:可供选择的最小mipmap层级。
10.MaxLOD:可供选择的最大mipmap层级。
常用选项:
1. D3D12_FLITER_MIN_MAG_MIP_POINT:对纹理贴图进行点过滤
2. D3D12_FLITER_MIN_MAG_LINEAR_MIP_POINT:对纹理贴图进行线性过滤,但对mipmap层级进行点过滤。
3. D3D12_FILTER_MIN_MAG_MIP_LINEAR:对纹理图进行双线性过滤,还要在两个临近的较高较低mipmap之间进行线性过滤,因此也称为三线性过滤(trillinear flitering)
4. D3D12_FLITER_ANISOTROPIC:对纹理的放大缩小以及mipmap均采用各向异性过滤。
可以根据这些示例推测出其它可能的组合选项,也可以通过查阅SDK文档来了解D3D12_FLITER的其它枚举类型
创建描述符示例:

D3D12_SAMPLER_DESC samplerDesc = {};
samplerDesc.Flitter = D3D12_FLITTER_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;
samplerDescComparisomFunc = D3D12_COMPARISON_FUN_ALWAYS;
md3dDevive->CreateSampler(&samplerDesc,
mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

将采样器描述符绑定到预定的根签名参数槽:

commandList->SetGraphicsRootDescriptorTable(1,
	mSamplerDescriptorHeap->GetGPUDescriptorHandleForHapStart());

9.7.2 静态采样器

  • Direct3D专门提供了一种特殊的方式来定义采样器数组,使用户可以不创建采样器堆就可以对它们进行配置。
  • CD3DX12_ROOT_SIGNATURE_DESC类有两个不同的Init函数,可以借此为应用程序定义所用的静态采样器数组。
  • 通过结构体D3D12_STATIC_SAMPLER_DESC来描述静态采样器。它与D3D12_SAMPLER_DESC结构体的区别:
  1. 边框颜色必须是下列成员之一:
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;

  1. 含有额外字段来指定着色器寄存器、寄存空间以及着色器的可见性,这些其实都是配置采样器的相关参数,。另外,用户只能定义2032个静态采样器,对于大多数程序来说是够用了,但是如果这个数量真的无法使我们满足,那就只能另外创建采样器堆来使用非静态采样器了。
	std::array GetStaticSamplers();
	std::array TexColumnsApp::GetStaticSamplers()
{
	// Applications usually only need a handful of samplers.  So just define them all up front
	// and keep them available as part of the root signature.  
	//一般只会用到这些采样器中的一部分,所以就将它们全部提前定义好,并作为根签名的一部分保留下来。

	const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
		0, // shaderRegister 着色器寄存器
		D3D12_FILTER_MIN_MAG_MIP_POINT, // filter过滤器类型
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressU U轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressV V轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW W轴上的寻址模式

	const CD3DX12_STATIC_SAMPLER_DESC pointClamp(
		1, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_POINT, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC linearWrap(
		2, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC linearClamp(
		3, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC anisotropicWrap(
		4, // shaderRegister
		D3D12_FILTER_ANISOTROPIC, // filter
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressW
		0.0f,                             // mipLODBias mipmap层级的偏置值
		8);                               // maxAnisotropy 最大各向异性值

	const CD3DX12_STATIC_SAMPLER_DESC anisotropicClamp(
		5, // shaderRegister
		D3D12_FILTER_ANISOTROPIC, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressW
		0.0f,                              // mipLODBias mipmap层级偏置值
		8);                                // maxAnisotropy 最大各向异性值

	return { 
		pointWrap, pointClamp,
		linearWrap, linearClamp, 
		anisotropicWrap, anisotropicClamp };
}


void TexColumnsApp::BuildRootSignature()
{
	CD3DX12_DESCRIPTOR_RANGE texTable;
	texTable.Init(
        D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 
        1,  // number of descriptors 描述符数量
        0); // register t0 着色器寄存器

    // Root parameter can be a table, root descriptor or root constants.
	//根参数可以是描述符表、根描述符或者根常量
    CD3DX12_ROOT_PARAMETER slotRootParameter[4];

	// Perfomance TIP: Order from most frequent to least frequent.
	//优化建议:从最频繁到最不频繁的顺序创建
	slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
    slotRootParameter[1].InitAsConstantBufferView(0); // register b0
    slotRootParameter[2].InitAsConstantBufferView(1); // register b1
    slotRootParameter[3].InitAsConstantBufferView(2); // register b2

	auto staticSamplers = GetStaticSamplers();

    // A root signature is an array of root parameters.
	//跟签名即是一系列根参数
	CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
		(UINT)staticSamplers.size(), staticSamplers.data(),
		D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

    // create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer
	//创建一个具有4个槽位的根签名,第一个指向含有单个着色器资源的描述符表,另外三个各指向一个常量缓冲区视图。
    ComPtr serializedRootSig = nullptr;
    ComPtr 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())));
}

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

通过下列HLSL语法来定义纹理对象,并将其分配给特定的纹理寄存器:

texture2D gDiffuseMap : register(t0);

纹理寄存器由tn来标定。其中整数n表示的是纹理寄存器的槽号,跟签名支出了由槽位参数到着色器寄存器的映射关系,这便是应用程序代码能将SRV绑定到着色器中特定Texture2D对象的原因。
下列HLSL语法定义了多个采样器对象,并将它们分配到特定的采样器寄存器

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

这些采样器对应于上一节中所配置的静态采样器数组。采样器寄存器由sn来指定,其中n表示的是采样器寄存器的槽号。
现在我们在像素着色器中为每个像素都指定其相应的纹理坐标(u,v),并通过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
{
    float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
	
	...
}

通过向Sample方法的第一个参数传递SamplerState对象来描述如何将纹理数据进项采样,再向第二个参数传递像素的纹理坐标为(u,v)。 这个方法会利用SamplerState对象指定的过滤方法,返回纹理图在点(u,v)处的插值颜色。

9.9 板条箱演示程序

9.9.1指定纹理坐标

GeometryGenerator::CreateBox函数用于生成立方体的纹理坐标,因此得以使纹理图像被映射到立方体的每个表面。

GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth, uint32 numSubdivisions)
{
    MeshData meshData;

    //
	// Create the vertices.
	//

	Vertex v[24];

	float w2 = 0.5f*width;
	float h2 = 0.5f*height;
	float d2 = 0.5f*depth;
    
	// Fill in the front face vertex data.
	//填写立方体前表面的顶点数据
	v[0] = Vertex(-w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[1] = Vertex(-w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[2] = Vertex(+w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	v[3] = Vertex(+w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the back face vertex data.
	//填写立方体后表面的顶点数据
	v[4] = Vertex(-w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
	v[5] = Vertex(+w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[6] = Vertex(+w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[7] = Vertex(-w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

	// Fill in the top face vertex data.
	//填写立方体顶面的顶点数据
	v[8]  = Vertex(-w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[9]  = Vertex(-w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[10] = Vertex(+w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	v[11] = Vertex(+w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the bottom face vertex data.
	//填写立方体底面的顶点数据
	v[12] = Vertex(-w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
	v[13] = Vertex(+w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[14] = Vertex(+w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[15] = Vertex(-w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

	// Fill in the left face vertex data.
	//填写左面的顶点数据
	v[16] = Vertex(-w2, -h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
	v[17] = Vertex(-w2, +h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
	v[18] = Vertex(-w2, +h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
	v[19] = Vertex(-w2, -h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

	// Fill in the right face vertex data.
	//填写右面的顶点数据
	v[20] = Vertex(+w2, -h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	v[21] = Vertex(+w2, +h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
	v[22] = Vertex(+w2, +h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
	v[23] = Vertex(+w2, -h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);

	meshData.Vertices.assign(&v[0], &v[24]);
 	...
}

9.9.2 创建纹理

struct Texture
{
	// Unique material name for lookup.
	//便于查找材质所使用的唯一名称
	std::string Name;

	std::wstring Filename;

	Microsoft::WRL::ComPtr Resource = nullptr;
	Microsoft::WRL::ComPtr UploadHeap = nullptr;
};
	std::unordered_map> mTextures;

void CrateApp::LoadTextures()
{
	auto woodCrateTex = std::make_unique();
	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);
}


将每个彼此独立的纹理都存于一个无序映射表(unordered map)中,再根据它们各自的名称来查找相应的纹理。在产品代码中,应该在纹理加载前检测它的数据是否已经存在,以防止同一个纹理被多次加载的情况。

9.9.3 设置纹理

如果纹理已经被创建,并且它的SRV也存在于描述符堆中,就只需要把所需要的纹理设置到根签名参数以将其绑定到 渲染流水线。以供着色器使用。

//获取欲绑定纹理的SRV
CD3DX12_ROOT_DESCRIPTOR_HANDLE tex(
mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex,mCbvSrvDescritorSize);
...
//将纹理绑定到根参数0。根参数指示了纹理将具体绑定到哪一个着色器寄存器槽
cmdList->SetGraphicsRootDescriptorTable(0,Tex);

9.9.4更新部分HLSL代码

// Defaults for number of lights.
//默认光源数量

#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 structures and functions for lighting.
//包含光照的结构体和函数
#include "LightingUtil.hlsl"

Texture2D    gDiffuseMap : register(t0);
SamplerState gsamLinear  : register(s0);


// Constant data that varies per frame.
//每一帧变化的常量数据
cbuffer cbPerObject : register(b0)
{
    float4x4 gWorld;
    float4x4 gTexTransform;//纹理的变换矩阵
};

// Constant data that varies per material.
//绘制过程中需要的杂项常量数据(关于每个材质都需要的常量数据)
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;

    // Indices [0, NUM_DIR_LIGHTS) are directional lights;
    // indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
    // indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
    // are spot lights for a maximum of MaxLights per object.
	//对于每个以MaxLights为光源数量最大值的对象,索引[0,NUM_DIR_LIGHTS]表示的是方向光,[NUM_DIR_LIGHTS,NUM_DIR_LIGHTS+NUM_POINT_LIGHTS]表示点光源
	//[NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)表示聚光灯光源
    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;
	
    // Transform to world space.
	//把坐标变换到世界空间
    float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
    vout.PosW = posW.xyz;

    // Assumes nonuniform scaling; otherwise, need to use inverse-transpose of world matrix.
	//假设这里进行的是等比缩放,否则需要使用世界矩阵的逆转置矩阵
    vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);

    // Transform to homogeneous clip space.
	//将顶点变换到齐次裁剪空间
    vout.PosH = mul(posW, gViewProj);
	
	// Output vertex attributes for interpolation across triangle.
	//为对三角形进行插值操作而输出的顶点属性(纹理)
    float4 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;

    // Interpolating normal can unnormalize it, so renormalize it.
	//对法线插值可能使之非规范化,因此进行规范化处理
    pin.NormalW = normalize(pin.NormalW);

    // Vector from point being lit to eye. 
	//光线经表面上一点反射到观察点这一方向上的向量
    float3 toEyeW = normalize(gEyePosW - pin.PosW);

    // Light terms.
	//光照项
    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;

    // Common convention to take alpha from diffuse material.
	//从漫反射反照率获取alpha值的常见手段
    litColor.a = diffuseAlbedo.a;

    return litColor;
}


9.10纹理变换

关于gTexTransform与gMatTransform。

    float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
    vout.TexC = mul(texC, gMatTransform).xy;

纹理坐标表示的事纹理平面中的2D点,有了这种坐标,就可以对纹理中的样点进行缩放、平移和旋转,例如:
1. 令砖块纹理随着一堵墙的模型拉伸,假设此墙顶点的当前纹理坐标范围为[0,1],在将该纹理放大4倍使它们变换到[0,4]时,该纹理会沿着墙面重复贴图44次。
2. 假设有许多云朵纹理绵延在蓝天背景之下,那么随着时间的函数来评议这些文理坐标,便能实现动态的白云浮过天空的效果。
3. 可以用于实现一些粒子效果,比如随着时间的推移让纹理进行旋转。
在crate演示程序中采用的是单位矩阵进行变换,并未对输入的纹理坐标进行任何修改。
变换2D纹理坐标需要4
4矩阵,因此要先将它扩充为一个4D向量:

vin.TexC—>float4(vin.TexC,0.0f,1.0f);

在完成乘法运算后再将所得的4D向量中的z分量和w分量去掉,就可以使之强制转换回2D向量。

float4 TeXC = mul(float4(vin.TexC,0.0f,1.0f),gTexTransform);
vout.TexC=mul(TexC,gMatTransform).xy;

gTexTransform和gMatTransform一种是关于材质的纹理变换,一种是关于物体属性的纹理变换。
纹理矩阵中平移了z坐标不会对纹理造成影响。

你可能感兴趣的:(C++,DirectX12)