
前言
这章看起来异常的快,大概是因为原理和opengl相差不大。我们先做下比较。
对比
无论是纹理本身还是采样器,都可以看做是Shader中的常量缓冲区。当然,Opengl和DirectX还是有一定区别的。
在opengl中,我们都是在DrawCall前,将纹理灌入到内存(或显存)中得到地址,然后指定采样器如何采样(包括UV过滤,采样方法等),这些都是在CPU中进行的,然后将地址传入到Uniform中,在glsl中直接就是唯一的采样器的类型,如果想在一次呈现中用不同的采样方法,只能才DrawCall后进行。
而在DirectX中,采样器作为一个对象,我们可以定义很多个,纹理和opengl一样,都是作为Shader资源,当DrawCall时,我们可以传入很多个采样器,渲染过程中可以根据需要换采样器,从而在一次DrawCall时做出不同的采样方法。
程序
想要渲染纹理,首先要改变的是输入布局,毕竟我们要UV坐标才能进行采样(327页):
//void TexWavesApp::BuildShadersAndInputLayout()
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "VS", "vs_5_0");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "PS", "ps_5_0");
mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
我们需要加载图像文件,关于DDS格式文件介绍见328页,加载图片见330页,调用相关方法即可。
同样作为抽象,我们定义了纹理类:
struct Texture
{
std::string Name;
std::wstring Filename;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
};
一个着色器资源,上传堆用来上传数据。Texture被App的mTextures记录着,Material类中记录着引用纹理在堆中的索引,RenderItem通过引用Material间接得到纹理,同时还增加了一个纹理变换的成员:
XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();
我们看hlsl中的常量缓冲区,采样器和纹理都属于常量缓冲区:
Texture2D gDiffuseMap : register(t0);//纹理
SamplerState gsamPointWrap : register(s0);//采样器
相应的,根描述符也需要改变,书中用描述符表,所以要为其创建描述符堆和视图,CrateApp.cpp中的BuildDescriptorHeaps方法如下:
void CrateApp::BuildDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};//Shader资源视图描述符
srvHeapDesc.NumDescriptors = 1;
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)));//创建堆
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
auto woodCrateTex = mTextures["woodCrateTex"]->Resource;
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;//涉及到分量互换,填这个就好
srvDesc.Format = woodCrateTex->GetDesc().Format;//视图格式
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;//维度
srvDesc.Texture2D.MostDetailedMip = 0;//[0, MipLevels-1]
srvDesc.Texture2D.MipLevels = woodCrateTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;//最小层级
md3dDevice->CreateShaderResourceView(woodCrateTex.Get(), &srvDesc, hDescriptor);//创建视图
}
仔细看看,有点不对劲啊,常量缓冲区多了俩,为何只创建了一个材质的,采样器去哪了?
首先,在342页有比较详细的采样器创建方法,和其他常量缓冲区的根参数一样,描述符参照此页的D3D12_SAMPLER_DESC即可。
事例中创建的方法略有不同,我们对采样器的功能往往不会要求太多,所以有了静态采样器一说(见344页)。事例中就是这种方法,提前预备好了6种静态采样器,这样功能或许没有之前的强大,但足够我们用了。书中还表示,用户只能定义2032个静态采样器……我是想不出有什么场合会用到这么多采样器。
其实之前就提到过,在创建根签名描述符时,就有参数:
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> CrateApp::GetStaticSamplers()
{
//创建6个静态采样器,代码在344页
const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
0, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_POINT, // filter
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW
//......
return {
pointWrap, pointClamp,
linearWrap, linearClamp,
anisotropicWrap, anisotropicClamp };
}
//void CrateApp::BuildRootSignature()方法内
auto staticSamplers = GetStaticSamplers();
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
(UINT)staticSamplers.size(), staticSamplers.data(),//传入静态采样器
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
曾经这两个参数传入的是0和nullptr,是因为我们还没用到过采样器,现在我们学会了创建方法,即可传入静态采样器。
然后就是hlsl中采样的方法,在像素着色器中发生了一些小的改变:
float4 PS(VertexOut pin) : SV_Target
{
//采样后,与反射率相乘
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamLinear, 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 };//构建一个新的mat(材质)再传入光照计算
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat, pin.PosW,
pin.NormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
// Common convention to take alpha from diffuse material.
litColor.a = diffuseAlbedo.a;
return litColor;
}
这样一来,板条箱的程序(Crate)完毕,而海岛程序涉及到,如何让本来平铺的贴图变小,并且让贴图重复采样,使地面看起来更精细,如何让海面的UV随着时间流动。
这还要归功于渲染项以及常量缓冲区增加的TexTransform,渲染项中,将其设定为放大5,5,1的Scale矩阵,这样岛的贴图会更精细,而每帧更新的Update中调用了AnimateMaterials方法,让海面的矩阵设定为Translate矩阵,并且根据时间改变(见355页)。
总结一下使用顺序:
1.在LoadTextures方法中读取并初始化Texture,将所有Texture存到成员mTextures中
2.因为是常量缓冲且用描述符表,所以要创建堆,在方法BuildDescriptorHeaps中,有几个纹理就创建几个
3.同样在这个方法中,要从成员mTextures中取出Texture,填写描述符结构体,创建SRV视图且将材质塞入
4.使用时,先找到堆的句柄(首位置),然后偏移到材质位置,之后用设置描述符表的方法cmdList->SetGraphicsRootDescriptorTable(0, tex);,即可将材质设为常量缓冲区。
网友评论