# 法线纹理

normal = (2*color)-1 // on each component

# 切线和副切线（Tangent and Bitangent）

deltaPos1 = deltaUV1.x * T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B

invTBN = transpose(TBN)

# 准备VBO

## 计算切线和副切线

void computeTangentBasis(
// inputs
std::vector & vertices,
std::vector & uvs,
std::vector & normals,
// outputs
std::vector & tangents,
std::vector & bitangents
){

for ( int i=0; i<vertices.size(); i+=3){

// Shortcuts for vertices
glm::vec3 & v0 = vertices[i+0];
glm::vec3 & v1 = vertices[i+1];
glm::vec3 & v2 = vertices[i+2];

// Shortcuts for UVs
glm::vec2 & uv0 = uvs[i+0];
glm::vec2 & uv1 = uvs[i+1];
glm::vec2 & uv2 = uvs[i+2];

// Edges of the triangle : postion delta
glm::vec3 deltaPos1 = v1-v0;
glm::vec3 deltaPos2 = v2-v0;

// UV delta
glm::vec2 deltaUV1 = uv1-uv0;
glm::vec2 deltaUV2 = uv2-uv0;

float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
glm::vec3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x)*r;

// Set the same tangent for all three vertices of the triangle.
// They will be merged later, in vboindexer.cpp
tangents.push_back(tangent);
tangents.push_back(tangent);
tangents.push_back(tangent);

// Same thing for binormals
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);

}

## 索引

// Try to find a similar vertex in out_XXXX
unsigned int index;
bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i],     out_vertices, out_uvs, out_normals, index);

if ( found ){ // A similar vertex is already in the VBO, use it instead !
out_indices.push_back( index );

// Average the tangents and the bitangents
out_tangents[index] += in_tangents[i];
out_bitangents[index] += in_bitangents[i];
}else{ // If not, it needs to be added in the output data.
// Do as usual
[...]
}

# 着色器

## 新增缓冲和uniform变量

GLuint tangentbuffer;
glGenBuffers(1, &tangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);

GLuint bitangentbuffer;
glGenBuffers(1, &bitangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);

[...]
[...]
GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");

GLuint ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");

// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(programID);

// Compute the MVP matrix from keyboard and mouse input
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Take the upper-left part of ModelViewMatrix
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

// Send our transformation to the currently bound shader,
// in the "MVP" uniform
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);

glm::vec3 lightPos = glm::vec3(0,0,4);
glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);

// Bind our diffuse texture in Texture Unit 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
// Set our "DiffuseTextureSampler" sampler to user Texture Unit 0
glUniform1i(DiffuseTextureID, 0);

// Bind our normal texture in Texture Unit 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, NormalTexture);
// Set our "Normal    TextureSampler" sampler to user Texture Unit 0
glUniform1i(NormalTextureID, 1);

// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
0,                  // attribute
3,                  // size
GL_FLOAT,           // type
GL_FALSE,           // normalized?
0,                  // stride
(void*)0            // array buffer offset
);

// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
1,                                // attribute
2,                                // size
GL_FLOAT,                         // type
GL_FALSE,                         // normalized?
0,                                // stride
(void*)0                          // array buffer offset
);

// 3rd attribute buffer : normals
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2,                                // attribute
3,                                // size
GL_FLOAT,                         // type
GL_FALSE,                         // normalized?
0,                                // stride
(void*)0                          // array buffer offset
);

// 4th attribute buffer : tangents
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(
3,                                // attribute
3,                                // size
GL_FLOAT,                         // type
GL_FALSE,                         // normalized?
0,                                // stride
(void*)0                          // array buffer offset
);

// 5th attribute buffer : bitangents
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(
4,                                // attribute
3,                                // size
GL_FLOAT,                         // type
GL_FALSE,                         // normalized?
0,                                // stride
(void*)0                          // array buffer offset
);

// Index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

// Draw the triangles !
glDrawElements(
GL_TRIANGLES,      // mode
indices.size(),    // count
GL_UNSIGNED_INT,   // type
(void*)0           // element array buffer offset
);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glDisableVertexAttribArray(4);

// Swap buffers
glfwSwapBuffers();

## 顶点着色器

vertexNormal_cameraspace = MV3x3 * normalize(vertexNormal_modelspace);
vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
vertexBitangent_cameraspace = MV3x3 * normalize(vertexBitangent_modelspace);

mat3 TBN = transpose(mat3(
vertexTangent_cameraspace,
vertexBitangent_cameraspace,
vertexNormal_cameraspace
)); // You can use dot products instead of building this matrix and transposing it. See References for details.

LightDirection_tangentspace = TBN * LightDirection_cameraspace;
EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;

## 片段着色器

// Local normal, in tangent space
vec3 TextureNormal_tangentspace = normalize(texture( NormalTextureSampler, UV ).rgb*2.0 - 1.0);

# 结果

• 砖块看上去凹凸不平，这是因为砖块表面法线变化比较剧烈
• 水泥部分看上去很平整，这是因为这部分的法线纹理全都是蓝色

# 延伸阅读

## 正交化（Orthogonalization）

t = glm::normalize(t - n * glm::dot(n, t));

n和t差不多是相互垂直的，只要把t沿-n方向稍微”推”一下，幅度是dot(n,t)。

## 左手系还是右手系？

if (glm::dot(glm::cross(n, t), b) < 0.0f){
t = t * -1.0f;
}

## 用立即模式（immediate mode）进行调试

glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

glMatrixMode(GL_PROJECTION);
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV = ViewMatrix * ModelMatrix;

glUseProgram(0);

glColor3f(0,0,1);
glBegin(GL_LINES);
for (int i=0; i<indices.size(); i++){
glm::vec3 p = indexed_vertices[indices[i]];
glVertex3fv(&p.x);
glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
p+=o*0.1f;
glVertex3fv(&p.x);
}
glEnd();

## 利用颜色进行调试

color.xyz = LightDirection_tangentspace;

• 在圆柱体的右侧，光线（如白色线条所示）是朝上（在切线空间中）的。也就是说，光线和三角形的法线同向。
• 在圆柱体的中间部分，光线和切线方向（指向+X）同向。

• 可视化前，变量是否需要归一化取决于具体情况。
• 如果结果不易理解，就逐个分量可视化。比如，只观察红色，而将绿色和蓝色分量强制设为0。
• alpha值过于复杂，别折腾 :)
• 若想将一个负值可视化，可以采用和处理法线纹理一样的技巧：转而把(v+1.0)/2.0可视化，于是黑色就代表-1，而白色代表+1。只不过这样做会让结果不直观。

# 练习

• 在indexVBO_TBN函数中，做加法前先把向量归一化，观察其作用。
• 用颜色可视化其他向量（如instance、EyeDirection_tangentspace），试着解释你看到的结果。