DX12 纹理贴图

Posted chenglixue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DX12 纹理贴图相关的知识,希望对你有一定的参考价值。

前言

​ 本篇重在展示如何进行纹理贴图,理论还请看这https://www.cnblogs.com/chenglixue/category/2175285.html

指定uv坐标

​ 为了简易性,只展示立方体盒设置的uv坐标。理论篇提到过,求各顶点的uv坐标用两次线性插值即可求得

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]);
 
	//
	// Create the indices.
	//

	uint32 i[36];

	// Fill in the front face index data
	i[0] = 0; i[1] = 1; i[2] = 2;
	i[3] = 0; i[4] = 2; i[5] = 3;

	// Fill in the back face index data
	i[6] = 4; i[7]  = 5; i[8]  = 6;
	i[9] = 4; i[10] = 6; i[11] = 7;

	// Fill in the top face index data
	i[12] = 8; i[13] =  9; i[14] = 10;
	i[15] = 8; i[16] = 10; i[17] = 11;

	// Fill in the bottom face index data
	i[18] = 12; i[19] = 13; i[20] = 14;
	i[21] = 12; i[22] = 14; i[23] = 15;

	// Fill in the left face index data
	i[24] = 16; i[25] = 17; i[26] = 18;
	i[27] = 16; i[28] = 18; i[29] = 19;

	// Fill in the right face index data
	i[30] = 20; i[31] = 21; i[32] = 22;
	i[33] = 20; i[34] = 22; i[35] = 23;

	meshData.Indices32.assign(&i[0], &i[36]);

    // Put a cap on the number of subdivisions.
    numSubdivisions = std::min<uint32>(numSubdivisions, 6u);

    for(uint32 i = 0; i < numSubdivisions; ++i)
        Subdivide(meshData);

    return meshData;

创建启用纹理

加载DDS文件

​ DDS文件是图形文件格式,它是一个纹理资源,于是我们创建一个纹理结构体用于存储纹理的名字,加载路径,存储资源的默认堆,上传资源的中间层上传堆

struct Texture

	// 自定义的纹理名字
	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;


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(着色器资源视图)描述符来描述如何使用它及它的属性(格式,维度,mipmap最小层级,最大层级及数量,对纹理向量的分量排序,平面切片索引),随后将它们全部存储在SRV堆

//创建SRV堆
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描述符,并放入描述符堆
//-----------------

//获得描述符堆的起始句柄
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

auto grassTex = mTextures["grassTex"]->Resource;
auto waterTex = mTextures["waterTex"]->Resource;
auto fenceTex = mTextures["fenceTex"]->Resource;

D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = ;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = grassTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = -1;
md3dDevice->CreateShaderResourceView(grassTex.Get(), &srvDesc, hDescriptor);

// next descriptor
hDescriptor.Offset(1, mCbvSrvDescriptorSize);

srvDesc.Format = waterTex->GetDesc().Format;
md3dDevice->CreateShaderResourceView(waterTex.Get(), &srvDesc, hDescriptor);

// next descriptor
hDescriptor.Offset(1, mCbvSrvDescriptorSize);

srvDesc.Format = fenceTex->GetDesc().Format;
md3dDevice->CreateShaderResourceView(fenceTex.Get(), &srvDesc, hDescriptor);

将纹理绑定至管线

​ 由于大部分场景中的纹理各不相同,若按更新材质常量缓冲区的方式为几何体添加材质属性,导致所有几何体都使用同一组材质,很显然这无法满足需求。因此,在这里我们将使用纹理映射——以texture map取代材质常量缓冲区来获取材质数据

​ 为了实例的简易性,我们只添加漫反射反射率的texture map

struct Material

    //...
    //当前漫反射反射率纹理在SRV堆中的索引
    int DiffuseSrvHeapIndex = -1;
    //漫反射反射率
    DirectX::XMFLOAT4 DiffuseAlbedo =  1.0f, 1.0f, 1.0f, 1.0f ;	//此值根据需求设置,设为1即不发生任何变化

​ 虽然我们把相应纹理的漫反射反射率加入texture map,但大部分时候根据所需,还需更新纹理的漫反射反射率,因此材质常量缓冲区内的漫反射反射率还需保留

在着色器中更新纹理的漫反射反射率

//.hlsl
//纹理对象
Texture2D    gDiffuseMap : register(t0);
//对纹理进行采样,提取其中当前纹理的漫反射反射率,再乘以常量缓冲区根据需求更新后的漫反射反射率,此即为所求的漫反射反射率
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;

将SRV构成的描述符表绑定至槽

CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);

CD3DX12_ROOT_PARAMETER slotRootParameter[4];
slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);

void TexWavesApp::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);

        //获取对应的SRV描述符起始句柄
        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);
    

采样器

​ 采样器对象用于定义创建纹理资源所用的寻址模式和过滤器,而一个app通常使用多个采样器对象以不同的方式采集纹理

创建采样器描述符

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;

创建并填充采样器描述符堆

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

md3dDevice->CreateSampler(&samplerDesc,mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

将采样器描述符绑定至管线

​ 因为采样器会被着色器使用,因此我们需要将采样器描述符绑定根签名,将其提交至管线

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_LAYOUT);

//绑定采样器描述符至管线
commandList->SetGraphicsRootDescriptorTable(1,samplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

静态采样器

​ 一般而言,app通常不会使用很多采样器,因此D3D提供一种方式来定义采样器数组,使得在不创建采样器堆的情况下也能对采样器进行配置

创建静态采样器描述符

创建静态采样器描述符数组

//一个数组,用于存储六种不同的静态采样器描述符
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> GetStaticSamplers();

std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> TexWavesApp::GetStaticSamplers()

	//app一般只会用到这些描述符的一部分  

	const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
		0, // 着色器寄存器
		D3D12_FILTER_MIN_MAG_MIP_POINT, // 采样纹理的方式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // U轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // V轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // 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,                             // mipmap层级的offset
		8);                               // 各向异性过滤的最大值

	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 TexWavesApp::BuildRootSignature()

	CD3DX12_DESCRIPTOR_RANGE texTable;
	texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);

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

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

在着色器中对纹理采样

定义纹理对象和采样器对象

//纹理对象
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;
    //...

纹理变换

​ 还有两个常量缓冲区中的两个变量我们还未曾提其作用:TexTransform和MatTransform

cbuffer cbPerObject : register(b0)

    //...
	float4x4 gTexTransform;
;

cbuffer cbMaterial : register(b2)

	//...
	float4x4 gMatTransform;
;

struct MaterialConstants

    DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();


struct ObjectConstants

    DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();

​ 这两个变量用于在顶点着色器中对输入的uv坐标进行变换,可以对uv坐标进行缩放、平移、旋转,实现许多效果。如:

  • 让纹理拉伸放大。若当前uv范围为[0,1],放大四倍则为[0,4],若指定为重复寻址模式,则会重复贴图4x4次
  • 通过时间函数平移纹理坐标。如白云飘动
  • 纹理旋转

​ 然后,运用两个变换矩阵可以实现uv动画:一个关于材质的纹理变换(比如水),一个是关于物体属性的纹理变换

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);
	
	// 此处运用纹理变换矩阵
	float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);	//z值不影响uv
	vout.TexC = mul(texC, gMatTransform).xy;
	
    return vout;

HLSL新增内容

cbuffer cbPerObject : register(b0)

    //...
	float4x4 gTexTransform;	//UV缩放矩阵,因为每个贴图的分辨率不同,所以需要进行拉伸
;

struct VertexIn

	//...
	float2 TexC    : TEXCOORD;
;

struct VertexOut

	//...
	float2 TexC    : TEXCOORD;
;

VertexOut VS(VertexIn vin)

    //...
	// Output vertex attributes for interpolation across triangle.
	float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
	vout.TexC = mul(texC, gMatTransform).xy;


float4 PS(VertexOut pin) : SV_Target

    float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
    //...

示例

​ 在此实例,我们将向陆地和河流添加纹理。但存在两个问题:

  1. 给曲面陆地分配纹理。由于陆地是个大的曲面,简单地沿其形状进行纹理拉伸,会导致每个三角形分配到极少的纹素,从而产生失真
  2. 如何让水流纹理沿波浪动起来

​ 解决之道是:

  1. 向陆地网格重复铺设草地纹理,这样就可以实现高分辨率
  2. 时间函数

生成uv坐标

​ 下图左侧是一个位于xz平面的m x n栅格,而右侧是栅格的归一化的uv空间,则uv空间中第i行、第j列的顶点坐标为:\\(u_ij = j · \\Delta_u, v_ij = i · \\Delta_v\\),而\\(\\Delta_u = \\frac1n - 1, \\Delta_v = \\frac1m - 1\\)

因此,生成栅格纹理坐标如下

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;

	//
	// Create the vertices.
	//

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

			// 为了后续铺设纹理,根据栅格拉伸纹理来对应后续铺设的纹理坐标
			meshData.Vertices[i*n+j].TexC.x = j*du;
			meshData.Vertices[i*n+j].TexC.y = i*dv;
		
	
 
    //
	// Create the indices.
	//

	meshData.Indices32.resize(faceCount*3); // 3 indices per face

	// Iterate over each quad and compute indices.
	uint32 k = 0;
	for(uint32 i = 0; i < m-1; ++i)
	
		for(uint32 j = 0; j < n-1; ++j)
		
			meshData.Indices32[k]   = i*n+j;
			meshData.Indices32[k+1] = i*n+j+1;
			meshData.Indices32[k+2] = (i+1)*n+j;

			meshData.Indices32[k+3] = (i+1)*n+j;
			meshData.Indices32[k+4] = i*n+j+1;
			meshData.Indices32[k+5] = (i+1)*n+j+1;

			k += 6; // next quad
		
	

    return meshData;

铺设纹理

先把栅格纹理拉伸放大,再以重复寻址模式来多次铺设纹理

void TexWavesApp::BuildRenderItems()

    //...
    auto gridRitem = std::make_unique<RenderItem>();
    gridRitem->World = MathHelper::Identity4x4();
	XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));	//让uv放大5倍,重复铺设5x5次
    //...

纹理动画

​ 为了让水动起来,需要根据时间函数再纹理平面内平移uv坐标,并以重复寻址模式进行贴图,如此才不会暴露。值得注意的是每帧的位移量要小一点,如此动画才会丝滑

void TexWavesApp::AnimateMaterials(const GameTimer& gt)

	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;

	// 材质属性发生变化,需要更新常量缓冲区
	waterMat->NumFramesDirty = gNumFrameResources;

reference

Directx12 3D 游戏开发实战

以上是关于DX12 纹理贴图的主要内容,如果未能解决你的问题,请参考以下文章

Unity shader - 如何根据纹理贴图生成法线贴图

Direct-X学习笔记--纹理映射

计算机图形学-纹理的应用,环境贴图凹凸贴图法线贴图位移贴图

计算机图形学-纹理的应用,环境贴图凹凸贴图法线贴图位移贴图

计算机图形学-纹理的应用,环境贴图凹凸贴图法线贴图位移贴图

✠OpenGL-5-纹理贴图