图形学

OpenGL学习30——法线贴图

2021-08-20  本文已影响0人  蓬篙人

法线贴图(Normal Mapping)

在网格上使用纹理给我们带来真实感,但是网格实际上由扁平的三角形组成,如果我们拉近视角观看,我们能够看到表面都是平整的。现实中物体的表面并不都是平整的,这样的表面忽略了很多的细节。看下面直接使用一张砖墙纹理渲染的平面:


直接使用纹理渲染

1. 法向量映射

vec3 rgb_normal = normal * 0.5 + 0.5;
uniform sampler2D normalMap;

void main()
{
    // 从法线贴图读取范围为[0, 1]的法向量
    normal = texture(normalMap, fs_in.TexCoords).rgb;
    // 将范围映射到[-1, 1]
    normal = normalize(normal * 2.0 - 1.0);
    ...
}

2. 切线空间(Tangent space)

  1. 上图中三角形的边E_1E_2可以表示为
    E_1=\Delta{U_1}T+\Delta{V_1}B \\ E_2=\Delta{U_2}T+\Delta{V_2}B
  2. 将切线矢量T和双切线矢量B以坐标法表示:
    (E_1x,E_1y,E_1z)=\Delta{U_1}(T_x,T_y,T_z)+\Delta{V_1}(B_x,B_y,B_z) \\ (E_2x,E_2y,E_2z)=\Delta{U_2}(T_x,T_y,T_z)+\Delta{V_2}(B_x,B_y,B_z)
  3. 将两个等式转换为矩阵乘法:
    \left[ \begin{matrix} E_1x & E_1y & E_1z \\ E_2x & E_2y & E_2z \end{matrix} \right] = \left[ \begin{matrix} \Delta{U_1} & \Delta{V_1} \\ \Delta{U_2} & \Delta{V_2} \end{matrix} \right] \left[ \begin{matrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{matrix} \right]
  4. 变换等式,将切线与双切线矢量转换到左侧:
    \left[ \begin{matrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{matrix} \right] = \left[ \begin{matrix} \Delta{U_1} & \Delta{V_1} \\ \Delta{U_2} & \Delta{V_2} \end{matrix} \right]^{-1} \left[ \begin{matrix} E_1x & E_1y & E_1z \\ E_2x & E_2y & E_2z \end{matrix} \right]
  5. 根据逆矩阵的计算方法,我们可以将等式转换为如下形式(具体逆矩阵的计算请自行查找相关资料):
    \left[ \begin{matrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{matrix} \right] = \frac{1}{\Delta{U_1}\Delta{V_2}-\Delta{U_2}\Delta{V_1}} \left[ \begin{matrix} \Delta{V_2} & -\Delta{V_1} \\ -\Delta{U_2} & \Delta{U_1} \end{matrix} \right] \left[ \begin{matrix} E_1x & E_1y & E_1z \\ E_2x & E_2y & E_2z \end{matrix} \right]

2.1 手动计算切线和双切线矢量

// 顶点位置
glm::vec3 pos1(-1.0f,  1.0f,  0.0f);
glm::vec3 pos2(-1.0f, -1.0f,  0.0f);
glm::vec3 pos3( 1.0f, -1.0f,  0.0f);
glm::vec3 pos4( 1.0f,  1.0f,  0.0f);
// 纹理坐标
glm::vec2 uv1(0.0f, 1.0f);
glm::vec2 uv2(0.0f, 0.0f);
glm::vec2 uv3(1.0f, 0.0f);  
glm::vec2 uv4(1.0f, 1.0f);
// 法向量
glm::vec3 nm(0.0f, 0.0f, 1.0f);
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;
float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);

bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);

// 第二个三角形与上述计算方式相似

2.2 使用切线空间的法线贴图

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;
void main()
{
    ...
    // 使用model转换到世界空间
    vec3 T = normalize(vec3(model * vec4(aTangent,   0.0)));
    vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0)));
    vec3 N = normalize(vec3(model * vec4(aNormal,    0.0)));
    mat3 TBN = mat3(T, B, N);
}
// 顶点着色器
...
out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    mat3 TBN;
} vs_out;

void main()
{
    ...
    vs_out.TBN = mat3(T, B, N);
}

// 片元着色器
...
in VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    mat3 TBN;
} fs_in;
...
vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
normal = normal * 2.0 - 1.0;
normal = normalize(fs_in.TBN * normal);
vs_out.TBN = transpose(mat3(T, B, N));
void main()
{
    vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
    normal = normalize(normal * 2.0 - 1.0);

    vec3 lightDir = fs_in.TBN * normalize(lightPos - fs_in.FragPos);
    vec3 viewDir = fs_in.TBN * normalize(viewPos - fs_in.FragPos);
    ...
}
...
out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;
uniform vec3 lightPos;
uniform vec3 viewPos;
...
void main()
{
    ...
    mat3 TBN = mat3(T, B, N);
    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vs_out.FragPos;
}
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians((float)glfwGetTime() * -10.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
shader.setMat4("model", model);
RenderQuad();

3. 模型

const aiScene* scene = importer.ReadFile(path,
    aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
vector.x = mesh->Tangents[i].x;
vector.y = mesh->Tangents[i].y;
vector.z = mesh->Tangents[i].z;
vertex.Tangent = vector;
vector<Texture> normalMaps = loadMaterialTextures(material,
    aiTextureType_HEIGHT, "texture_normal");

4. 最后一点

vec3 T = normalize(vec3(model * vec4(aTangent,   0.0)));
vec3 N = normalize(vec3(model * vec4(aNormal,    0.0)));
// 基于N重新正交化T
T = normalize(T - dot(T, N) * N);
// 叉积N和T获得B
vec3 B = cross(N, T);

mat3 TBN = mat3(T, B, N);
上一篇下一篇

猜你喜欢

热点阅读