纹理贴图是一种将图像数据映射到网络三角形上的技术,可以使我们为场景增添更多的细节,令它更具真实感。如下:

学习目标:
- 学习如何将局部纹理映射到网络的三角形上
- 探究如何创建和启用纹理
- 学会如何通过纹理过滤来创建更为平滑的图像效果
- 探索如何用寻址模式来进行多次贴图
- 探究如何将多个纹理进行组合,从而创建出新的纹理与特效
- 学习如何通过纹理动画来创建一些基本效果
一些纹理的format

- 还有一种无类型格式,我们仅用它来预留一块内存,并要在稍后将纹理绑定到渲染流水线的时候指出如何重新解释其中的数据。DXGI_FORMAT_R8G8B8A8_TYPELESS这个就是为每个元素预留了4个8位分量
- 纹理可以绑定到渲染流水线的不同阶段,一个常见的例子是即可将纹理用作渲染目标,又能把它作为着色器资源。一个纹理可以当作渲染目标,也可以充当着色器资源,但是不能同时的作用,可以串行使用,也就是先渲染目标,然后在当作着色器资源这种。要使纹理扮演渲染目标与着色器资源这两种角色,我们就需要为此纹理资源创建两个描述符:一个存于渲染目标堆中(以D3D12_DESCRIPTOR_HEAP_TYPE_RTV描述),另一个位于着色器资源堆中(以D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV描述)

纹理坐标与纹理数据源
这里因为我们要引入纹理坐标所以我们的Vertex顶点的输入就需要更改,还有输入的描述
cpp
struct Vertex
{
DirectX::XMFLOAT3 Pos;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT2 TexC;
};
m_InputLayout = {
{"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}
};
纹理数据源
对于图像文件BMP,DDS,TGA,PNG等游戏应用程序会在加载期间将图像文件载入ID3D12Resource对象。对于实时图形应用程序来说,DDS图形文件格式(DirectDraw图面格式,DirectDraw Surface format,DDS)是一种比较好的选择。
DDS格式概述
DDS对于3D图形来说是一种理想的格式,因为它支持一些专用于3D图形的特殊格式以及纹理类型。从本质上来讲,它实为一种针对GPU而专门设计的图像格式。例如,DDS纹理满足用于3D图形开发的以下特征:
- mipmap
- GPU能自行解压的压缩格式
- 纹理数组
- 立方体图(cube map,也有译作立方体贴图)
- 体纹理(Volume texture,也有译作体积纹理、立方体纹理等)
- 更节省磁盘空间
DDS格式能够支援不同的像素格式。像素格式由枚举DXGI_FORMAT中的成员来表示,但是并非所有的格式都适用于DDS纹理。非压缩图像数据一般会采用下列格式

随着虚拟场景中纹理数量的大幅增长,对GPU端显存的需求也在迅速增加。为了缓解这些内存的需求压力,Direct3D支持下列几种压缩纹理格式:


创建DDS文件
https://developer.nvidia.com/nvidia-texture-tools-adobe-photoshop
https://github.com/microsoft/DirectXTex

cpp
texconv -m 10 -f BC3_UNORM test.png
Microsoft (R) DirectX Texture Converter [DirectXTex] Version 2025.7.10.1
Copyright (C) Microsoft Corp.
reading test.png (540x544 R8G8B8A8_UNORM 2D α:Opaque) as (540x544,10 BC3_UNORM 2D α:Opaque)
writing test.dds
texconv -m 10 -f BC3_UNORM test.png展示了向texconv输入一个png文件,并通过它来输出格式为BC3_UNORM且具有mipmap链的DDS文件
创建以及启用纹理
加载DDS文件
这里作者表面写了一个脚本来方便Direct12 来读取DDS,我们可以在这里得到DirectXTex/DDSTextureLoader/DDSTextureLoader12.cpp at main · microsoft/DirectXTex
这里可以看到我们使用的函数
cpp
HRESULT __cdecl LoadDDSTextureFromFile(
_In_ ID3D12Device* d3dDevice,
_In_z_ const wchar_t* szFileName,
_Outptr_ ID3D12Resource** texture,
std::unique_ptr<uint8_t[]>& ddsData,
std::vector<D3D12_SUBRESOURCE_DATA>& subresources,
size_t maxsize = 0,
_Out_opt_ DDS_ALPHA_MODE* alphaMode = nullptr,
_Out_opt_ bool* isCubeMap = nullptr);
为了根据DDS来创建对应的texture 。我们需要定义Texture数据结构,包括名字,resource等
cpp
struct Texture
{
std::string Name;
std::wstring FileName;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
std::unique_ptr<uint8_t[]> Data;
std::vector<D3D12_SUBRESOURCE_DATA> SubResourceData;
};
- 这里其中texture会在这个程序中帮你创建resource
cpp
const CD3DX12_HEAP_PROPERTIES defaultHeapProperties(D3D12_HEAP_TYPE_DEFAULT);
hr = d3dDevice->CreateCommittedResource(
&defaultHeapProperties,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_ID3D12Resource, reinterpret_cast<void**>(texture));
- 数据最终是给到subresources中,也就是我们需要通过上传堆去copy这resource到我们实际的堆中。这里我们可以看到它的dds包含的mipmap的数量还有一些操作都会给到subresource
cpp
// Create the texture
size_t numberOfResources = (resDim == D3D12_RESOURCE_DIMENSION_TEXTURE3D)
? 1 : arraySize;
numberOfResources *= mipCount;
numberOfResources *= numberOfPlanes;
if (numberOfResources > D3D12_REQ_SUBRESOURCES)
return E_INVALIDARG;
subresources.reserve(numberOfResources);
- 我们定义上传堆的时候是需要知道数据的大小的,也就是resource desc,这里可以使用d3dx12的辅助函数,这个函数的作用就是在已经有了pDestinationResource的基础上,也就是我们这里已经有了默认堆,配置了对应的desc.这样子资源通过一系列的计算得到对应的字节数。
cpp
//------------------------------------------------------------------------------------------------
// Returns required size of a buffer to be used for data upload
inline UINT64 GetRequiredIntermediateSize(
_In_ ID3D12Resource* pDestinationResource,
_In_range_(0,D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
_In_range_(0,D3D12_REQ_SUBRESOURCES-FirstSubresource) UINT NumSubresources) noexcept
{
auto Desc = pDestinationResource->GetDesc();
UINT64 RequiredSize = 0;
ID3D12Device* pDevice = nullptr;
pDestinationResource->GetDevice(IID_PPV_ARGS(&pDevice));
pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, 0, nullptr, nullptr, nullptr, &RequiredSize);
pDevice->Release();
return RequiredSize;
}
- 然后我们就可以通过subresource和上传堆将资源传入到我们的默认堆中了
cpp
void MaterialApp::LoadTextureResources()
{
std::unique_ptr<Texture> woodCrateTex;
woodCrateTex->Name = "woodCrateTex";
woodCrateTex->FileName = L"resources/wood.dds";
ThrowIfFailed(DirectX::LoadDDSTextureFromFile(m_Device.Get(),
woodCrateTex->FileName.c_str(),
woodCrateTex->Resource.GetAddressOf(),
woodCrateTex->Data,
woodCrateTex->SubResourceData
));
// subresource has data
CD3DX12_RESOURCE_BARRIER pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(woodCrateTex->Resource.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
m_CommandList->ResourceBarrier(1, &pBarrier);
D3D12_HEAP_PROPERTIES p_p = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
UINT byteSize = GetRequiredIntermediateSize(woodCrateTex->Resource.Get(), 0, woodCrateTex->SubResourceData.size());
D3D12_RESOURCE_DESC p_d = CD3DX12_RESOURCE_DESC::Buffer(byteSize);
ThrowIfFailed(m_Device->CreateCommittedResource(&p_p,
D3D12_HEAP_FLAG_NONE,
&p_d,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(woodCrateTex->UploadHeap.GetAddressOf())
));
UpdateSubresources(
m_CommandList.Get(),
woodCrateTex->Resource.Get(),
woodCrateTex->UploadHeap.Get(), 0,
0, woodCrateTex->SubResourceData.size(), woodCrateTex->SubResourceData.data()
);
pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(woodCrateTex->Resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
m_CommandList->ResourceBarrier(1, &pBarrier);
m_Textures[woodCrateTex->Name] = std::move(woodCrateTex);
}
着色器资源视图堆
创建了纹理资源后,我们还需要为它创建一个SRV(着色器资源视图)描述符,并将其设置到一个根签名参数槽(root signature parameter slot),以供着色器程序使用。为此,首先要用ID3D12Device::CreateDescriptorHeap函数创建描述符堆,用来存储SRV描述符。
cpp
void MaterialApp::CreateSRVDescriptorHeap()
{
D3D12_DESCRIPTOR_HEAP_DESC srvdesc{};
srvdesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvdesc.NumDescriptors = 3;
srvdesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
srvdesc.NodeMask = 0;
ThrowIfFailed(m_Device->CreateDescriptorHeap(&srvdesc, IID_PPV_ARGS(m_SRVDescriptorHeap.GetAddressOf())));
}
创建着色器资源视图描述符
就是创建对应的描述符,需要用到device的CreateShaderResourceView
cpp
virtual void STDMETHODCALLTYPE CreateShaderResourceView(
_In_opt_ ID3D12Resource *pResource,
_In_opt_ const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc,
_In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) = 0;
cpp
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_RAYTRACING_ACCELERATION_STRUCTURE_SRV RaytracingAccelerationStructure;
} ;
} 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部分
- Format:视图的格式。如果待创建视图的资源有具体的格式,就使用该格式。如果是通过无类型的DXGI_Format比如上面的DXGI_FORMAT_R8G8B8A8_TYPELESS,来定义的资源,则一定要在这里填写具体的类型,因为只有这样GPU才能知道怎样解释并处理这一数据。
- ViewDimension:资源的维数。目前我们只使用2D纹理,所以将此参数指定为D3D12_SRV_DIMENSION_TEXTURE2D。以下是几种常见的纹理维数:

- Shader4ComponentMapping:在着色器中对纹理进行采样时,它将返回特定纹理坐标处的纹理数据向量。这个字段提供一种方法,可以将采样时所返回的纹理向量中的分量进行重新排序。例如可以用此字段将红色分量与绿色分量互换。该方法常用于一些特殊的场合,但本书中并不涉及到这些情景。因此,只要将它指定为D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING即可。这样一来,向量分量的顺序将不会改变,它会以纹理资源中默认的数据顺序直接返回。
- MostDetailedMip:指定此视图中图像细节最详尽的mipmap层级的索引。此参数的取值范围在0与MipLevels -1之间
- MipLevels:自MostDetailedMip算起,待创建视图的mipmap层级数量。通过将这个字段与MostDetailedMip配合起来,我们就能指定此视图mipmap层级的连续子范围。可以将此字段设置为-1.用来表示自MostDetailMip始至最后一个mipmap层级之间的所有mipmap级别
- PlaneSlice :平面切片的索引
- ResourceMinLODClamp:指定可以访问最小的mipmap层级。设置为0.0表示可以访问所有的mipmap层级。将此参数指定为3.0,则表示从3.0到MipCount -1的所有mipmap层级都可以访问
这是一个构建三个纹理视图的例子
cpp
void MaterialApp::CreateSRVView()
{
ComPtr<ID3D12Resource> p1;
ComPtr<ID3D12Resource> p2;
ComPtr<ID3D12Resource> p3;
CD3DX12_CPU_DESCRIPTOR_HANDLE cpuHandle(m_SRVDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
srvDesc.Format = p1->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = p1->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
m_Device->CreateShaderResourceView(p1.Get(), &srvDesc, cpuHandle);
cpuHandle.Offset(1, m_CbvSrvUavDescriptorSize);
srvDesc.Format = p2->GetDesc().Format;
srvDesc.Texture2D.MipLevels = p2->GetDesc().MipLevels;
m_Device->CreateShaderResourceView(p2.Get(), &srvDesc, cpuHandle);
cpuHandle.Offset(1, m_CbvSrvUavDescriptorSize);
srvDesc.Format = p3->GetDesc().Format;
srvDesc.Texture2D.MipLevels = p3->GetDesc().MipLevels;
m_Device->CreateShaderResourceView(p3.Get(), &srvDesc, cpuHandle);
}
将纹理绑定到流水线上
这一节中将添加漫反射反照率纹理贴图,以此来给出材质的漫反射反照率分量。在使用这个纹理贴图的时候,我们仍需要在材质常量缓冲区中保留gDiffuseAlbedo分量。其与纹理的结合方式为:

我们通常设DiffuseAlbedo=(1,1,1,1)但是有的时候,比如一个板砖的纹理,但是,贴图师希望它的色调略显蓝色,便可以通过设置DiffuseAlbedo=(0.9,0.9,1,1)来削减红色和绿色成分达到这个目的
我们向材质的定义中添加了一个索引,借此引用了与此材质相关联的纹理描述符堆中的一个SRV:
cpp
struct Material
{
...
//漫反射纹理在SRV堆中的索引。在第9章纹理贴图时会用到
int DiffuseSrvHeapIndex = -1;
...
};
接下来假设,根签名被定义为需要把着色器资源视图构成的描述符表绑定到第0个槽处,那么,我们便可以通过下列代码来使用纹理绘制渲染项
cpp
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(m_SRVDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->mat->DiffuseSrvHeapIndex, m_CbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(0, tex);
}

过滤器
放大
原本一个纹理的大小是64 x 64,如果它就在屏幕上占据这个位置,那么一个纹素对应一个像素点。但是当我们靠经这个物体的时候,此时其64x64的表面就需要覆盖更大的表面,这也就意味着将纹理放大。比如一个256的分辨率的屏幕,你一个纹理的像素要在4个像素上显示。此时就需要针对这个纹理的区域进行插值,比如你根据三角形的顶点的uv得到了顶点的值,然后插值得到了屏幕的其中一个像素的uv的值,然后此时这个uv就会乘64得到他在纹理上的像素位置,得到一个float,然后根据线性插值或者最近邻采样的方式来得到最终的纹素的值。左边是最近邻,右边是线性插值

这是双线性插值,一般纹理就是使用二维版

两种方式的对比:左边是最近邻,可以看到明显的块状,右边是线性插值可以看到比较平滑。不管是哪种办法,经过插值以后数据不如真实数据(如更高分辨率的纹理)来得完美


缩小
缩小就是不同的现象,类似于256的纹理大小,但是该物体的表面映射到屏幕的64 x 64的区域。此时你通过映射就会发现,一个像素点对应多个纹素。如果是取近值,就会出现一大片像素同样的属性,会产生较大的锯齿的现象。
另一个办法就是双线性插值,同样的,我们一个像素还是会有对应的在纹理上的位置,然后双线性插值就可以了。这样可以得到比较平滑的结果这是一定的。
但是这种现象无法解决一种事情,纹理过大,则需要把纹理变小后贴到空间的物体之上。纹理图变小了,那texel就更密集了,从而一个像素大小可能会覆盖很多个texel。问题就来了,映射结果是随机选一个幸运纹素还是求平均呢?
如下面这张图,左边是参考图(理想结果),右边是实际用按每个像素对纹理图进行映射的结果。

其从近到远就会发生下面的像素覆盖情况,可以看到远处的像素覆盖了多个纹理,就可能导致这种摩尔纹的现象,也算一种采样不足的现象。

解决办法:
- 增加采样,在远处多采样,然后求平均
- 使用MipMap
mipmap
从直观上来讲,我们求和平均的过程可以预先的处理,也就是mipmap,我们通过平均周围的像素得到下采样的像素值,比如256x256 -> 64 x 64。

在运行的时候,一般从以下两种方案中选择
- 在纹理贴图时,选择与待投影到屏幕上的几何体分辨率最为匹配的mipmap层级,并根据具体需求选用常数插值或线性插值。这便是针对**mipmap的点过滤,**该名称的由来是因为此种方法与常数插值很相似------我们仅选取与目标分辨率最近邻的那个mipmap层级并用它进行纹理贴图
- 在纹理贴图时,选取与待投影到屏幕上的几何体分辨率最为匹配的两个邻近的mipmap层级(一个稍大于屏幕几何体分辨率的,一个稍小的)。接下来对这两种mipmap层级分别应用常量过滤或线性过滤,以生成它们各自相应的纹理颜色。最后,在这两种插值纹理之间再次进行颜色的插值计算。这个过程称为mipmap的线性过滤。原因是这种方法与线性插值比较相似------我们需要对目标分辨率最近邻的两个mipmap层级进行插值计算。
各向异性过滤
还有一种各向异性过滤的过滤器类型。该过滤器有助于缓解多边形法向量与相机观察向量之间夹角过大(比如当多边形正交于观察窗口)所导致的失真的现象。这种过滤器开销最大,但是其矫正失真的效果非常好。比如下面的木箱上面,可以看到其像素的跨度非常大,若采用线性过滤就会导致左边的模糊,右边是各向异性过滤的效果

MipMap分层,只做了主对角线的图,各向异性又多了很多图,解决了竖向和横向的长方形的查询。比如上面的箱子的变化就可以使用宽度变化较大的下面的图,去得到对应的像素。

寻址模式
寻址模式的意思就是当出现uv的坐标不在0到1的范围的时候,映射到纹理上的值。包括direct运行以下的4种模式:
- 重复地址寻址模式:通过在坐标的每个整数点处重复绘制图像来拓扑纹理函数,意思就是每个0到1的范围都重复的绘制,比如2,3, 4就对应1

- 边框寻址模式:程序员指定一种颜色,任何不在0~1范围内的uv映射到的都是一种颜色

- 钳位寻址模式:通过将范围外的每个uv都映射为范围内最近的点的uv对应的纹理值

- 镜像寻址模式:通过在坐标的每个证书点处绘制图像的镜像来扩充纹理函数,它这个镜像还是奇数对应的

在Direct3D中寻址模式由枚举类型D3D12_Texture_address_mode来表示

采样器对象
从前面可以看出,在运用纹理的过程中,除了纹理数据本身之外,还有两个相关的重要概念,即纹理过滤以及寻址模式。采集纹理资源时所用的过滤器和寻址模式都是由采样器对象来定义的,一个应用程序通常需要采样若干个采样器对象以不同的方式来采集纹理
创建采样器
采样器也是一种资源,他就是指定了过滤方式和寻址模式,所以我们需要将用描述符去引用它,然后我们需要在根签名中让shader知道怎么用这个资源绑定槽位
下面的代码展示了这样的一个根签名实例,它的第二个槽位获取了一个描述符表,此表中存有1个与采样器寄存器槽0相绑定的采样器描述符。
cpp
void MaterialApp::createRootSignature()
{
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); //sampler
descRange[3].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
CD3DX12_ROOT_PARAMETER rootParameter[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);
}
我们需要创建对应的描述符和对应的描述符堆去使用这个sampler的资源。我们在创建对应的描述符的时候会填写D3D12_SAMPLER_DESC
cpp
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;
- Filter:过滤方式
- AddressU:纹理在水平u轴方向上所用的寻址模式

- AddressV:v方向上的
- AddressW:深度方向(3D纹理)
- MipLODBias:设置,mipmap层级的偏置值,如果将此值指定为0.0,则表示mipmap层级保持不变:若将此参数设置为2,mipmap层级设置为3,则采样第5层的(3 + 2)
- MaxAnisotropy:最大各向异性值,此参数的取值区间为[1, 16]。只有将Filter设置为D3D12_FILTER_ANISOTROPIC 或 D3D12_FILTER_COMPARISON_ANISOTROPIC之后该项才能生效,此数值越大开销越大,效果越好
- ComparisonFunc:用于实现阴影贴图这样一类特殊应用的高级选项。目前设置为D3D12_COMPARISON_FUNC_ALWAYS
- BorderColor:用于指定在D3D12_TEXTURE_ADDRESS_MODE_BORDER,这个边框寻址模式下的颜色。
- MinLOD:可供选择的最小的mipmap层级
- MaxLOD:最大
以下是 D3D12_FILTER 常用的选项:
- D3D12_FILTER_MIN_MAG_MIP_POINT:对纹理图与mipmap层级即最接近目标物体分辨率的mipmap层级)进行点过滤
- D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT:对纹理图进行双线性过滤,但对mipmap层级(即最接近目标物体分辨率的mipmap层级)进行点过滤
- D3D12_FILTER_MIN_MAG_MIP_LINEAR:对纹理图进行双线性过滤,同时还要在两个邻近的mimap之间进行线性过滤(三线性过滤)
- D3D12_FILTER_ANISOTROPIC:对于纹理的放大,缩小以及mipmap均采用各向异性过滤
下面是一种方式:
cpp
void MaterialApp::createSamplerDescriptor()
{
D3D12_DESCRIPTOR_HEAP_DESC samplerDesc = {};
samplerDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
samplerDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
samplerDesc.NodeMask = 0;
samplerDesc.NumDescriptors = 1;
ComPtr<ID3D12DescriptorHeap> samplerDescHeap;
ThrowIfFailed(m_Device->CreateDescriptorHeap(&samplerDesc, IID_PPV_ARGS(samplerDescHeap.GetAddressOf())));
D3D12_SAMPLER_DESC sd{};
sd.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
sd.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sd.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sd.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sd.MinLOD = 0;
sd.MaxLOD = D3D12_FLOAT32_MAX;
sd.MipLODBias = 0.0f;
sd.MaxAnisotropy = 1;
sd.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
CD3DX12_CPU_DESCRIPTOR_HANDLE handle(samplerDescHeap->GetCPUDescriptorHandleForHeapStart());
m_Device->CreateSampler(&sd, handle);
}
使用的时候就是设置对应的parameter的槽对应那个handle

静态采样器
事实证明,图形应用程序通常不会使用过多的采样器。为此,Direct3D专门提供了一种特殊的方式来定义采样器数组,使用户可以在不创建采样器堆的情况下也能对它们进行配置。
CD3DX12_ROOT_SIGNATURE_DESC类有两种参数不同的Init函数,用户可以借此为应用程序定义所用的静态采样器数组。我们通过结构体D3D12_STATIC_SAMPLER_DESC来描述静态采样器,它与D3D12_SAMPLER_DESC结构体比较相似,但在下面存在区别
- 边框颜色,静态采样器的边框颜色必须为下列成员之一
cpp
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个静态采样器,对于大多数应用程序而言是够用了。然而,如果这个数量真的无法使我们满足,那就只有另建采样器堆来使用非静态采样器了。
演示程序中使用的是静态采样器,下述代码展示了怎样来定义它们。注意,我们的演示程序可能并不会用到所有的这些静态采样器,但却会一直保留这些定义,这样一来,在需要的时候便触手可及。
这些预留的静态采样器并不多,而且它们也不会影响后续再定义的或用或不用的其它采样器。
cpp
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> MaterialApp::GetStaticSamplers()
{
// 应用程序一般只会用到这些采样器中的一部分
//所以将它们全部提前定义好,并作为根签名的一部分保留下来
const CD3DX12_STATIC_SAMPLER_DESC pointWrap{
0,
D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP
};
const CD3DX12_STATIC_SAMPLER_DESC pointClamp{
1,
D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP
};
const CD3DX12_STATIC_SAMPLER_DESC linearWrap{
2,
D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP
};
const CD3DX12_STATIC_SAMPLER_DESC linearClamp{
3,
D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP
};
const CD3DX12_STATIC_SAMPLER_DESC anisotropicWrap{
4,
D3D12_FILTER_ANISOTROPIC,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
0.0f,
8
};
const CD3DX12_STATIC_SAMPLER_DESC anisotropicClamp{
5,
D3D12_FILTER_ANISOTROPIC,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP
};
return std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6>{pointWrap, pointClamp,
linearWrap, linearClamp,
anisotropicWrap, anisotropicClamp};
}
void MaterialApp::createRootSignature()
{
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_ALL);
slotRootParameter[1].InitAsConstantBufferView(0);
slotRootParameter[2].InitAsConstantBufferView(1);
slotRootParameter[3].InitAsConstantBufferView(2);
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> staticSamplers = GetStaticSamplers();
CD3DX12_ROOT_SIGNATURE_DESC rootSignature(4, slotRootParameter, (UINT)staticSamplers.size(),
staticSamplers.data(), D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> error;
ComPtr<ID3DBlob> sig;
HRESULT hr = D3D12SerializeRootSignature(&rootSignature, D3D_ROOT_SIGNATURE_VERSION_1_0, sig.GetAddressOf(), error.GetAddressOf());
if (error != nullptr)
{
::OutputDebugStringA((char*)error->GetBufferPointer());
}
ThrowIfFailed(hr);
ThrowIfFailed(m_Device->CreateRootSignature(0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(m_RootSignature.GetAddressOf())));
}
在着色器中对纹理进行采样
通过下面的方式定义纹理的寄存器
cpp
Texture2D gDiffuseMap : register(t0);
注意,纹理寄存器由tn来标定,其中,整数n表示的是纹理寄存器的槽号。此根签名的定义指出了由槽位参数到着色器寄存器的映射关系,这便是应用程序代码能将SRV绑定到着色器中特定的Texture2D对象的原因。
类似地,下列HLSL语法定义了多个采样器对象,并将它们分别分配到了特定的采用器寄存器。
cpp
SamplerState gsamPointWrap :register(s0);
SamplerState gsampPointClamp :register(s1);
SamplerState gsamLinearWrap :register(s2);
SamplerState gsamLinearClamp :register(s3);
SamplerState gsamAnisotropicWrap :register(s4);
SamplerState gsamAnisotropicClamp :register(s5);
这些采样器对应于我们在上一节中所配置的静态采样器数组。注意,采样器寄存器由sn来指定,其中整数n表示的是采样器寄存器的槽号。
现在,我们在像素着色器中为每个像素指定相应的纹理坐标(u,v),并通过Texture2D::Sample方法正式进行纹理采样
cpp
#include "Default.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);
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方法的第一个参数传递SampleState对象来描述如何对纹理数据进行采样,再向第二个参数传递像素的纹理坐标(u,v)。这个方法将利用SampleState对象所指定的过滤方法,返回纹理图在点(u, v)处的插值颜色
板条箱演示程序
首先还是我们需要去生成陆地和海洋的演示程序,刚好就是多次熟练。绘制一件事情,首先最主要的就是准备绘制的资源,也就是顶点等数据。类似生成render data。然后再去配置shader, rootSignature, inputlayout, pso。最后就是绘制
数据的生成
这里的数据,本次程序中用到的是陆地海洋程序还有box的几何数据
我们首先是需要根据生成的几何数据提取出我们需要的数据
对于box,因为我们不同的位置它的法线和切线是不一样的,所以我们需要类似与ue的vertexinstance的情况,也就是我们需要对于原始的数据创建多个不同的法线和tangent
cpp
GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth)
{
MeshData meshData;
Vertex v[24];
float w2 = 0.5f * width;
float h2 = 0.5f * height;
float d2 = 0.5f * depth;
//front
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);
//back
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);
// left (左面,法向量指向X轴负方向)
v[8] = Vertex(-w2, -h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f); // 左下后
v[9] = Vertex(-w2, h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); // 左上后
v[10] = Vertex(-w2, h2, d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f); // 左上前
v[11] = Vertex(-w2, -h2, d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f); // 左下前
// right (右面,法向量指向X轴正方向)
v[12] = Vertex(w2, -h2, d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f); // 右下前
v[13] = Vertex(w2, h2, d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); // 右上前
v[14] = Vertex(w2, h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f); // 右上后
v[15] = Vertex(w2, -h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f); // 右下后
//up (上面,法向量向上)
v[16] = Vertex(-w2, h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[17] = Vertex(w2, h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[18] = Vertex(w2, h2, d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
v[19] = Vertex(-w2, h2, d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
//down (下面,法向量向下)
v[20] = Vertex(-w2, -h2, d2, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[21] = Vertex(w2, -h2, d2, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[22] = Vertex(w2, -h2, -d2, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
v[23] = Vertex(-w2, -h2, -d2, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
// 索引数组:每个面2个三角形(6个索引),共6个面,36个索引
uint32 indices[36] = {
// 1. 前面(front,法向量 -z):v0→v1→v2(三角形1),v0→v2→v3(三角形2)
0, 1, 2, 0, 2, 3,
// 2. 后面(back,法向量 +z):v4→v5→v6(三角形1),v4→v6→v7(三角形2)
4, 5, 6, 4, 6, 7,
// 3. 左面(left,法向量 -x):v8→v9→v10(三角形1),v8→v10→v11(三角形2)
8, 9, 10, 8, 10, 11,
// 4. 右面(right,法向量 +x):v12→v13→v14(三角形1),v12→v14→v15(三角形2)
12, 13, 14, 12, 14, 15,
// 5. 上面(up,法向量 +y):v16→v17→v18(三角形1),v16→v18→v19(三角形2)
16, 17, 18, 16, 18, 19,
// 6. 下面(down,法向量 -y):v20→v21→v22(三角形1),v20→v22→v23(三角形2)
20, 21, 22, 20, 22, 23
};
meshData.Indices32.assign(&indices[0], &indices[36]);
meshData.Vertices.assign(&v[0], &v[24]);
return meshData;
}
这里我们一个几何就对应一个geo的数据,这样比较好维护,其中的sub实际上就是对应不同的材质,但是这里我们的材质是一样的。
cpp
void MaterialApp::createGeometries()
{
//first get data
GeometryGenerator geoGen;
GeometryGenerator::MeshData land = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);
GeometryGenerator::MeshData box = geoGen.CreateBox(10, 10, 10);
// land and box
std::vector<Vertex> landVertex(land.Vertices.size());
for (UINT i = 0; i < land.Vertices.size(); ++i)
{
Vertex v;
v.Pos = land.Vertices[i].Position;
v.Pos.y = GetHillHeight(v.Pos.x, v.Pos.z);
v.Normal = GetHillsNormal(v.Pos.x, v.Pos.z);
v.TexC = land.Vertices[i].TexC;
landVertex[i] = std::move(v);
}
//land geometry
std::unique_ptr<d3dUtil::MeshGeometry> landGeo;
UINT vbByteSize = sizeof(Vertex) * landVertex.size();
UINT ibByteSize = sizeof(uint16_t) * land.GetIndices16().size();
ThrowIfFailed(D3DCreateBlob(vbByteSize, landGeo->VertexBufferCPU.GetAddressOf()));
ThrowIfFailed(D3DCreateBlob(ibByteSize, landGeo->IndexBufferCPU.GetAddressOf));
CopyMemory(landGeo->VertexBufferCPU->GetBufferPointer(), landVertex.data(), vbByteSize);
CopyMemory(landGeo->IndexBufferCPU->GetBufferPointer(), land.GetIndices16().data(), ibByteSize);
landGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), landGeo->VertexBufferCPU->GetBufferPointer(),
vbByteSize, landGeo->VertexBufferUploader);
landGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), landGeo->IndexBufferCPU->GetBufferPointer(),
ibByteSize, landGeo->IndexBufferUploader);
landGeo->DisposeUploaders();
landGeo->VertexByteStride = sizeof(Vertex);
landGeo->VertexByteSize = vbByteSize;
landGeo->IndexBufferByteSize = ibByteSize;
landGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
d3dUtil::SubmeshGeometry landSub;
landSub.IndexCount = land.GetIndices16().size();
landSub.StartIndexLocation = 0;
landSub.BaseVertexLocation = 0;
landGeo->DrawArgs["land"] = std::move(landSub);
m_Geometries["land"] = std::move(landGeo);
std::vector<Vertex> boxVertex(box.Vertices.size());
for (UINT i = 0; i < box.Vertices.size(); ++i)
{
Vertex v;
v.Pos = box.Vertices[i].Position;
v.Normal = box.Vertices[i].Normal;
v.TexC = box.Vertices[i].TexC;
boxVertex[i] = std::move(v);
}
// box geometry
std::unique_ptr<d3dUtil::MeshGeometry> boxGeom;
vbByteSize = sizeof(Vertex) * boxVertex.size();
ibByteSize = sizeof(uint16_t) * box.GetIndices16().size();
ThrowIfFailed(D3DCreateBlob(vbByteSize, boxGeom->VertexBufferCPU.GetAddressOf()));
ThrowIfFailed(D3DCreateBlob(ibByteSize, boxGeom->IndexBufferCPU.GetAddressOf()));
CopyMemory(boxGeom->VertexBufferCPU->GetBufferPointer(), boxVertex.data(), vbByteSize);
CopyMemory(boxGeom->IndexBufferCPU->GetBufferPointer(), box.GetIndices16().data(), ibByteSize);
boxGeom->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(),
boxGeom->VertexBufferCPU->GetBufferPointer(), vbByteSize, boxGeom->VertexBufferUploader);
boxGeom->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(),
boxGeom->IndexBufferCPU->GetBufferPointer(), ibByteSize, boxGeom->IndexBufferUploader);
boxGeom->VertexByteStride = sizeof(Vertex);
boxGeom->VertexByteSize = vbByteSize;
boxGeom->IndexBufferByteSize = ibByteSize;
boxGeom->IndexFormat = DXGI_FORMAT_R16_UINT;
boxGeom->DisposeUploaders();
d3dUtil::SubmeshGeometry boxSub;
boxSub.IndexCount = box.GetIndices16().size();
boxSub.StartIndexLocation = 0;
boxSub.BaseVertexLocation = 0;
boxGeom->DrawArgs["box"] = std::move(boxSub);
m_Geometries["box"] = std::move(boxGeom);
// sea Geometry
m_SeaUploaderBuffer = std::make_unique<UploadBuffer<Vertex>>(m_Device.Get(), land.Vertices.size(), false);
for (UINT i = 0; i < land.Vertices.size(); ++i)
{
Vertex v;
v.Pos.x = land.Vertices[i].Position.x;
v.Pos.y = land.Vertices[i].Position.y;
v.Pos.z = land.Vertices[i].Position.z;
v.Normal.x = 0.0f;
v.Normal.y = 1.0f;
v.Normal.z = 0.0f;
v.TexC.x = land.Vertices[i].TexC.x;
v.TexC.y = land.Vertices[i].TexC.y;
m_SeaUploaderBuffer->CopyData(i, v);
}
std::unique_ptr<d3dUtil::MeshGeometry> seaGeo = std::make_unique<d3dUtil::MeshGeometry>();
vbByteSize = sizeof(Vertex) * land.Vertices.size();
ibByteSize = sizeof(uint16_t) * land.GetIndices16().size();
ThrowIfFailed(D3DCreateBlob(vbByteSize, seaGeo->VertexBufferCPU.GetAddressOf()));
ThrowIfFailed(D3DCreateBlob(ibByteSize, seaGeo->IndexBufferCPU.GetAddressOf()));
CopyMemory(seaGeo->VertexBufferCPU->GetBufferPointer(), landVertex.data(), vbByteSize);
CopyMemory(seaGeo->IndexBufferCPU->GetBufferPointer(), land.GetIndices16().data(), ibByteSize);
seaGeo->Name = "sea";
seaGeo->VertexBufferGPU = (ID3D12Resource*)*m_SeaUploaderBuffer;
seaGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), seaGeo->IndexBufferCPU->GetBufferPointer(),
ibByteSize, seaGeo->IndexBufferUploader);
seaGeo->VertexByteStride = sizeof(Vertex);
seaGeo->VertexByteSize = sizeof(Vertex) * landVertex.size();
seaGeo->IndexBufferByteSize = sizeof(uint16_t) * land.GetIndices16().size();
d3dUtil::SubmeshGeometry seaSub;
seaSub.IndexCount = land.GetIndices16().size();
seaSub.StartIndexLocation = 0;
seaSub.BaseVertexLocation = 0;
seaGeo->DrawArgs["sea"] = std::move(seaSub);
m_Geometries[seaGeo->Name] = std::move(seaGeo);
}
这样我们就把所有的几何数据都绑定到了对应的缓冲区中了。接下来就是纹理数据的postload,本来它就应该和几何数据一起load的,因为理论上submesh是根据纹理定义的。
创建纹理
我们上面创建了几何的数据,接下来是载入材质的数据
通过下面的方式在初始化阶段利用dds文件创建纹理
cpp
struct Texture
{
std::string Name;
std::wstring FileName;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
std::unique_ptr<uint8_t[]> Data;
std::vector<D3D12_SUBRESOURCE_DATA> SubResourceData;
DirectX::DDS_ALPHA_MODE AlphaMode;
bool is_cube;
};
std::unordered_map<std::string, std::unique_ptr<Texture>> m_Textures;
void MaterialApp::LoadTextureResources()
{
std::unique_ptr<Texture> woodCrateTex = std::make_unique<Texture>();
woodCrateTex->Name = "woodCrateTex";
woodCrateTex->FileName = L"resources/wood.dds";
woodCrateTex->AlphaMode = DirectX::DDS_ALPHA_MODE_OPAQUE;
woodCrateTex->is_cube = false;
ThrowIfFailed(DirectX::LoadDDSTextureFromFile(m_Device.Get(),
woodCrateTex->FileName.c_str(),
woodCrateTex->Resource.GetAddressOf(),
woodCrateTex->Data,
woodCrateTex->SubResourceData,
0,
&woodCrateTex->AlphaMode,
&woodCrateTex->is_cube
));
CD3DX12_RESOURCE_BARRIER pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(woodCrateTex->Resource.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
m_CommandList->ResourceBarrier(1, &pBarrier);
// subresource has data
CD3DX12_HEAP_PROPERTIES pP(D3D12_HEAP_TYPE_UPLOAD);
UINT byteSize = GetRequiredIntermediateSize(woodCrateTex->Resource.Get(), 0,
woodCrateTex->SubResourceData.size());
CD3DX12_RESOURCE_DESC pD = CD3DX12_RESOURCE_DESC::Buffer(byteSize);
ThrowIfFailed(m_Device->CreateCommittedResource(&pP,
D3D12_HEAP_FLAG_NONE,
&pD, D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr, IID_PPV_ARGS(woodCrateTex->UploadHeap.GetAddressOf())
));
UpdateSubresources(m_CommandList.Get(),
woodCrateTex->Resource.Get(),
woodCrateTex->UploadHeap.Get(),
0, 0, woodCrateTex->SubResourceData.size(),
woodCrateTex->SubResourceData.data());
pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(woodCrateTex->Resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_COMMON);
m_CommandList->ResourceBarrier(1, &pBarrier);
m_Textures[woodCrateTex->Name] = std::move(woodCrateTex);
}
设置纹理
我们需要把上面创建得到的纹理绑定到对应的srv堆中,用view去绑定。然后我们就可以在绑定根签名后使用它。
cpp
void MaterialApp::CreateSRVView()
{
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc{};
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.NodeMask = 0;
srvHeapDesc.NumDescriptors = 1;
ThrowIfFailed(m_Device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(m_SRVDescriptorHeap.GetAddressOf())));
//1. create vie
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
ID3D12Resource* woodTex = m_Textures["woodCrateTex"]->Resource.Get();
srvDesc.Format = woodTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Texture2D.MipLevels = woodTex->GetDesc().MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
CD3DX12_CPU_DESCRIPTOR_HANDLE handle(m_SRVDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
m_Device->CreateShaderResourceView(m_Textures["woodCrateTex"]->Resource.Get(),
&srvDesc, handle);
}
更新hlsl部分代码
我们需要修改Default.hlsl代码,将纹理贴图的内容放进去,这样后续的shader都能使用
cpp
#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 "LightingUtils.hlsl"
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gWorldInvTranspose;
float4x4 gTexTransform;
};
cbuffer cbMaterial : register(b1)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
cbuffer cbPass : register(b2)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float gcbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
Light gLights[MaxLights];
};
struct VertexIn
{
float3 PosL : POSITIONT;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
};
struct VertexOut
{
float4 PosH : SV_Position;
float3 PosW : POSITIONT;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
};
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);
cpp
#include "Default.hlsl"
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut) 0.0f;
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosW = posW;
vout.NormalW = mul(vin.NormalL, (float3x3) gWorldInvTranspose);
vout.PosH = mul(posW, gViewProj);
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;
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;
}
纹理变化
上面的代码中对uv出现了gTexTransform和gMatTransform的变换,这两个变量用于在顶点着色器中对输入的纹理坐标进行变换:
cpp
float4 TexC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
vout.TexC = mul(TexC, gMatTransform).xy;
return vout;
纹理坐标表示的是纹理平面中的2D点。有了这种坐标,我们就能像其它的2D点一样,对纹理中的样点进行缩放、平移和旋转。下面是一些适用于纹理变换的实例:其实就是对纹理坐标进行变换,然后配合上之前的寻址模式,即可采样不同的纹理值,实现纹理效果的动态变化等

因为这里是纯粹的刚体变化,非投影那种,所以这里只需要根据正常的矩阵计算后取前二的值即可。

cpp
vout.TexC = mul(TexC, gMatTransform).xy;
这里为什么用两个独立的纹理变换矩阵呢?是因为有一种情况是针对纹理的变换,比如平面上模拟水。还有是物体导致的纹理坐标的变换。由于这里使用的是2d纹理坐标,所以我们只关心前两个坐标轴的变换情况。例如,如果纹理矩阵平移了z坐标,这并不会对纹理坐标造成任何影响。
创建默认纹理
这块我不想去找海洋还有草地的纹理了,直接考虑默认纹理的创建。
cpp
void MaterialApp::LoadDefaultWhiteTexture()
{
std::unique_ptr<Texture> defaultWhiteTex = std::make_unique<Texture>();
defaultWhiteTex->Name = "default";
defaultWhiteTex->FileName = L"";
defaultWhiteTex->AlphaMode = DirectX::DDS_ALPHA_MODE_OPAQUE;
defaultWhiteTex->is_cube = false;
const UINT width = 1;
const UINT height = 1;
const DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;
const UINT mipLevels = 1;
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
format,
width,
height,
1,
mipLevels,
1,
0,
D3D12_RESOURCE_FLAG_NONE);
ThrowIfFailed(m_Device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultWhiteTex->Resource.GetAddressOf())));
const UINT pixelSize = 4; //R8G8B8A8 4个字节
const UINT rowPitch = width * pixelSize;
const UINT slicePitch = rowPitch * height;
defaultWhiteTex->Data = std::make_unique<uint8_t[]>(slicePitch); //数组的首地址
memset(defaultWhiteTex->Data.get(), 0xFF, slicePitch);//数组的首地址开始全部赋值为1
D3D12_SUBRESOURCE_DATA subResourceData{};
subResourceData.pData = defaultWhiteTex->Data.get();
subResourceData.RowPitch = rowPitch;
subResourceData.SlicePitch = slicePitch;
defaultWhiteTex->SubResourceData.push_back(subResourceData);
CD3DX12_HEAP_PROPERTIES uploaderHeapProps(D3D12_HEAP_TYPE_UPLOAD);
UINT byteSize = GetRequiredIntermediateSize(defaultWhiteTex->Resource.Get(), 0, defaultWhiteTex->SubResourceData.size());
CD3DX12_RESOURCE_DESC uploadDesc = CD3DX12_RESOURCE_DESC::Buffer(byteSize);
m_Device->CreateCommittedResource(&uploaderHeapProps,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(defaultWhiteTex->UploadHeap.GetAddressOf()));
CD3DX12_RESOURCE_BARRIER pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(
defaultWhiteTex->Resource.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST);
m_CommandList->ResourceBarrier(1, &pBarrier);
UpdateSubresources(m_CommandList.Get(), defaultWhiteTex->Resource.Get(),
defaultWhiteTex->UploadHeap.Get(),
0, 0,
defaultWhiteTex->SubResourceData.size(),
defaultWhiteTex->SubResourceData.data());
pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(defaultWhiteTex->Resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_COMMON);
m_CommandList->ResourceBarrier(1, &pBarrier);
m_Textures[defaultWhiteTex->Name] = std::move(defaultWhiteTex);
}
修改一下view的创建
cpp
void MaterialApp::CreateSRVDescriptorHeap()
{
D3D12_DESCRIPTOR_HEAP_DESC srvdesc{};
srvdesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvdesc.NumDescriptors = 2;
srvdesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
srvdesc.NodeMask = 0;
ThrowIfFailed(m_Device->CreateDescriptorHeap(&srvdesc, IID_PPV_ARGS(m_SRVDescriptorHeap.GetAddressOf())));
}
void MaterialApp::CreateSRVView()
{
//1. create vie
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
ID3D12Resource* woodTex = m_Textures["woodCrateTex"]->Resource.Get();
srvDesc.Format = woodTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Texture2D.MipLevels = woodTex->GetDesc().MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
CD3DX12_CPU_DESCRIPTOR_HANDLE handle(m_SRVDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
m_Device->CreateShaderResourceView(m_Textures["woodCrateTex"]->Resource.Get(),
&srvDesc, handle);
ID3D12Resource* defaultTex = m_Textures["default"]->Resource.Get();
srvDesc.Format = defaultTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = defaultTex->GetDesc().MipLevels;
handle.Offset(1, m_CbvSrvUavDescriptorSize);
m_Device->CreateShaderResourceView(m_Textures["default"]->Resource.Get(),
&srvDesc, handle);
}
创建帧资源
首先是对应的纹理资源,数据我们先制作,在update的时候在对应的帧资源中更新。然后还有就是每帧更新的帧资源,以及物体的const buffer。
纹理数据
这里的DiffuseSrvHeapIndex对应的就是在srv中我们的材质的偏移
cpp
void MaterialApp::CreateMaterial()
{
std::unique_ptr<d3dUtil::Material> grass = std::make_unique<d3dUtil::Material>();
grass->Name = "land";
grass->MatCBIndex = 0;
grass->DiffuseSrvHeapIndex = 1;
grass->DiffuseAlbedo = DirectX::XMFLOAT4(0.2f, 0.6f, 0.2f, 1.0f);
grass->FresnelR0 = DirectX::XMFLOAT3(0.01f, 0.01f, 0.01f);
grass->Roughness = 0.125f;
//当前这种水的材质定义得并不是很好,但是由于我们还未学会所需的全部渲染工具(如透明度、环境反射等),因此暂时先用这些数据解决
auto water = std::make_unique<d3dUtil::Material>();
water->Name = "sea";
water->MatCBIndex = 1;
water->DiffuseSrvHeapIndex = 1;
water->DiffuseAlbedo = DirectX::XMFLOAT4(0.0f, 0.2f, 0.6f, 1.0f);
water->FresnelR0 = DirectX::XMFLOAT3(0.1f, 0.1f, 0.1f);
water->Roughness = 0.0f;
auto box = std::make_unique<d3dUtil::Material>();
box->Name = "box";
box->MatCBIndex = 2;
box->DiffuseSrvHeapIndex = 0;
box->DiffuseAlbedo = DirectX::XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
box->FresnelR0 = DirectX::XMFLOAT3(0.01f, 0.01f, 0.01f);
box->Roughness = 0.125f;
m_Materials[grass->Name] = std::move(grass);
m_Materials[water->Name] = std::move(water);
m_Materials[box->Name] = std::move(box);
}
创建帧资源缓冲区
cpp
void MaterialApp::CreateFrameResources()
{
for (UINT i = 0; i < gNumFrameResources; i++)
{
m_FrameResources.push_back(std::make_unique<FrameResource>(m_Device.Get(), 1, m_RenderItems.size(), m_Materials.size()));
}
}
创建渲染项
cpp
void MaterialApp::CreateRenderItems()
{
std::unique_ptr<RenderItem> landRitem = std::make_unique<RenderItem>();
landRitem->ObjCBIndex = 0;
landRitem->Geo = m_Geometries["land"].get();
landRitem->mat = m_Materials["land"].get();
landRitem->Geo = m_Geometries["land"].get();
landRitem->IndexCount = landRitem->Geo->DrawArgs["land"].IndexCount;
landRitem->StartIndexLocation = landRitem->Geo->DrawArgs["land"].StartIndexLocation;
landRitem->BaseVertexLocation = landRitem->Geo->DrawArgs["land"].BaseVertexLocation;
landRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
std::unique_ptr<RenderItem> seaRitem = std::make_unique<RenderItem>();
seaRitem->ObjCBIndex = 1;
seaRitem->Geo = m_Geometries["sea"].get();
seaRitem->mat = m_Materials["sea"].get();
seaRitem->IndexCount = seaRitem->Geo->DrawArgs["sea"].IndexCount;
seaRitem->BaseVertexLocation = seaRitem->Geo->DrawArgs["sea"].BaseVertexLocation;
seaRitem->StartIndexLocation = seaRitem->Geo->DrawArgs["sea"].StartIndexLocation;
seaRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
std::unique_ptr<RenderItem> boxRitem = std::make_unique<RenderItem>();
boxRitem->ObjCBIndex = 2;
boxRitem->Geo = m_Geometries["box"].get();
boxRitem->mat = m_Materials["box"].get();
boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;
boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;
boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;
boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
m_RenderItems.push_back(std::move(landRitem));
m_RenderItems.push_back(std::move(seaRitem));
m_RenderItems.push_back(std::move(boxRitem));
}
创建shader and inputlayout
这里我们直接就可以根据对应的shader 创建 rootsignature,因为我们采用的是根描述符
cpp
void MaterialApp::CreateShaderAndInputLayout()
{
m_InputLayout = {
{"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}
};
m_VSShader = d3dUtil::CompileShader(L"source/shader/Texture.usf", nullptr, "VS", "vs_5_0");
m_PSShader = d3dUtil::CompileShader(L"source/shader/Texture.usf", nullptr, "PS", "ps_5_0");
}
创建rootsignature
cpp
void MaterialApp::createRootSignature()
{
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_ALL);
slotRootParameter[1].InitAsConstantBufferView(0);
slotRootParameter[2].InitAsConstantBufferView(1);
slotRootParameter[3].InitAsConstantBufferView(2);
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> staticSamplers = GetStaticSamplers();
CD3DX12_ROOT_SIGNATURE_DESC rootSignature(4, slotRootParameter, (UINT)staticSamplers.size(),
staticSamplers.data(), D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> error;
ComPtr<ID3DBlob> sig;
HRESULT hr = D3D12SerializeRootSignature(&rootSignature, D3D_ROOT_SIGNATURE_VERSION_1_0, sig.GetAddressOf(), error.GetAddressOf());
if (error != nullptr)
{
::OutputDebugStringA((char*)error->GetBufferPointer());
}
ThrowIfFailed(hr);
ThrowIfFailed(m_Device->CreateRootSignature(0, sig->GetBufferPointer(), sig->GetBufferSize(), IID_PPV_ARGS(m_RootSignature.GetAddressOf())));
}
创建PSO
cpp
void MaterialApp::CreatePSO()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc{};
psoDesc.pRootSignature = m_RootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(m_VSShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(m_PSShader.Get());
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.InputLayout = {m_InputLayout.data(), (UINT)m_InputLayout.size()};
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = m_BackBufferFormat;
psoDesc.DSVFormat = m_DepthStencilFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? m4xMsaaQuality - 1 : 0;
psoDesc.NodeMask = 0;
ComPtr<ID3D12PipelineState> pso;
ThrowIfFailed(m_Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(pso.GetAddressOf())));
m_PSOs["material"] = std::move(pso);
}
更新
这里符合一个设计理念,就是我们只在渲染前再去更新资源到对应的resource中。我们在一开始postload的时候,只需要准备相关的数据,比如纹理,顶点数据等(当然顶点数据这里因为是对应在Geo中的所以是提前绑定了对应的顶点缓冲区)
cpp
void MaterialApp::Update(const GameTimer& gt)
{
m_CurrentFrameIndex = (m_CurrentFrameIndex + 1) % gNumFrameResources;
m_CurrentFrameResource = m_FrameResources[m_CurrentFrameIndex].get();
if (m_CurrentFrameResource->Fence != 0 && m_Fence->GetCompletedValue() < m_CurrentFrameResource->Fence)
{
HANDLE event = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
m_Fence->SetEventOnCompletion(m_CurrentFrameResource->Fence, event);
WaitForSingleObject(event, INFINITE);
CloseHandle(event);
}
float x = m_Radius * sinf(m_Phi) * cosf(m_Theta);
float y = m_Radius * cosf(m_Phi);
float z = m_Radius * sinf(m_Phi) * sinf(m_Theta);
DirectX::XMVECTOR pos = DirectX::XMVectorSet(x, y, z, 1.0f);
DirectX::XMVECTOR target = DirectX::XMVectorZero();
DirectX::XMVECTOR up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
DirectX::XMMATRIX view = DirectX::XMMatrixLookAtLH(pos, target, up);
DirectX::XMStoreFloat4x4(&m_View, view);
DirectX::XMStoreFloat3(&m_Eyepos, pos);
UpdateFramresouce(gt);
UpdateObj(gt);
UpdateMaterial(gt);
UpdateSea(gt);
}
void MaterialApp::UpdateObj(const GameTimer& gt)
{
for (auto& r : m_RenderItems)
{
if (r->NumFrameDirty > 0)
{
r->NumFrameDirty--;
ObjectConsts objConstData;
DirectX::XMMATRIX world = DirectX::XMLoadFloat4x4(&r->World);
DirectX::XMMATRIX worldT = DirectX::XMMatrixTranspose(world);
DirectX::XMFLOAT4X4 p;
DirectX::XMStoreFloat4x4(&p, worldT);
DirectX::XMMATRIX invtworld = MathHelper::InverseTanspose(world);
DirectX::XMFLOAT4X4 w;
DirectX::XMStoreFloat4x4(&w, DirectX::XMMatrixTranspose(invtworld));
objConstData.TInvWorld = w;
objConstData.World = p;
m_CurrentFrameResource->ObjectCB->CopyData(r->ObjCBIndex, objConstData);
}
}
}
void MaterialApp::UpdateFramresouce(const GameTimer& gt)
{
PassConstant passCB;
DirectX::XMMATRIX view = DirectX::XMLoadFloat4x4(&m_View);
DirectX::XMMATRIX proj = DirectX::XMLoadFloat4x4(&m_Projection);
DirectX::XMMATRIX viewProj = DirectX::XMMatrixMultiply(view, proj);
DirectX::XMVECTOR viewDeter = DirectX::XMMatrixDeterminant(view);
DirectX::XMMATRIX invView = DirectX::XMMatrixInverse(&viewDeter, view);
DirectX::XMVECTOR projDeter = DirectX::XMMatrixDeterminant(proj);
DirectX::XMMATRIX invProj = DirectX::XMMatrixInverse(&projDeter, proj);
DirectX::XMVECTOR viewProjDeter = DirectX::XMMatrixDeterminant(viewProj);
DirectX::XMMATRIX invViewProj = DirectX::XMMatrixInverse(&viewProjDeter, viewProj);
DirectX::XMStoreFloat4x4(&passCB.View, DirectX::XMMatrixTranspose(view));
DirectX::XMStoreFloat4x4(&passCB.InvView, DirectX::XMMatrixTranspose(invView));
DirectX::XMStoreFloat4x4(&passCB.Proj, DirectX::XMMatrixTranspose(proj));
DirectX::XMStoreFloat4x4(&passCB.InvProj, DirectX::XMMatrixTranspose(invProj));
DirectX::XMStoreFloat4x4(&passCB.ViewProj, DirectX::XMMatrixTranspose(viewProj));
DirectX::XMStoreFloat4x4(&passCB.InvViewProj, DirectX::XMMatrixTranspose(invViewProj));
passCB.EyePosW = m_Eyepos;
passCB.RenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(m_ClientWidth), static_cast<float>(m_ClientHeight));
passCB.InvRenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(1.0f / m_ClientWidth), static_cast<float>(1.0f / m_ClientHeight));
passCB.NearZ = 1.0f;
passCB.FarZ = 1000.0f;
passCB.TotalTime = gt.TotalTime();
passCB.DeltaTime = gt.DeltaTime();
DirectX::XMStoreFloat4(&passCB.gAmbientLight, DirectX::XMVectorSet(0.2f, 0.2f, 0.2f, 1.0f));
float sunX = 1.0f * sinf(m_SunPhi) * cosf(m_SunTheta);
float sunY = 1.0f * cosf(m_SunPhi);
float sunZ = 1.0f * sinf(m_SunTheta) * sinf(m_SunPhi);
DirectX::XMVECTOR lightDir = DirectX::XMVectorSet(-sunX, -sunY, -sunZ, 0.0f);
DirectX::XMStoreFloat3(&passCB.gLights[0].Direction, lightDir);
passCB.gLights[0].Strength = {1.0f, 1.0f, 0.9f};
m_CurrentFrameResource->PassCB->CopyData(0, passCB);
}
void MaterialApp::UpdateMaterial(const GameTimer& gt)
{
for (auto& mat : m_Materials)
{
d3dUtil::Material* pMat = mat.second.get();
if (pMat->NumFrameDirty > 0)
{
pMat->NumFrameDirty --;
MaterialConstants matC;
matC.DiffuseAlbedo = pMat->DiffuseAlbedo;
matC.Roughness = pMat->Roughness;
matC.FresnelR0 = pMat->FresnelR0;
matC.MatTransform = pMat->MatTransform;
m_CurrentFrameResource->MaterialCB->CopyData(pMat->MatCBIndex, matC);
}
}
}
DirectX::XMFLOAT3 MaterialApp::calculateNormal(float amplitude, float waveSpeed, float speed, float time, DirectX::XMFLOAT2 direction, DirectX::XMFLOAT2 pos_xy)
{
DirectX::XMFLOAT3 normal = DirectX::XMFLOAT3(0.0f, 0.0f, 1.0f);
float w = 2.0f / waveSpeed;
float phi = speed * w;
float k = cosf((direction.x * pos_xy.x + direction.y * pos_xy.y) * w + time * phi);
float x = -1.0f * amplitude * w * direction.x * k;
float z = -1.0f * amplitude * w * direction.y * k;
return {x, 1.0f, z};
}
float MaterialApp::CalculateWaveHeight(float amplitude, float waveSpeed, float speed, float time, DirectX::XMFLOAT2 direction, DirectX::XMFLOAT2 pos_xy)
{
float w = 2.0f / waveSpeed;
float phi = speed * w;
float dDotPos = direction.x * pos_xy.x + direction.y * pos_xy.y;
float y = amplitude * sinf(w * dDotPos + time * phi);
return y;
}
void MaterialApp::UpdateSea(const GameTimer& gt)
{
// 从原始网格数据获取顶点数量
GeometryGenerator geoGen;
GeometryGenerator::MeshData sea = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);
WaveParams waveParams;
//然后根据数据去修改
float time = gt.TotalTime();
UINT vIdx = 0;
for (auto& vv : sea.Vertices)
{
Vertex v;
v.Pos = vv.Position;
v.Normal = vv.Normal;
DirectX::XMFLOAT2 seed;
seed.x = MathHelper::frac(v.Pos.x * 0.1234f + v.Pos.z * 0.5678f);
seed.y = MathHelper::frac(v.Pos.x * 0.8765f - v.Pos.z * 0.4321f);
for (UINT i = 0; i <3; i++)
{
float seedOffset = static_cast<float>(i) * 0.333f;
DirectX::XMFLOAT2 waveSeed;
waveSeed.x = MathHelper::frac(seed.x + seedOffset);
waveSeed.y = MathHelper::frac(seed.y + seedOffset * 1.7f);
float amplitude = waveParams.A_min +
(waveParams.A_max - waveParams.A_min) * waveSeed.x;
float waveLength = waveParams.WaveLength_min +
(waveParams.WaveLength_max - waveParams.WaveLength_min) * waveSeed.y;
float speed = waveParams.Speed_min +
(waveParams.Speed_max - waveParams.Speed_min) *
MathHelper::frac(waveSeed.x + waveSeed.y);
// 生成波浪方向
float angle = waveSeed.x * DirectX::XM_2PI;
DirectX::XMFLOAT2 direction(cosf(angle), sinf(angle));
DirectX::XMFLOAT2 pos_xy = {v.Pos.x, v.Pos.z};
// 累加波浪高度
v.Pos.y += CalculateWaveHeight(amplitude, waveLength, speed,
time, direction, pos_xy);
// 累加法线偏移
DirectX::XMFLOAT3 deltaNormal = calculateNormal(amplitude, waveLength,
speed, time, direction, pos_xy);
v.Normal.x += deltaNormal.x;
v.Normal.z += deltaNormal.z;
v.Normal.y = 1.0f;
}
DirectX::XMVECTOR n = DirectX::XMLoadFloat3(&v.Normal);
n = DirectX::XMVector3Normalize(n);
DirectX::XMStoreFloat3(&v.Normal, n);
m_SeaUploaderBuffer->CopyData(vIdx, v);
++vIdx;
}
}
绘制
在上面中我们已经把render data需要的资源(vertex index)准备好了,并在update的时候把缓冲区的资源也都传递到了framresource对应的资源中了。
接下来就是绘制了
cpp
void MaterialApp::Draw(const GameTimer& gt)
{
ID3D12CommandAllocator* alloc = m_CurrentFrameResource->CmdListAlloc.Get();
ThrowIfFailed(alloc->Reset());
ID3D12PipelineState* pso = m_PSOs["material"].Get();
ThrowIfFailed(m_CommandList->Reset(alloc, pso));
ID3D12Resource* CurrentFrame = CurrentBackBuffer();
D3D12_CPU_DESCRIPTOR_HANDLE FrameHandle = CurrentBackBufferHandle();
D3D12_CPU_DESCRIPTOR_HANDLE DepthHandle = DepthStencilView();
CD3DX12_RESOURCE_BARRIER pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(CurrentFrame, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
m_CommandList->ResourceBarrier(1, &pBarrier);
m_CommandList->OMSetRenderTargets(1, &FrameHandle, true, &DepthHandle);
m_CommandList->RSSetViewports(1, &m_ViewPort);
m_CommandList->RSSetScissorRects(1, &m_ScissorRect);
m_CommandList->ClearRenderTargetView(FrameHandle, DirectX::Colors::LightSteelBlue, 0, nullptr);
m_CommandList->ClearDepthStencilView(DepthHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
ID3D12DescriptorHeap* heaps[] = { m_SRVDescriptorHeap.Get() };
m_CommandList->SetDescriptorHeaps(1, heaps);
m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());
ID3D12Resource* FrameConstResource = (ID3D12Resource*)*m_CurrentFrameResource->PassCB.get();
D3D12_GPU_VIRTUAL_ADDRESS pFrameResource = FrameConstResource->GetGPUVirtualAddress();
m_CommandList->SetGraphicsRootConstantBufferView(3, pFrameResource);
m_Opaques.clear();
for (int i = 0; i < m_RenderItems.size(); ++i)
{
m_Opaques.push_back(m_RenderItems[i].get());
}
DrawRenderItems(m_CommandList.Get(), m_Opaques);
pBarrier = CD3DX12_RESOURCE_BARRIER::Transition(CurrentFrame, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
m_CommandList->ResourceBarrier(1, &pBarrier);
ThrowIfFailed(m_CommandList->Close());
ID3D12CommandList* cmdLists[] = {m_CommandList.Get()};
m_CommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);
ThrowIfFailed(m_SwapChain->Present(1, 0));
m_CurrentBackBufferIndex = (m_CurrentBackBufferIndex + 1) % SwapChainBufferCount;
m_CurrentFrameResource->Fence = ++m_CurrentFence;
m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence);
}
void MaterialApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{
for (size_t i = 0; i < ritems.size(); i++)
{
auto ri = ritems[i];
D3D12_VERTEX_BUFFER_VIEW vertexBufferView = ri->Geo->VertexBufferView();
D3D12_INDEX_BUFFER_VIEW indexBufferView = ri->Geo->IndexBufferView();
cmdList->IASetVertexBuffers(0, 1, &vertexBufferView);
cmdList->IASetIndexBuffer(&indexBufferView);
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(m_SRVDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->mat->DiffuseSrvHeapIndex, m_CbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(0, tex);
ID3D12Resource* objResource = (ID3D12Resource*)*m_CurrentFrameResource->ObjectCB.get();
ID3D12Resource* matResource = (ID3D12Resource*)*m_CurrentFrameResource->MaterialCB.get();
D3D12_GPU_VIRTUAL_ADDRESS objGPU = objResource->GetGPUVirtualAddress() + ri->ObjCBIndex * d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConsts));
D3D12_GPU_VIRTUAL_ADDRESS matGPU = matResource->GetGPUVirtualAddress() + ri->mat->MatCBIndex * d3dUtil::CalcConstBufferByteSize(sizeof(MaterialConstants));
cmdList->SetGraphicsRootConstantBufferView(1, objGPU);
cmdList->SetGraphicsRootConstantBufferView(2, matGPU);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}
}

这里还有一个提升,我们之前不是设定了两个矩阵吗?
其实我们这里可以怎么认为,这里的两个矩阵变化实际上分别对应物体和material instance对于纹理坐标的变化。
我们在renderItem中添加纹理坐标的变换
cpp
struct RenderItem
{
RenderItem() = default;
//描述物体局部空间相对于世界空间的世界矩阵
//它定义了物体位于世界空间中的位置、朝向以及大小
DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();
//用已更行标记(dirty flag)来表示物体的相关数据已发生改变,这意味着我们此时需要更新常量缓冲区。
//由于每个FrameResource中都有一个物体常量缓冲区,所以我们必须对每个FrameResource都进行更新
//即,当我们修改物体数据的时候,应当按NumFramesDirty=gNumFrameResource进行设置
//从而每个帧资源都得到更新
int NumFrameDirty = gNumFrameResources; // 这里的意思就是物体primitive更新以后,那么所有的帧资源都应该更新
//该索引指向的GPU常量缓冲区对应于当前渲染项中的物体常量缓冲区
UINT ObjCBIndex =-1;
//此渲染项参与绘制的几何体。注意,绘制一个几何体可能会用到多个渲染项
d3dUtil::MeshGeometry* Geo = nullptr;
d3dUtil::Material* mat = nullptr;
//DrawIndexedInstanced方法的参数
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType;
};
cpp
DirectX::XMMATRIX boxTexTransform = DirectX::XMLoadFloat4x4(&boxRitem->TexTransform);
boxTexTransform = DirectX::XMMatrixMultiply(boxTexTransform, DirectX::XMMatrixScaling(5.0f, 5.0f, 1.0f));
DirectX::XMStoreFloat4x4(&boxRitem->TexTransform, DirectX::XMMatrixTranspose(boxTexTransform));
objConstData.TexTransform = r->TexTransform;

纹理动画
为了使水流纹理,顺着波浪几何体滚动播放,我们要在每个更新周期中调用AnimateMaterials方法,变动我们的纹理坐标。我们用无缝纹理按重复寻址模式进行贴图,这样就能沿着纹理坐标系平面接连不断并不露痕迹地平移纹理坐标。
cpp
void MaterialApp::AnimateMaterials(const GameTimer& gt)
{
auto waterMat = m_Materials["sea"].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->NumFrameDirty = gNumFrameResources;
}
