法线贴图是通过扰动法线来产生视觉上的凹凸感
法线贴图的每个像素保存了切线空间的坐标值
如图所示,T ,B , N构成了切线空间,法线贴图中记录的就是该空间中的坐标
然而我们的光照计算是在世界空间进行的,所以我们需要知道该点在世界空间中的法线坐标。
如图所示:假设三角形的三个点为
V0
,
V1
,
V2
.对应的纹理坐标为(
u0,v0
) , (
u1,v1
) ,
(u2,v2)
∴e0→=V1−V0
e1→=V2−V0
(Δu0,Δv0)=(u1−u0,v1−v0)
(Δu1,Δv1)=(u2−u0,v2−v0)
∵e0→=Δu0∗T→+Δv0∗B→
e1→=Δu1∗T→+Δv1∗B→
∴
∵
∴
通常我们会直接传入每个顶点的法线和切线,可以方便的得到每个顶点在世界空间中的法线坐标和切线坐标,以此计算出TBN为基的坐标系在世界空间的坐标
N
为该顶点法线在世界空间中的坐标,其切线
因此
B=N×T′
因此我们得到以切线坐标系的三个轴在世界空间中的坐标,因此可以构造一个矩阵,用于将Normal Map中定义于切线空间中的法线坐标变换到世界空间,以得到我们想要的法线坐标
float3 NormalSampleToWorldSpace(float3 NormalMapSample , float3 unitNormalW , float3 tangentW)
{
// 将范围从[0 , 1]变换到[-1 , 1]
float3 normalT = 2 * NormalMapSample - 1;
// 计算正交基,原因在上面说了
float3 N = unitNormalN;
float3 T = normalize(tangentW - dot(N , tangentW)*tangentW);
float3 B = cross(N , T);
float3x3 TBN = float3x3(T , B , N);
// 被normal map扰动后的法线
float3 bumpNormal = mul(normalT , TBN);
}
我们可以在Vertex Shader中计算出该点法线和切线在世界空间中的坐标
cbuffer Transform
{
float4x4 World;
float4x4 WorldInvTranspose;
float4x4 WorldViewProj;
float4x4 TexTranspose;
};
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float3 TangentL : TANGENT;
float2 Tex : TEXCOORD;
...
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float3 TangentW : TANGENG;
float2 Tex : TEXCOORD;
...
}
VertexOut main(VertexIn input)
{
PosH = mul(float4(input.PosL , 1.0f) , WorldViewProj);
PosW = mul(float4(input.PosL , 1.0f) , World).xyz;
NormalW = mul(input.NormalL , (float3x3)WorldInvTranspose);
TangentW = mul(input.TangentL , (float3x3)World);
...
}
Pixel Shader
Texture2D NormalMap;
float4 main(VertexOut in) : SV_TANGENT
{
float3 normal = normalize(in.NormalW);
float3 normalMapSample = NormalMap.Sample(SampleLinear , in.Tex).xyz;
float3 BumpNormal = NormalSampleToWorldSpace(normalSample , normal , in.TangentW);
}