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;
//...
示例
在此实例,我们将向陆地和河流添加纹理。但存在两个问题:
- 给曲面陆地分配纹理。由于陆地是个大的曲面,简单地沿其形状进行纹理拉伸,会导致每个三角形分配到极少的纹素,从而产生失真
- 如何让水流纹理沿波浪动起来
解决之道是:
- 向陆地网格重复铺设草地纹理,这样就可以实现高分辨率
- 时间函数
生成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 纹理贴图的主要内容,如果未能解决你的问题,请参考以下文章