DX12 实战 BlinnPhong & 纹理贴图

发布时间 2023-04-27 22:24:08作者: 爱莉希雅

前言

本篇将展示如何实现BlinnPhong光照,以及为人物模型贴上纹理

对于理论不清楚的小伙伴可以看这图形学理论 局部光照,[图形学理论 纹理贴图](https://www.cnblogs.com/chenglixue/p/17109214.html)

具体代码看这github.com

材质

由于漫反射率和镜面反射率我们都是从纹理图中提取,因此材质class中的主要内容是specualrShiness用于控制镜面光照的范围、环境光强度ambientAlbedo

struct Material
{
    // material name for looking up
    std::string name;

    // index of constant buffer
    UINT materialCBIndex;

    // diffuse index of Srv heap
    UINT diffuseSrvHeapIndex;

    UINT specularSrvHeapIndex;

    UINT numFramesDirty;

    // specualr equation's pow
    int specualrShiness;
    
    // ambient light strength
    float ambientAlbedo;
};

材质的构建:

void MyD3D12::BuildMaterial()
{
    for (size_t i = 0; i < m_diffuseTextures.size(); ++i)
    {
        auto pMaterial = std::make_unique<Core::Material>();

        std::string temp = m_models[i]->GetTextureName();
        pMaterial->name = temp.substr(0, temp.find_last_of('_'));
        pMaterial->materialCBIndex = i;
        pMaterial->diffuseSrvHeapIndex = 2 * i;
        pMaterial->specularSrvHeapIndex = 2 * i + 1;
        pMaterial->numFramesDirty = FrameCount;
        pMaterial->specualrShiness = 16;
        pMaterial->ambientAlbedo = 0.1;

        m_materials.push_back(std::move(pMaterial));
    }
    assert(m_materials.size() != 0);
}

渲染项的改动:

void MyD3D12::BuildRenderer()
{
    for (size_t i = 0; i < m_draws.size(); ++i)
    {
        auto pRenderer = std::make_unique<Core::Renderer>();

        XMStoreFloat4x4(&pRenderer->world, DirectX::XMMatrixRotationY(MathHelper::Pi));
        pRenderer->objectIndex = i;
        pRenderer->numFramesDirty = FrameCount;
        pRenderer->pGeometry = m_geometries[0].get();
        pRenderer->pMaterail = m_materials[i].get();
        pRenderer->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
        pRenderer->indexCount = m_draws[i]->indexCount;
        pRenderer->startIndex = m_draws[i]->startIndex;
        pRenderer->baseVertex = m_draws[i]->baseVertex;

        m_allRenderers.push_back(std::move(pRenderer));
    }
    assert(m_allRenderers.size() != 0);

    for (auto& r : m_allRenderers)
    {
        m_opaqueRenderers.push_back(r.get());
    }
    assert(m_opaqueRenderers.size() != 0);
}

光照

​ 在实战中,我们会实现点光源,平行光源,聚光灯,因此Light class中包含这三类光源所需参数

  1. 由于平行光源忽视距离的因素,认定光强是恒定的。因此它只需要一个参数lightDirection
  2. 点光源受距离影响。因此他需要三个参数,两个衰减方程的——falloffStart & falloffEnd,以及光源位置lightPosition
  3. 聚光灯圆锥体范围内所有光线强度不尽相同。因此在点光源参数的基础上,还多出一个spotPower用于控制圆锥体范围内的光线衰减
struct Light
{
    // light color
    XMFLOAT3 lightColor;

    // for falloff function
    float falloffStart;

    // light direction
    XMFLOAT3 lightDirection;

    // for falloff function
    float falloffEnd;

    // light position
    XMFLOAT3 lightPosition;

    // spot equation's pow
    float spotPower;
};

修改FrameResource

由于需要将材质数据和光源数据从CPU传递给shader,因此我们需要在FrameResource中增加新的内容:

struct MaterialConstantBuffer
{
    int specualrShiness;

    float ambientStrength;

    float pad1;
    float pad2;
};

struct PassConstantBuffer
{
    XMFLOAT4X4 view;
    XMFLOAT4X4 inverseView;
    XMFLOAT4X4 projection;
    XMFLOAT4X4 inverseProjection;
    XMFLOAT4X4 viewProjection;
    XMFLOAT4X4 inverseViewProjection;

    // 用于求光源方向
    XMFLOAT3 eyeWorldPosition;

    float pad1;

    // 存储的所有光源信息
    Light lights[MAX_LIGHT_COUNT];
};

更新方式如下:

void XM_CALLCONV FrameResource::UpdatePassConstantBuffers(XMMATRIX& view, XMMATRIX& projection, XMFLOAT3& eyeWorldPosition)
{
    // ...
    passConstantBuffer.eyeWorldPosition = eyeWorldPosition;

        // direction light
        passConstantBuffer.lights[0].lightPosition = { 0.f, 0.f, 0.f };
        passConstantBuffer.lights[0].lightDirection = { 0.f, 0.f, -1.f };
        passConstantBuffer.lights[0].lightColor = { 1.f, 1.f, 1.f };
        passConstantBuffer.lights[0].falloffStart = 1.f;
        passConstantBuffer.lights[0].falloffEnd = 20.f;
        passConstantBuffer.lights[0].spotPower = 2.f;

        // point light
        passConstantBuffer.lights[1].lightPosition = { 0.f, 0.f, -2.f };
        passConstantBuffer.lights[1].lightDirection = { 0.f, 0.f, 1.f };
        passConstantBuffer.lights[1].lightColor = { 1.f, 1.f, 1.f };
        passConstantBuffer.lights[1].falloffStart = 1.f;
        passConstantBuffer.lights[1].falloffEnd = 20.f;
        passConstantBuffer.lights[1].spotPower = 2.f;

        // spot light
        passConstantBuffer.lights[2].lightPosition = { 0.f, 0.f, -2.f };
        passConstantBuffer.lights[2].lightDirection = { 0.f, 0.f, 1.f };
        passConstantBuffer.lights[2].lightColor = { 1.f, 1.f, 1.f };
        passConstantBuffer.lights[2].falloffStart = 1.f;
        passConstantBuffer.lights[2].falloffEnd = 20.f;
        passConstantBuffer.lights[2].spotPower = 2.f;
    // ...
}

void XM_CALLCONV FrameResource::UpdateMaterialConstantBuffers(std::vector<std::unique_ptr<Material>>& pMaterials)
{
    auto currMaterialCB = this->materialUploadCB.get();

        for (auto& e : pMaterials)
        {
            Material* pMaterial = e.get();

            if (pMaterial->numFramesDirty > 0)
            {
                MaterialConstantBuffer materialCB;
                
                materialCB.ambientStrength = pMaterial->ambientAlbedo;
                materialCB.specualrShiness = pMaterial->specualrShiness;

                currMaterialCB->CopyData(pMaterial->materialCBIndex, materialCB);

                pMaterial->numFramesDirty--;
            }
        }
}

提取DDS纹理数据

微软官方提供了用于DX12提取DDS数据的工具包DDSTextureLoader。在此我对该工具函数进行了包装:

struct Texture
{
    std::string name;	// texture name

    std::wstring fileName;	// texture file name

    ComPtr<ID3D12Resource> defaultTexture;	// texture resource for default heap

    std::unique_ptr<uint8_t[]> ddsData;	// for LoadDDSTextureFromFile()

    std::vector<D3D12_SUBRESOURCE_DATA> subResources; // for LoadDDSTextureFromFile()

    ComPtr<ID3D12Resource> uploadTexture;	// texture resource for upload heap to copy date to default texture
};

// copy dds data from subresource to defualt buffer and upload buffer is a intermediate object 
static void CreateD3DResource(
    ID3D12Device* device,
    ID3D12GraphicsCommandList* cmdList,
    std::vector<D3D12_SUBRESOURCE_DATA>& subResources,
    ComPtr<ID3D12Resource>& defaultTexture,
    ComPtr<ID3D12Resource>& uploadTexture
)

{
    const UINT64 textureUploadBufferSize = GetRequiredIntermediateSize(defaultTexture.Get(), 0, static_cast<UINT>(subResources.size()));

    CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);

    auto desc = CD3DX12_RESOURCE_DESC::Buffer(textureUploadBufferSize);

    ThrowIfFailed(device->CreateCommittedResource(
        &heapProps,
        D3D12_HEAP_FLAG_NONE,
        &desc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(uploadTexture.GetAddressOf())
    ));

    cmdList->ResourceBarrier(
        1,
        &CD3DX12_RESOURCE_BARRIER::Transition(defaultTexture.Get(),
                                              D3D12_RESOURCE_STATE_COMMON,
                                              D3D12_RESOURCE_STATE_COPY_DEST
                                             ));

    UpdateSubresources(cmdList, defaultTexture.Get(), uploadTexture.Get(), 0, 0, static_cast<UINT>(subResources.size()), subResources.data());

    cmdList->ResourceBarrier(
        1,
        &CD3DX12_RESOURCE_BARRIER::Transition(defaultTexture.Get(),
                                              D3D12_RESOURCE_STATE_COPY_DEST,
                                              D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
                                             ));
}

具体使用方法如下:

void MyD3D12::LoadTexture()
{
    for (size_t i = 0; i < m_models.size(); ++i)
    {
        auto pTexture = std::make_unique<Core::Texture>();

        auto textureName = m_models[i]->GetTextureName();
        auto textureFileName = m_models[i]->GetDirectory() + textureName + "_diffuse.dds";
        // 用于调试,验证提取的纹理数据是否正确
        OutputDebugStringA((textureName + "_diffuse" + '\n').c_str());
        OutputDebugStringA((textureFileName + '\n').c_str());

        pTexture->name = textureName;
        pTexture->fileName = Util::UTF8ToWideString(textureFileName);

        ThrowIfFailed(LoadDDSTextureFromFile(
            m_device.Get(),
            pTexture->fileName.c_str(),
            &pTexture->defaultTexture,
            pTexture->ddsData,
            pTexture->subResources
        ));

        Core::CreateD3DResource(m_device.Get(), m_commandList.Get(), pTexture->subResources, pTexture->defaultTexture, pTexture->uploadTexture);

        m_diffuseTextures.push_back(std::move(pTexture));
    }

    for (size_t i = 0; i < m_models.size(); ++i)
    {
        auto pTexture = std::make_unique<Core::Texture>();

        auto textureName = m_models[i]->GetTextureName();
        auto textureFileName = m_models[i]->GetDirectory() + textureName + "_specular.dds";
        OutputDebugStringA((textureName + "_specular" + '\n').c_str());
        OutputDebugStringA((textureFileName + '\n').c_str());

        pTexture->name = textureName;
        pTexture->fileName = Util::UTF8ToWideString(textureFileName);

        ThrowIfFailed(LoadDDSTextureFromFile(
            m_device.Get(),
            pTexture->fileName.c_str(),
            &pTexture->defaultTexture,
            pTexture->ddsData,
            pTexture->subResources
        ));

        Core::CreateD3DResource(m_device.Get(), m_commandList.Get(), pTexture->subResources, pTexture->defaultTexture, pTexture->uploadTexture);

        m_specularTextures.push_back(std::move(pTexture));
    }

    assert(m_specularTextures.size() != 0);
}

SRV

创建SRV描述符堆

D3D12_DESCRIPTOR_HEAP_DESC cbvSrvHeapDesc = {};
cbvSrvHeapDesc.NumDescriptors = m_diffuseTextures.size() + m_specularTextures.size();
cbvSrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvSrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&cbvSrvHeapDesc, IID_PPV_ARGS(&m_cbvSrvHeap)));
NAME_D3D12_OBJECT(m_cbvSrvHeap);

m_cbvSrvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

创建SRV

// create srv descriptor
{
    CD3DX12_CPU_DESCRIPTOR_HANDLE hSRVDescriptor(m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart());

    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    srvDesc.Texture2D.MostDetailedMip = 0;  // the index of the most detailed mipmap level to use
    srvDesc.Texture2D.ResourceMinLODClamp = 0.f;    // min mipmap level that you can access. 0.0f means access all of mipmap levels

    for (auto& pTexture : m_diffuseTextures)
    {
        auto currTexture = pTexture->defaultTexture;

        srvDesc.Format = currTexture->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = currTexture->GetDesc().MipLevels;   // combine with MostDetailedMip

        m_device->CreateShaderResourceView(currTexture.Get(), &srvDesc, hSRVDescriptor);

        hSRVDescriptor.Offset(2, m_cbvSrvDescriptorSize);
    }

    hSRVDescriptor = m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart();
    hSRVDescriptor.Offset(1, m_cbvSrvDescriptorSize);
    for (auto& pTexture : m_specularTextures)
    {
        auto currTexture = pTexture->defaultTexture;

        srvDesc.Format = currTexture->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = currTexture->GetDesc().MipLevels;   // combine with MostDetailedMip

        m_device->CreateShaderResourceView(currTexture.Get(), &srvDesc, hSRVDescriptor);

        hSRVDescriptor.Offset(2, m_cbvSrvDescriptorSize);
    }
}

随后在根签名中进行绑定即可

静态采样器

由于纹理存在过大过小及mipmap的缺陷情况,因此我们创建的静态采样器都需考虑到这三种情况

// static sampler
static std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> GetStaticSamplers()
{
    const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
        0, // shader register
        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

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

    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 { pointWrap, pointClamp, linearWrap, linearClamp, anisotropicWrap, anisotropicClamp };
}

在根签名中绑定静态采样器

auto staticSamplers = Core::GetStaticSamplers();

CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(_countof(rootParameters), rootParameters, staticSamplers.size(), staticSamplers.data(), D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

HLSL

VS:

cbuffer ObjectCB : register(b0)
{
    float4x4 world;
}

cbuffer PassCB : register(b1)
{
    float4x4 view;
    float4x4 inverseView;
    float4x4 projection;
    float4x4 inverseProjection;
    float4x4 viewProjection;
    float4x4 inverseViewProjection;
    
    float3 eyeWorldPosition;
    
    float pad;
}

struct VSInput
{
    float3 position : POSITION;
    float3 normal : NORMAL;
    float2 uv : TEXCOORD;
    //float3 tangent : TANGENT;
    //float3 bitNormal : BINORMAL;
};

struct PSInput
{
    float3 positionW : POSITION;
    float4 positionH : SV_POSITION;
    float3 normalW : NORMAL;
    float2 uv : TEXCOORD;
};

PSInput main(VSInput input)
{
    PSInput result;

    float4 positionW = mul(float4(input.position, 1.f), world);
    
    result.positionW = positionW.xyz;
    
    result.positionH = mul(positionW, viewProjection);
    
    // 一般光照的计算在世界空间中进行
    result.normalW = mul(input.normal, (float3x3)world);

    result.uv = input.uv;

	return result;
}

PS:

//-------------------------------------------------------------------------- macro --------------------------------------------------------------------------
#define MAX_LIGHT_COUNT 3

#ifndef DIRECTION_LIGHT_COUNT
	#define DIRECTION_LIGHT_COUNT 1
#endif

#ifndef POINT_LIGHT_COUNT
	#define POINT_LIGHT_COUNT 1
#endif

#ifndef SPOT_LIGHT_COUNT
	#define SPOT_LIGHT_COUNT 1
#endif

//-------------------------------------------------------------------------- declaration --------------------------------------------------------------------------

struct PSInput
{
    float3 positionW : POSITION;
    float4 positionH : SV_POSITION;
    float3 normalW : NORMAL;
    float2 uv : TEXCOORD;
};

struct Material
{
    float4 diffuseAlbedo;
    
    float4 specularAlbedo;
    
    float ambientAlbedo;

    int specualrShiness;
};

struct Light
{
	// light color
    float3 lightColor;

	// for falloff function
    float falloffStart;

	// light direction
    float3 lightDirection;

	// for falloff function
    float falloffEnd;

	// light position
    float3 lightPosition;

	// spot equation's pow
    float spotPower;
};

float3 BlinnPhong(float3 lightColor, float3 toLightDir, float3 normal, float3 toEyeDir, Material material);

float CalcAttenuation(float falloffStart, float falloffEnd, float d);

float3 CalcDiretionLight(Light light, Material material, float3 normal, float3 toEyeDir);

float3 CalcPointLight(Light light, Material material, float3 normal, float3 toEyeDir, float3 objectPos);

float3 CaleSpotLight(Light light, Material material, float3 normal, float3 toEyeDir, float3 objectPos);

float4 CalcLightColor(Light lights[MAX_LIGHT_COUNT], Material material, float3 normal, float3 toEyeDir, float3 objectPos);

//-------------------------------------------------------------------------- binding data --------------------------------------------------------------------------

//-------------------------------------------------------------------------- binding data --------------------------------------------------------------------------

cbuffer MaterialCB : register(b2)
{
    int specualrShiness;
    float ambientAlbedo;
}

cbuffer PassCB : register(b1)
{
    float4x4 view;
    float4x4 inverseView;
    float4x4 projection;
    float4x4 inverseProjection;
    float4x4 viewProjection;
    float4x4 inverseViewProjection;
    
    float3 eyeWorldPosition;

    Light lights[MAX_LIGHT_COUNT];
}

SamplerState g_SamperPointWrap           : register(s0);
SamplerState g_SamperPointClamp          : register(s1);
SamplerState g_SamperLinerWrap           : register(s2);
SamplerState g_SamperLinerClamp          : register(s3);
SamplerState g_SamperAnisotropyWrap      : register(s4);
SamplerState g_SamperAnisotropyClamp     : register(s5);

Texture2D g_diffuseTexture  : register(t0);
Texture2D g_specularTexture : register(t1);

//-------------------------------------------------------------------------- main function --------------------------------------------------------------------------

float4 main(PSInput input) : SV_TARGET
{
    float4 textureDiffuseAlbedo = g_diffuseTexture.Sample(g_SamperAnisotropyWrap, input.uv);
    float4 textureSpecularAlbedo = g_specularTexture.Sample(g_SamperAnisotropyWrap, input.uv);
    
    input.normalW = normalize(input.normalW);
    
    float3 toEyeDirW = normalize(eyeWorldPosition - input.positionW);
    
    Material material = { textureDiffuseAlbedo, textureSpecularAlbedo, ambientAlbedo, specualrShiness };

    float4 resultLightColor = CalcLightColor(lights, material, input.normalW, toEyeDirW, input.positionW);
    
    // 一般从漫反射纹理图中提取alpha
    resultLightColor.a = textureDiffuseAlbedo.a;
    
    return resultLightColor;
}

//-------------------------------------------------------------------------- function define --------------------------------------------------------------------------

float3 BlinnPhong(float3 lightColor, float3 toLightDir, float3 normal, float3 toEyeDir, Material material)
{
	// ambient
    float3 ambient = lightColor * material.ambientAlbedo * material.diffuseAlbedo.rgb;

    float3 diffuse = material.diffuseAlbedo.rgb * max(0.f, dot(normal, toLightDir)) * lightColor;

	// specular reflection
    float3 halfVector = normalize(toLightDir + toEyeDir);
    float3 specular = pow(max(0, dot(halfVector, normal)), material.specualrShiness) * lightColor * material.specularAlbedo.rgb;

    return ambient + diffuse + specular;
}

// calculate falloff of light strength
float CalcAttenuation(float falloffStart, float falloffEnd, float d)
{
    return saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}

// calculate direction light
float3 CalcDiretionLight(Light light, Material material, float3 normal, float3 toEyeDir)
{
    float3 toLightDir = normalize(-light.lightDirection);

    float3 lightColor = max(dot(normal, toLightDir), 0) * light.lightColor;

    return BlinnPhong(lightColor, toLightDir, normal, toEyeDir, material);
}

// calculate point light
float3 CalcPointLight(Light light, Material material, float3 normal, float3 toEyeDir, float3 objectPos)
{
    float3 toLightDir = light.lightPosition - objectPos;

	// d of falloff equation
    float d = length(toLightDir);

    if (d > light.falloffEnd)
    {
        return float3(0.f, 0.f, 0.f);
    }
	
	// normalize
    toLightDir /= d;
	
	// Lambert
    float3 lightColor = max(dot(toLightDir, normal), 0.f) * light.lightColor;
	
	// calc attenuation accroding to distance
    lightColor *= CalcAttenuation(light.falloffStart, light.falloffEnd, d);
	
    return BlinnPhong(lightColor, toLightDir, normal, toEyeDir, material);
}

// calculate spot light
float3 CaleSpotLight(Light light, Material material, float3 normal, float3 toEyeDir, float3 objectPos)
{
    float3 toLightDir = light.lightPosition - objectPos;

	// d of falloff equation
    float d = length(toLightDir);

    if (d > light.falloffEnd)
    {
        return float3(0.f, 0.f, 0.f);
    }
	
	// normalize
    toLightDir /= d;
	
	// Lambert
    float3 lightColor = max(dot(toLightDir, normal), 0.f) * light.lightColor;
	
	// calc attenuation accroding to distance
    lightColor *= CalcAttenuation(light.falloffStart, light.falloffEnd, d);
	
	// scale spot light strength
    float spotLightFactor = pow(max(0, dot(toLightDir, normal)), light.spotPower);
    lightColor *= spotLightFactor;

    return BlinnPhong(lightColor, toLightDir, normal, toEyeDir, material);
}

float4 CalcLightColor(Light lights[MAX_LIGHT_COUNT], Material material, float3 normal, float3 toEyeDir, float3 objectPos)
{
    float3 result = 0.f;
	
    int i = 0;
	
#if (DIRECTION_LIGHT_COUNT > 0)

    for (i = 0; i < DIRECTION_LIGHT_COUNT; ++i)
    {
        result += CalcDiretionLight(lights[i], material, normal, toEyeDir);
    }
	
#endif
	
#if (POINT_LIGHT_COUNT > 0)
	
    for (i = DIRECTION_LIGHT_COUNT; i < DIRECTION_LIGHT_COUNT + POINT_LIGHT_COUNT; ++i)
    {
        result += CalcPointLight(lights[i], material, normal, toEyeDir, objectPos);
    }
	
#endif
	
#if (SPOT_LIGHT_COUNT > 0)
	
    for (i = DIRECTION_LIGHT_COUNT + POINT_LIGHT_COUNT; i < DIRECTION_LIGHT_COUNT + POINT_LIGHT_COUNT + SPOT_LIGHT_COUNT; ++i)
    {
        result += CaleSpotLight(lights[i], material, normal, toEyeDir, objectPos);
    }
	
#endif

    return float4(result, 1.f);
}

输出

image-20230427221652783

other texture format转DDS format

微软公司提供了一个将其他纹理格式转为DDS格式的.exe工具Texconv,该工具可以调整大小、改变格式、生成mipmap层、块压缩(Block Compression)

  • 语法

    texconv [-r | -r:keep | -r:flatten] [-flist <filename>]
        [-w number] [-h number] [-m number] [-f format]
        [-if filter] [-srgb | -srgbi | -srgbo]
        [-px string] [-sx string] [-o directory] [-l] [-y]
        [-ft file-type]
        [-hflip] [-vflip]
        [-sepalpha] [-keepcoverage number]
        [-nowic] [-wrap | -mirror]
        [-pmalpha | -alpha] [-at number]
        [-fl feature-level] [-pow2]
        [-nmap flags] [-nmapamp number]
        [-tu | -tf] [-dword] [-badtails] [-fixbc4x4] [-xlum]
        [-dx10 | -dx9] [-tga20]
        [-wicq number] [-wiclossless] [-wicmulti]
        [-nologo] [-timing]
        [-singleproc] [-gpu number | -nogpu] [-bc flags] [-aw number]
        [-c colorkey] [-rotatecolor rot] [-nits number] [-tonemap]
        [-x2bias] [-inverty] [-reconstructz] [-swizzle rgbamask]
        <file-name(s)>
    

    常用的参数含义:

    1. <file-name(s)>:转换的目标文件的格式。支持的格式有dds、tga、hdr、ppm、pfm、bmp、jpg、png、jxr等等
    2. -r:搜索目标路径的子目录。默认为-r:flatten,意味在子目录下输出文件;若指定为-r:keep,意味在子目录下创建一个新的目录,并输出于此
    3. -o:输出目录
    4. -I:将输出目录的和文件名都视为小写(windows文件系统不分大小写,但git区分)
    5. -y:覆盖现有的输出文件
    6. -w:输出纹理图的宽
    7. -h:输出纹理图的高
    8. -hflip:纹理图水平翻转
    9. -vflip:纹理图垂直翻转
    10. -m:输出纹理图中生成的mipmap层数。默认为0,意味生成所有mipmap层。该选项仅用于DDS
    11. -f:输出纹理图的压缩格式
    12. -ft file-type:输出的纹理图类型。默认为dds
    13. -pow2:将纹理的宽高调整为2的整数幂,尽量不改变宽高比
    14. -nmap:将高度映射转换到法线映射。必须包含以下一个以上的标志位组合
      • r:使用输入的r作为高度
      • g:使用输入的g作为高度
      • b:使用输入的b作为高度
      • a:使用输入的a作为高度
      • l:由输入的rgb计算得到的亮度作为高度
      • o:计算一个粗略的遮挡值,将其编码至alpha
    15. -nmapamp:法线贴图的振幅,默认为1(不懂可以看这ComputeNormalMap)
  • 示例
    image-20230423165914423
    image-20230423165849132
    image-20230423171317686

loading

下一步实现立方体贴图,加入背景图

reference

DDSTextureLoader

Texconv

Directx12 3D 游戏开发实战