# OpenGL着色器程序解析--法线贴图

### 背景

• 将tangent向量传入到顶点着色器中；
• 将tangent向量变换到世界坐标系中并传入到片元着色器；
• 在片元着色器中使用tangent向量和法线向量（都处于世界坐标系下）来计算出bitangent向量；
• 通过tangent-bitangent-normal矩阵生成一个将法线信息变换到世界坐标系中的变换矩阵；
• 从法线纹理中采样得到法线信息；
• 通过使用上述的矩阵将法线信息变换到世界坐标系中；
• 继续和往常一样进行光照计算。

### 源代码详解

(mesh.h:33)
struct Vertex
{
Vector3f m_pos;
Vector2f m_tex;
Vector3f m_normal;
Vector3f m_tangent;

Vertex() {}

Vertex( const Vector3f& pos,
const Vector2f& tex,
const Vector3f& normal,
const Vector3f& Tangent )
{
m_pos = pos;
m_tex = tex;
m_normal = normal;
m_tangent = Tangent;
}
};

for (unsigned int i = 0 ; i < Indices.size() ; i += 3) {
Vertex& v0 = Vertices[Indices[i]];
Vertex& v1 = Vertices[Indices[i+1]];
Vertex& v2 = Vertices[Indices[i+2]];

Vector3f Edge1 = v1.m_pos - v0.m_pos;
Vector3f Edge2 = v2.m_pos - v0.m_pos;

float DeltaU1 = v1.m_tex.x - v0.m_tex.x;
float DeltaV1 = v1.m_tex.y - v0.m_tex.y;
float DeltaU2 = v2.m_tex.x - v0.m_tex.x;
float DeltaV2 = v2.m_tex.y - v0.m_tex.y;

float f = 1.0f / (DeltaU1 * DeltaV2 - DeltaU2 * DeltaV1);

Vector3f Tangent, Bitangent;

Tangent.x = f * (DeltaV2 * Edge1.x - DeltaV1 * Edge2.x);
Tangent.y = f * (DeltaV2 * Edge1.y - DeltaV1 * Edge2.y);
Tangent.z = f * (DeltaV2 * Edge1.z - DeltaV1 * Edge2.z);

Bitangent.x = f * (-DeltaU2 * Edge1.x - DeltaU1 * Edge2.x);
Bitangent.y = f * (-DeltaU2 * Edge1.y - DeltaU1 * Edge2.y);
Bitangent.z = f * (-DeltaU2 * Edge1.z - DeltaU1 * Edge2.z);

v0.m_tangent += Tangent;
v1.m_tangent += Tangent;
v2.m_tangent += Tangent;
}

for (unsigned int i = 0 ; i < Vertices.size() ; i++) {
Vertices[i].m_tangent.Normalize();
}

(mesh.cpp:195)
void Mesh::Render()
{
...
glEnableVertexAttribArray(3);

for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
...
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)32);
}
...
glDisableVertexAttribArray(3);
}

(lighting.vs)
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in vec3 Tangent;

uniform mat4 gWVP;
uniform mat4 gLightWVP;
uniform mat4 gWorld;

out vec4 LightSpacePos;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
out vec3 Tangent0;

void main()
{
gl_Position = gWVP * vec4(Position, 1.0);
LightSpacePos = gLightWVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
Tangent0 = (gWorld * vec4(Tangent, 0.0)).xyz;
WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

(lighting.fs:132)
vec3 CalcBumpedNormal()
{
vec3 Normal = normalize(Normal0);
vec3 Tangent = normalize(Tangent0);
Tangent = normalize(Tangent - dot(Tangent, Normal) * Normal);
vec3 Bitangent = cross(Tangent, Normal);
vec3 BumpMapNormal = texture(gNormalMap, TexCoord0).xyz;
BumpMapNormal = 2.0 * BumpMapNormal - vec3(1.0, 1.0, 1.0);
vec3 NewNormal;
mat3 TBN = mat3(Tangent, Bitangent, Normal);
NewNormal = TBN * BumpMapNormal;
NewNormal = normalize(NewNormal);
return NewNormal;
}

void main()
{
vec3 Normal = CalcBumpedNormal();
...

• ‘bricks.jpg’是颜色纹理；
• ‘normal_map.jpg’是从’bricks.jpg’纹理中生成的法线纹理；
• ‘normal_up.jpg’是一个也是一个发现纹理，但是这个纹理中所有发现都是朝上的。使用这个纹理作为法线纹理时，场景的效果就像没有使用法线纹理技术一样，我们可以通过绑定这个纹理来使得我们的法线纹理失效（尽管效率不是很高）。你可以通过按‘b’键在法线纹理和普通纹理之间的切换。

• 广告
• 抄袭
• 版权
• 政治
• 色情
• 无意义
• 其他

120