# 第二十六课 法线纹理

## 背景

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

## 代码

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

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

• 本文已收录于以下专栏：

## 法线向量（用于贴图）

• qq_25867649
• 2017年02月08日 16:33
• 687

## OpenGL教程翻译 第二十六课 法线纹理

• Vcube
• 2015年11月21日 10:27
• 750

## 关于点法线向量的计算

• chy19911123
• 2015年06月12日 21:08
• 1339

## 用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十二课

• csxiaoshui
• 2014年05月01日 15:08
• 1569

## OpenGL核心技术之法线贴图

• jxw167
• 2017年02月28日 16:16
• 2400

## 法线贴图与位移贴图

• u010897187
• 2015年01月28日 23:47
• 1487

## 编辑器快捷键

• MrLeoLi
• 2018年02月05日 14:29
• 56

## 法线纹理(贴图)Normal Mapping

• u014800094
• 2016年11月07日 19:06
• 325

## Unity Shader入门精要学习笔记 - 第13章 使用深度和法线纹理

• u010848412
• 2017年06月24日 19:09
• 990

## OpenGL中启用光照前的准备——指定法线

OpenGL中启用光照前的准备——指定法线      我们在使用光源时，除了强度和颜色之外，还需要指定光源的位置和方向，并且这些光源的位置和方向将会极大地影响场景的外观。      Open...
• Haohan_Meng
• 2014年12月07日 08:21
• 4537

举报原因： 您举报文章：OpenGL教程翻译 第二十六课 法线纹理 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)