https://github.com/jiabaodan/Direct12BookReadingNotes

回顾我们已经在第四章使用过的纹理,深度缓存和后台缓存都是一张2D纹理,它们由ID3D12Resource包含的值为D3D12_RESOURCE_DIMENSION_TEXTURE2D的D3D12_RESOURCE_DESC::Dimension属性的接口来表示。
纹理和缓冲(buffer)不同,它不仅仅是保存数据,它还可以包含纹理细化等级(mipmap levels),并且GPU可以对它做特殊操作,比如应用过滤器(filters)和多重纹理映射。因为要支持这些特殊操作,所以它的格式有限制。格式通过DXGI_FORMAT枚举来定义:
纹理格式可以定义完整的类型,比如:DXGI_FORMAT_R32G32B32_FLOAT,包含3个32位浮点数;也可以定义无类型格式,比如:DXGI_FORMAT_R8G8B8A8_TYPELESS。
根据DX11的文档:定义完整类型的格式,可以进行运行时的优化。也就是说为了性能,只有当你真正需要无类型格式,否则都定义成完整类型的格式。
纹理可以绑定到渲染管线很多阶段,重用的方式是做为一个渲染目标,或者着色器的资源。为了让纹理用作渲染目标和着色器资源,我们需要创建2个描述:1、D3D12_DESCRIPTOR_HEAP_TYPE_RTV;2、D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:
// Bind as render target.
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = …;
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = …;
cmdList->OMSetRenderTargets(1, &rtv, true, &dsv);
// Bind as shader input to root parameter.
CD3DX12_GPU_DESCRIPTOR_HANDLE tex = …;
cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, tex);
资源描述本质上做了2件事情:1、告诉D3D该资源将如何被使用;2、如果资源定义时是无类型的,那么需要定义它的类型。
为了添加贴图u,v坐标,修改定点结构如下:
struct Vertex
{
	DirectX::XMFLOAT3 Pos;
	DirectX::XMFLOAT3 Normal;
	DirectX::XMFLOAT2 TexC;
};
>>>>>>>>>>>>>>>>>>
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),然后应用于多个物体。这样可以避免多次资源加载,减少DrawCall,优化性能。
对于实时图形应用,DDS(DirectDraw Surface format)文件格式更好,GPU可以直接原生使用很多它的文件格式;并且它支持压缩,可以让GPU原生解压缩。
DDS是3D图形更理想的格式是因为,它支持专门针对3D图形设计的特殊格式和纹理类型。它本质上是针对GPU设计的图像类型。比如DDS支持下面的特征:
DDS文件支持DXGI_FORMAT枚举中的部分类型(不是全部),对于非压缩的数据可以使用:
为了让纹理能够快速应用,我们需要将它们放到GPU内存中。为了解决这些需求,D3D支持压缩纹理格式:BC1, BC2, BC3, BC4, BC5, BC6, 和 BC7:
一个压缩的纹理只能用以着色器输入参数,不能用以渲染目标。
因为块压缩算法是以4x4像素块来工作的,所以纹理的大小必须是4的倍数。
再次声明,使用这个格式的好处是它们可以压缩保存在GPU内存,并且当要使用的时候可以直接被GPU解压缩;另一个好处是可以节省你的硬盘空间。
下面是两种可以将传统文件(.bmp .png等)转换成DDS文件的方法:
下面的命令就是一个例子,输入一个bricks.bmp文件,导出一个bricks.dds文件并设置格式为BC3_UNORM并且创建10 mipmaps:
texconv -m 10 -f BC3_UNORM treeArray.dds
微软提供了另一个叫texassemble的命令行工具,可以用来创建保存了texture arrays, volume maps, and cube maps的DDS文件,它的文档和下载链接:https://directxtex.codeplex.com/wikipage?title=Texassemble&referringTitle=Documentation
VS2015有一个内置的图像编辑器可以支持DDS文件。你可以直接拖拽DDS文件到VS中查看。
微软提供了一个轻量级源代码来加载DDS文件:
https://github.com/Microsoft/DirectXTK/wiki/DDSTextureLoader
但是在写本书的时候,这个代码只能支持DX11。我们需要修改DDSTextureLoader.h/.cpp文件来支持DX12:
HRESULT DirectX::CreateDDSTextureFromFile12(
	_In_ ID3D12Device* device,
	_In_ ID3D12GraphicsCommandList* cmdList,
	_In_z_ const wchar_t* szFileName,
	_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>& texture,
	_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>& textureUploadHeap);
为了创建加载一个叫WoodCreate01.dds的纹理,我们可以这样写:
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;
};
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));
当一个纹理资源被创建以后,我们需要创建一个可以让我们把它设置到根签名参数槽来让着色器程序使用的SRV描述。为了达到这个目的,我们需要先使用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_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
	&srvHeapDesc,
	IID_PPV_ARGS(&mSrvDescriptorHeap)));
当我们创建后SRV对后,我们需要创建真正的描述。一个SRV描述是通过填D3D12_SHADER_RESOURCE_VIEW_DESC对象来创建:
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纹理,我们只关心共用体中的3D12_TEX2D_SRV部分:
下面的代码为3个资源创建了描述:
// Suppose the following texture resources are already created.
// ID3D12Resource* bricksTex;
// ID3D12Resource* stoneTex;
// ID3D12Resource* tileTex;
// Get pointer to the start of the heap.
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(
	mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
	
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = bricksTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(bricksTex.Get(), &srvDesc, hDescriptor);
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = stoneTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = stoneTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(), &srvDesc, hDescriptor);
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = tileTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = tileTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(), &srvDesc, hDescriptor);
目前为止,我们通过逐绘制调用更新材质常量缓冲来指定材质。这代表当前绘制调用中几何体都会使用一种材质。这个就限制了我们不能逐像素的改变材质,所以我们的场景就缺少细节。解决想法是通过纹理映射的纹理贴图来获取材质数据而不是常量缓冲。这就可以进行逐像素的改变材质来增加细节和真实感。
本章中,我们增加了一个diffuse albedo纹理贴图来定义材质的diffuse albedo组件。FresnelR0和Roughness材质值将继续逐绘制调用的在常量缓冲中更新,但是在Normal Mapping章中,我们将介绍如何通过纹理贴图来进行逐像素定义。我们将合并纹理的diffuse albedo值在像素着色器中:
// Get diffuse albedo at this pixel from texture.
float4 texDiffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC);
// Multiple texture sample with constant buffer albedo.
float4 diffuseAlbedo = texDiffuseAlbedo * gDiffuseAlbedo;
通常情况下我们设置DiffuseAlbedo=(1,1,1,1),不去改变texDiffuseAlbedo。但是有些特殊效果需要调整这个值(比如,砖块调整成淡蓝色)。
我们在材质中增加一个索引,代表对应SRV在堆中的索引:
struct Material
{
	…
	// Index into SRV heap for diffuse texture.
	int DiffuseSrvHeapIndex = -1;
	…
};
然后假设根签名已经定义了一个SRV绑定到0槽上,我们就可以使用纹理绘制物体通过下面的代码:
void CrateApp::DrawRenderItems(
	ID3D12GraphicsCommandList* cmdList,
	const std::vector<RenderItem*>& 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);
	}
}
纹理图集可以增加性能,因为可以绘制多个几何体在同一个绘制调用中。即使在DX12中绘制调用的开销已经明显较少了,但是减少绘制调用还是非常值得的。
放大有两种方式:常量差值(下图a)和线性差值(下图b)。

2D线性差值叫做双线性差值,如下图所示:

下图展示了使用真实数据资源,两种差值方式的对比,线性差值要更平滑一些,但是比起原本的资源,看起来效果也不是那么好:

常量差值也叫点滤波器(point filtering),线性差值也叫线性滤波器(linear filtering),它们是D3D的术语。
常量和线性滤波器依然适用于缩小。还有一种叫做多级渐进纹理的技术可以高效模拟缩小操作,但是会占用更多的内存。在运行时,程序员需要通过图形硬件会基于mipmap做两件不同的事情:

这个滤波器帮助较少当几何体的法向量和摄像机看向的方向夹角变大时,产生的纹理变形和扭曲。这个滤波器的运算成本是最大的。

D3D允许我们通过4种方式来扩展纹理获取颜色的方式:包裹(wrap)、边缘色彩(border color)、clamp和镜像(mirror)。




地址模式的默认值是wrap。贴图如果是无缝的,wrap就可以得到很好的效果。
地址模式在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;
使用什么滤波器和地址模式是通过采样对象来定义的,一个应用程序一般都需要多个采样对象。
为了绑定采样器到着色器程序,我们需要描述到采样对象,下面的代码是一个例子:
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(3, rootParameters, 0,
	nullptr,
	D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_
如果我们要设置一个采样器描述,我们需要一个采样器堆。采样器堆是通过填充一个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_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_SAMPLER_DESC;
下面是一些常用的D3D12_FILTER类型:
你可以从这些例子中找到其他的排列,或者查看SDK文档中的D3D12_FILTER枚举。
下面的代码是一个如何创建采样器描述的例子:
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,
	samplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
一个应用程序通常只使用少数采样器,所以,D3D提供了一个特殊捷径来定义一个采样器数组,并且不需要再创建采样器堆。CD3DX12_ROOT_SIGNATURE_DESC类的Init函数有两个参数,可以让你定义一个叫做静态采样器的数组。静态采样器通过D3D12_STATIC_SAMPLER_DESC结构来描述,这个结构和D3D12_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个静态采样器,一般情况下应用程序也不需要这么多;如果你真需要更多,可以通过采样器堆来使用非静态采样器。
在我们的Demo中我们使用静态采样器,下面的代码就是使用静态采样器的例子。我们会定义很多静态采样器,但是Demo中可能不需要用到,对于多出来的采样器,不会造成太多的问题:
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> 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
		D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressV
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW
		
	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
		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
		8); // maxAnisotropy
		
	return {
		pointWrap, pointClamp,
		linearWrap, linearClamp,
		anisotropicWrap, anisotropicClamp };
}
void TexColumnsApp::BuildRootSignature()
{
	CD3DX12_DESCRIPTOR_RANGE texTable;
	texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
	
	// Root parameter can be a table, root descriptor or root constants.
	CD3DX12_ROOT_PARAMETER slotRootParameter[4];
	slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
	slotRootParameter[1].InitAsConstantBufferView(0);
	slotRootParameter[2].InitAsConstantBufferView(1);
	slotRootParameter[3].InitAsConstantBufferView(2);
	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_
	// create a root signature with a single slot which points to a
	// descriptor range consisting of a single constant buffer
	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())));
}
一个纹理对象在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
{
	float4 diffuseAlbedo =
	gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) *
	gDiffuseAlbedo;
	…
这个方法返回了差值后的颜色值。
现在我们复习一下,添加纹理到一个箱子上的主要的点。
GeometryGenerator::CreateBox方法创建了纹理坐标,所以纹理可以覆盖到整个箱子上。
GeometryGenerator::MeshData
GeometryGenerator::CreateBox( float width, float height, float depth, uint32 numSubdivisions)
{
	MeshData meshData;
	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, 1.0f);
	v[1] = Vertex(-w2, +h2, -d2, …, 0.0f, 0.0f);
	v[2] = Vertex(+w2, +h2, -d2, …, 1.0f, 0.0f);
	v[3] = Vertex(+w2, -h2, -d2, …, 1.0f, 1.0f);
	
	// Fill in the back face vertex data.
	v[4] = Vertex(-w2, -h2, +d2, …, 1.0f, 1.0f);
	v[5] = Vertex(+w2, -h2, +d2, …, 0.0f, 1.0f);
	v[6] = Vertex(+w2, +h2, +d2, …, 0.0f, 0.0f);
	v[7] = Vertex(-w2, +h2, +d2, …, 1.0f, 0.0f);
	
	// Fill in the top face vertex data.
	v[8] = Vertex(-w2, +h2, -d2, …, 0.0f, 1.0f);
	v[9] = Vertex(-w2, +h2, +d2, …, 0.0f, 0.0f);
	v[10] = Vertex(+w2, +h2, +d2, …, 1.0f, 0.0f);
	v[11] = Vertex(+w2, +h2, -d2, …, 1.0f, 1.0f);
我们在初始化的时候创建纹理:
// Helper structure to group data related to the texture.
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;
};
std::unordered_map<std::string, std::unique_ptr<Texture>> mTextures;
void CrateApp::LoadTextures()
{
	auto woodCrateTex = std::make_unique<Texture>();
	woodCrateTex->Name = "woodCrateTex";
	woodCrateTex->Filename = L"Textures/WoodCrate01.dds";
	ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.mCommandList.Get(), 
		woodCrateTex->Filename.c_str(),
		woodCrateTex->Resource, woodCrateTex->UploadHeap));
	mTextures[woodCrateTex->Name] = std::move(woodCrateTex);
}
当一个纹理创建好,并且一个SRV被创建到一个描述堆的时候,就可以绑定纹理到渲染管线:
// Get SRV to texture we want to bind.
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(
	mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
	
tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);
…
// Bind to root parameter 0. The root parameter description specifies which
// shader register slot this corresponds to.
cmdList->SetGraphicsRootDescriptorTable(0, tex);
// 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 gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// 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.
	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(gsamAnisotropicWrap, 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 albedo.
	litColor.a = diffuseAlbedo.a;
	
	return litColor;
}
有两个常量缓冲的变量我们目前还没有讨论:gTexTransform和gMatTransform。它们是顶点着色器用来变换纹理坐标的:
// Output vertex attributes for interpolation across triangle.
float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
vout.TexC = mul(texC, gMatTransform).xy;
纹理坐标表示2D纹理上的点,所以我们可以移动,旋转,缩放它们。
使用4 x 4矩阵变换2D坐标,我们需要把坐标增加到4D:
vin.TexC ---> float4(vin.Tex, 0.0f, 1.0f)
变换过后,需要把坐标变回到2D:
vout.TexC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform).xy;
我们之所以分开为2个变换矩阵:gTexTransform和gMatTransform是因为有些时候材质需要变换纹理(比如流动的水流),但是有些时候纹理变换是物体对象的一个属性。

根据下图可以看出,网格的纹理坐标为:


创建代码如下:
GeometryGenerator::MeshData GeometryGenerator::CreateGrid(float width, float depth, uint32 m, uint32 n)
{
	MeshData meshData;
	uint32 vertexCount = m*n;
	uint32 faceCount = (m-1)*(n-1)*2;
	float halfWidth = 0.5f*width;
	float halfDepth = 0.5f*depth;
	float dx = width / (n-1);
	float dz = depth / (m-1);
	**float du = 1.0f / (n-1);
	float dv = 1.0f / (m-1);**
	meshData.Vertices.resize(vertexCount);
	
	for(uint32 i = 0; i < m; ++i)
	{
		float z = halfDepth - i*dz;
		for(uint32 j = 0; j < n; ++j)
		{
			float x = -halfWidth + j*dx;
			meshData.Vertices[i*n+j].Position = XMFLOAT3(x, 0.0f, z);
			meshData.Vertices[i*n+j].Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
			meshData.Vertices[i*n+j].TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
			
			**// Stretch texture over grid.
			meshData.Vertices[i*n+j].TexC.x = j*du;
			meshData.Vertices[i*n+j].TexC.y = i*dv;**
		}
	}
为了将纹理铺到整个网格,我们选择地址模式和缩放纹理坐标:
void TexWavesApp::BuildRenderItems()
{
	auto gridRitem = std::make_unique<RenderItem> ();
	gridRitem->World = MathHelper::Identity4x4();
	XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));
	…
}
为了让水可以流动,我们添加一个AnimateMaterials函数,我们使用wrap地址模式在一个无缝纹理上。下面的代码就是一个例子:
void TexWavesApp::AnimateMaterials(const GameTimer& gt)
{
	// Scroll the water material texture coordinates.
	auto waterMat = mMaterials["water"].get();
	float& tu = waterMat->MatTransform(3, 0);
	float& tv = waterMat->MatTransform(3, 1);
	
	tu += 0.1f * gt.DeltaTime();
	tv += 0.02f * gt.DeltaTime();
	
	if(tu >= 1.0f)
		tu -= 1.0f;
	if(tv >= 1.0f)
		tv -= 1.0f;
		
	waterMat->MatTransform(3, 0) = tu;
	waterMat->MatTransform(3, 1) = tv;
	
	// Material has changed, so need to update cbuffer.
	waterMat->NumFramesDirty = gNumFrameResources;
}
1. 修改Crate Demo达到不同的寻址模式和滤波器效果:
代码在https://github.com/jiabaodan/Direct12BookReadingNotes中的Chapter9_Exercises_1_CrateTexMode工程

修改renderitem中的TexTransform或者材质中的MatTransform:
// 对纹理放大5倍
XMStoreFloat4x4(&boxRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));
然后修改像素着色器中的采样器
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicClamp, pin.TexC) * gDiffuseAlbedo;
2. 使用DirectX Texture Tool创建mipmap,每个等级使用不同的颜色,然后在Crate Demo中增加控制距离,观察在点和线性滤波器下的mipmap的变化:
代码在https://github.com/jiabaodan/Direct12BookReadingNotes中的Chapter9_Exercises_2_CrateMipmap工程

根据要求创建好mipmap问题,然后加载进去即可
3&4. 用乘法合并两张纹理,并旋转:
代码在https://github.com/jiabaodan/Direct12BookReadingNotes中的Chapter9_Exercises_4_CrateTexAnim工程

根签名里加个槽,然后把纹理放进去,然后修改Shader:
VS:
// 按照中心点旋转
vin.TexC -= float2(0.5, 0.5);
// Output vertex attributes for interpolation across triangle.
   float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
   vout.TexC = mul(texC, gMatTransform).xy;
// 按照中心点旋转
vout.TexC += float2(0.5, 0.5);
PS:
float4 diffuseAlbedo = (gDiffuseMapFront.Sample(gsamAnisotropicWrap, pin.TexC) 
	* gDiffuseMapBack.Sample(gsamAnisotropicWrap, pin.TexC)) * gDiffuseAlbedo;
6. 在之前的LitColumns Demo里加上材质:
Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第九章:贴图
原文:https://www.cnblogs.com/lonelyxmas/p/10817154.html