Direct X 12 – Texture 贴图

9.9 Crate Demo

现在我们将复习整章节的要点,也就是如何时立方体拥有一张贴图。

9.9.1 确定贴图坐标

函数GeometryGenerator::CreateBox将为立方体生成贴图坐标,这样整张贴图就能够映射到立方体的每个面了。为了让代码看上去不那么冗长,我们将只展示立方体正面,背面和顶面的顶点定义。请注意,我们还跳过了顶点构造函数中法线向量和切线向量的部分。

GeometryGenerator::MeshData GeometryGenerator::CreateBox(
    float width, float height, float depth,
    uint32 numSubdivisions)
{
    MeshData meshData;
    Vertex v[24];
    float w2 = 0.5f * width;
    float h2 = 0.5f * height;
    float d2 = 0.5f * depth;

    // 传入立方体正面的顶点数据
    v[0] = Vertex(-w2, -h2, -d2, ..., 0.f, 1.f);
    v[1] = Vertex(-w2, h2, -d2, ..., 0.f, 0.f);
    v[2] = Vertex(w2, h2, -d2, ..., 1.f, 0.f);
    v[3] = Vertex(w2, -h2, -d2, ..., 1.f, 1.f);

    // 传入立方体背面的顶点数据
    v[4] = Vertex(-w2, -h2, d2, ..., 1.f, 1.f);
    v[5] = Vertex(w2, -h2, d2, ..., 0.f, 1.f);
    v[6] = Vertex(w2, h2, d2, ..., 0.f, 0.f);
    v[7] = Vertex(-w2, h2, d2, ..., 1.f, 0.f);

    // 传入立方体顶面的顶点数据
    v[8] = Vertex(-w2, h2, -d2, ..., 0.f, 1.f);
    v[9] = Vertex(-w2, h2, d2, ..., 0.f, 0.f);
    v[10] = Vertex(w2, h2, d2, ..., 1.f, 0.f);
    v[11] = Vertex(w2, h2, -d2, ..., 1.f, 1.f);

    ...
}

如果大家忘记了贴图坐标的概念,那么可以参考之前9.3节的内容。

9.9.2 创建贴图

我们将在初始化时从图片文件来创建一张贴图,代码如下:

// 该结构体将贴图的相关数据进行归纳
struct Texture
{
    // 用于查找贴图
    std::string Name;

    std::wstring Filename;
    Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
    Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
};

void CrateApp::LoadTexture()
{
    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);
}

我们将所有的贴图都存储在一个unordered map中,这样就能通过贴图的name来查询某个贴图。在游戏开发中,在读取贴图之前,我们需要检查该贴图的数据是否已经被读取成功了(例如,贴图是否在unordered map中),这样便不需要多次读取同一张图像了。

9.9.3 设置贴图

当贴图被成功创建,且在descriptor heap中创建了该贴图的shader resource view/descriptor,只需要将贴图的shader resource view设置为root signature的root parameter就能够将贴图绑定至渲染管线并让着色器使用该贴图:

// 拿到我们想要绑定的贴图的shader resource view/descriptor
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(
    mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);

...

// 将贴图绑定至root parameter 0。
// 而root parameter的description将确定贴图被绑定至哪一个register slot。
cmdList->SetGraphicsRootDescriptorTable(0, tex);

9.9.4 HLSL

以下代码是修改后的Default.hlsl文件,现在我们的着色器将支持贴图特性。

// HLSL
// 默认灯光数量
#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 "LightingUtil.hlsl"

Texture2D gDiffuseMap : register(t0);

SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

// 每个物体的constant buffer数据
cbuffer cbPerObject : register(b0)
{
    float4x4 gWorld;
    float4x4 gTexTransform;
};

// 基于渲染通道的constant buffer数据
cbuffer cbPass : register(b1)
{
    float4x4 gView;
    float4x4 gInvView;
    float4x4 gProj;
    float4x4 gInvProj;
    float4x4 gViewProj;
    float4x4 gInvViewProj;
    float3 gEyePosW;
    float cbPerObjectPad1;
    float2 gRenderTargetSize;
    float2 gInvRenderTargetSize;
    float gNearZ;
    float gTotalTime;
    float gDeltaTime;
    float4 gAmbientLight;

    Light gLights[MaxLights];
};

cbuffer cbMaterial : register(b2)
{
    float4 gDiffuseAlbedo;
    float3 gFresnelR0;
    float gRoughness;
    float4x4 gMatTransform;
};

struct VertexIn
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 TexC : TEXCOORD;
};

struct VertexOut
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION;
    float3 NormalW : NORMAL;
    float2 TexC : TEXCOORD;
};

VertexOut VS(VertexIn vin)
{
    VertexOut vout = (VertexOut)0.f;

    // 转换为世界坐标系
    float4 posW = mul(float4(vin.PosL, 1.f), gWorld);
    vout.PosW = posW.xyz;

    // 将设并有不对称缩放;如果有的话我妈洗要使用gWord的逆转制矩阵
    vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);

    // 转换为齐次剪裁空间
    vout.PosH = mul(posW, gViewProj);

    // 输出贴图坐标
    float4 texC = mul(float4(vin.TexC, 0.f, 1.f), 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);

    // 由物体表面一点指向camera的向量
    float3 toEyeW = normalize(gEyePosW - pin.PosW);

    // 光照计算
    float4 ambient = gAmbientLight * diffuseAlbedo;
    const float shiness = 1.f - gRoughness;
    Material mat = { diffuseAlbedo, gFresnelR0, shininess };
    float3 shadowFactor = 1.f;
    float4 directLight = ComputeLight(gLights, mat, pin.PosW,
        pin.NormalW, toEyeW, shadowFactor);
    float4 litColor = ambient + directLight;

    // 从diffuse albedo拿到该点的α值
    litColor.a = diffuseAlbedo.a;

    return litColor;
}

《Direct X 12 – Texture 贴图》有5条留言

  1. 老铁,这本书的中译版正在走最后阶段,预计今年就要出版了……
    而且这本书坑很多,如果要翻译造福大家,请用心……
    谢谢!

    回复
    • 哈哈,中文版已经要出版啦,是在审核还是在翻译?是程序员翻译的么,还是那种大学老师翻译的?写这本书的老铁感觉没有写dx11时那么认真,里面有些东西直接从上一本拷过来。。坑的话我觉得还行吧,就是一些要点容易遗漏。。。

      回复
      • 已经在最后审核期了,大约几个月后就能出了吧~
        如果不是这些坑,这本书的中文版半年前可能就已经出来了……
        十分同意老铁的感觉,尤其是改得还不彻底,还漏了些东西。
        最坑的是阿三带领的微软团队,随着win10的更新方式的更改,隔半年就有新特性、新函数加进去,简直是坑神之神吗~ XD
        另外,题外话,就冲着win10,我感觉GitHub早晚要被微软玩儿坏……
        应该说算是程序员翻译的,之前做过相关的东西,我觉得中译本质量应该还算说得过去,如果出版社不坑的话……到现在,这本书已经翻译快满两年了,一个人翻译的……
        不管怎么说,也要感谢老铁在业余时间给大伙分享技术!
        DX 12与福尔康一样,偏底层,不好(容易)玩儿,到现在也没见有谁正经用它优化,做出什么强大的游戏来……
        您翻译的速度还真是挺快的……orz
        我记得今年八月rtr 4th就要来了,如果能翻译好,定是国内图形界的福音啦~ 🙂

        回复
        • 额。。。两年也太久了吧,辛苦啦,哈哈。我是觉得翻译的话,对书中知识点的理解挺有用的,如果只是粗略地看比较容易漏。。
          DX12可能还是微软自己旗下的工作室在用吧,这一个世代的xbx one肯定还是DX11为主。。看看下一代主机上新的话能不能普及DX12

          回复
          • 扎心啊,老铁!
            里面的事儿太多太杂,水深啊……如果愿意的话,届时买一本(估摸也不太可能……)看看就明白了……不过他马上就要弄完了,好歹算了了这件心事儿啦……据说裤衩都要当出去鸟……
            总是在黑暗的角落里暗想,这玩意儿也许他们自己也不会使的,驱动配合、无穷更新……统统加起来这玩意儿其实……
            嗯,这次1803倒没更新什么东西,说明相对趋于稳定了,或许这个节骨眼儿上发出中译本是个合适的时机?谁知道捏……
            最后再贫一句话,书里的错误有软有硬,所以就算是一字一句对着翻出来,结果也可想而知,这就是为什么这本要花那么长时间准备的原因之一啦,老铁!所以,我才会说如果要翻译给大家看,要用心啦……

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据