天天看点

d3d12龙书学习之MiniEngine的最小化实现(十五) 龙书第19章 法线贴图 Normal Map为什么要有法线贴图原理实施MiniEngine注意点

文章目录

  • 为什么要有法线贴图
  • 原理
  • 实施
  • MiniEngine注意点

为什么要有法线贴图

先来看一张截图

d3d12龙书学习之MiniEngine的最小化实现(十五) 龙书第19章 法线贴图 Normal Map为什么要有法线贴图原理实施MiniEngine注意点

很容易发现,这个柱子的反光不对,一眼看过去就知道是一张贴图,非常的不真实。

因为我们的柱子模型,本身就是平滑的。当然了可以通过雕刻,使得柱子模型完美的匹配对应的纹理。但工作量太大。

于是就用到了法线贴图技术

原理

  • 根据贴图纹理生成一张法线贴图
  • 这个法线贴图中每个像素记录的是该点的法向量
  • 在PS阶段,通过该点本身的法向量与法线贴图中记录的法向量进行一些计算,来计算出一个新的法向量
  • 使用新的法向量参与后边的光照等计算

实施

原书代码已经提供好了法线贴图,这里直接使用就好。

  • 我们直接把第18章的代码拷过来作为基础代码。
  • 简单调整下代码结果,把新的几张法线贴图加入到纹理队列中
  • 跑一次看看结果是否正确。

之所以进行上边的步骤,在于我本身擅长的是迭代式开发。这样一小步一小步的做,期间发现了问题比较容易定位。

运行起来,发现符合预期。

github:

https://github.com/mversace/DirectX12-MiniEngine-Dragon/tree/ecd7c09258f26212e4e77b9c530376f05e8e3d2e

接下来就是shader的修改。

在这里,顶点结构中新添加了切线。

  • 在VS中把切线转换到世界坐标系
  • 添加源码中的那个计算函数
  • 在PS中利用新函数计算出新的法向量

这个新函数,我没有细看,工作就是根据原法线、切线、法线贴图中的法线,计算出一个新的法线

//---------------------------------------------------------------------------------------
// Transforms a normal map sample to world space.
//---------------------------------------------------------------------------------------
float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float3 tangentW)
{
    // Uncompress each component from [0,1] to [-1,1].
    float3 normalT = 2.0f * normalMapSample - 1.0f;

    // Build orthonormal basis.
    float3 N = unitNormalW;
    float3 T = normalize(tangentW - dot(tangentW, N) * N);
    float3 B = cross(N, T);

    float3x3 TBN = float3x3(T, B, N);

    // Transform from tangent space to world space.
    float3 bumpedNormalW = mul(normalT, TBN);

    return bumpedNormalW;
}
           

然后程序跑起来,纹理不对劲。

开始挺长时间的调试。。。VS的图形调试真的是无限崩溃啊

MiniEngine注意点

最终定位到问题点在于纹理载入函数,对于法线贴图,第二个参数应该传为false。否则会修改存储格式

// 7个纹理
    m_srvs.resize(7);
    TextureManager::Initialize(L"Textures/");
    m_srvs[0] = TextureManager::LoadFromFile(L"bricks2", true)->GetSRV();
    m_srvs[1] = TextureManager::LoadFromFile(L"bricks2_nmap", false)->GetSRV();
    m_srvs[2] = TextureManager::LoadFromFile(L"tile", true)->GetSRV();
    m_srvs[3] = TextureManager::LoadFromFile(L"tile_nmap", false)->GetSRV();
    m_srvs[4] = TextureManager::LoadFromFile(L"white1x1", true)->GetSRV();
    m_srvs[5] = TextureManager::LoadFromFile(L"default_nmap", false)->GetSRV();
    m_srvs[6] = TextureManager::LoadFromFile(L"snowcube1024", true)->GetSRV();
           

虽然MiniEngine已经足够简单了,但依旧会有一些“坑”,难以想象直接看UE代码会是怎样的体验。

效果图

d3d12龙书学习之MiniEngine的最小化实现(十五) 龙书第19章 法线贴图 Normal Map为什么要有法线贴图原理实施MiniEngine注意点