当前位置: 代码迷 >> 综合 >> d3d12龙书学习之MiniEngine的最小化实现(十五) 龙书第19章 法线贴图 Normal Map
  详细解决方案

d3d12龙书学习之MiniEngine的最小化实现(十五) 龙书第19章 法线贴图 Normal Map

热度:27   发布时间:2023-11-18 11:18:31.0

文章目录

  • 为什么要有法线贴图
  • 原理
  • 实施
  • 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代码会是怎样的体验。

效果图
在这里插入图片描述

  相关解决方案