# 第十三课：法线贴图

## 法线纹理

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

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

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

invTBN = transpose(TBN)

## 准备VBO

### 计算切线和双切线

void computeTangentBasis(    // inputs    std::vector<glm::vec3> & vertices,    std::vector<glm::vec2> & uvs,    std::vector<glm::vec3> & normals,    // outputs    std::vector<glm::vec3> & tangents,    std::vector<glm::vec3> & 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 NormalTexture = loadTGA_glfw("normal.tga");    [...]    GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");

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

        // Clear the screen        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // Use our shader        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(texture2D( NormalTextureSampler, UV ).rgb*2.0 - 1.0);

## 结果

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

## 延伸阅读

### 正交化（Orthogonalization）

Vertex shader中，为了计算得更快，我们没有用矩阵求逆，而是进行了转置。这只有当矩阵表示的坐标系是正交的时候才成立，而眼前这个矩阵还不是正交的。幸运的是这个问题很容易解决：只需在computeTangentBasis()末尾让切线与法线垂直。I

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

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

### 左手坐标系还是右手坐标系？

dot( cross(n,t) , b ) < 0，就要翻转t

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

computeTangentBasis()末对每个顶点都做这个操作。

### 用立即模式进行调试

glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

glMatrixMode(GL_PROJECTION);glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);glMatrixMode(GL_MODELVIEW);glm::mat4 MV = ViewMatrix * ModelMatrix;glLoadMatrixf((const GLfloat*)&MV[0]);

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函数中，在做加法前把向量归一化，看看结果。
• 用颜色可视化其他向量（如instanceEyeDirection_tangentspace），试着解释你看到的结果。