📜  计算每个片段的切线空间 (1)

📅  最后修改于: 2023-12-03 15:12:04.529000             🧑  作者: Mango

计算每个片段的切线空间

在计算机图形学中,切线空间是一个重要的概念。切线空间是一个局部坐标系,它与表面的局部几何属性密切相关,可以用于计算表面的纹理、法线、位移等。

对于每个平面曲面片段,它可以被近似为一个平面,并且可以通过计算该平面的切向量和法向量来计算该平面的切线空间。以下的代码将介绍如何计算每个片段的切向量和法向量。

首先,我们定义一个平面曲面片段的顶点数为 $n$,其顶点集合为 $V = {\vec{v_1}, \vec{v_2}, ..., \vec{v_n}}$。它们对应的纹理坐标集合为 $T = {\vec{t_1}, \vec{t_2}, ..., \vec{t_n}}$。我们还需要计算每个片段的法向量和切向量。

计算法向量

对于一个平面曲面片段,可以通过计算其每个面的法向量,最终得到该片段的法向量。具体的算法如下:

  1. 对于每个面 $F = {\vec{v_{i_1}}, \vec{v_{i_2}}, \vec{v_{i_3}}}$,计算其两个边向量 $\vec{e_1} = \vec{v_{i_2}} - \vec{v_{i_1}}, \vec{e_2} = \vec{v_{i_3}} - \vec{v_{i_1}}$。
  2. 通过叉积运算计算两个向量的法向量 $\vec{n} = \vec{e_1} \times \vec{e_2}$。
  3. 对于每个顶点 $\vec{v_i}$,遍历所有包含该顶点的面 $F_j$,并将该面的法向量 $\vec{n_j}$ 累加到顶点的法向量上:$\vec{N_i} = \sum_j \vec{n_j}$。
  4. 最终,对于每个顶点 $\vec{v_i}$,其法向量为 $\vec{N_i}$。

下面是代码片段:

// vertices: 顶点坐标数组
// indices: 三角面片数组
// normals: 顶点法向量数组
for (size_t i = 0; i < num_vertices; i++) {
    normals[i] = glm::vec3(0.0f);
}
for (size_t i = 0; i < num_indices; i += 3) {
    glm::vec3 v0 = vertices[indices[i]];
    glm::vec3 v1 = vertices[indices[i + 1]];
    glm::vec3 v2 = vertices[indices[i + 2]];
    glm::vec3 e1 = v1 - v0;
    glm::vec3 e2 = v2 - v0;
    glm::vec3 n = glm::normalize(glm::cross(e1, e2));
    normals[indices[i]] += n;
    normals[indices[i + 1]] += n;
    normals[indices[i + 2]] += n;
}
for (size_t i = 0; i < num_vertices; i++) {
    normals[i] = glm::normalize(normals[i]);
}

这段代码中,使用 glm 库计算向量叉积和单位向量,其中 vertices 存放顶点坐标,indices 存放三角形面片的顶点索引,normals 存放顶点法向量。可以看到,这段代码首先遍历所有的面,并计算每个面的法向量,然后将该法向量累加到面上的每个顶点上,最终将每个顶点上的法向量单位化。

计算切向量和副切向量

得到每个顶点上的法向量之后,可以通过计算每个顶点的切向量和副切向量来获得该片段的切线空间。具体的算法如下:

  1. 对于每个顶点 $\vec{v_i}$,找到与该顶点相关联的三角面片 $F_j$。
  2. 计算面 $F_j$ 上与该顶点相邻的两个顶点 $\vec{v_k}, \vec{v_l}$,并将其对应的纹理坐标 $\vec{t_k}, \vec{t_l}$ 和该顶点的纹理坐标 $\vec{t_i}$ 一起使用以下公式计算切向量和副切向量:

$$ \vec{t} = \frac{\vec{t_l} - \vec{t_k}}{\lVert \vec{v_l} - \vec{v_k} \rVert} \ \vec{b} = \vec{n} \times \vec{t} $$

其中,$\vec{t}$ 和 $\vec{b}$ 分别为切向量和副切向量,$\vec{n}$ 为该顶点的法向量。

  1. 对于每个顶点 $\vec{v_i}$,将其对应的切向量和副切向量 $\vec{t_i}$ 和 $\vec{b_i}$ 归一化。
  2. 最终,对于每个顶点 $\vec{v_i}$,其切向量和副切向量为 $\vec{t_i}$ 和 $\vec{b_i}$。

下面是代码片段:

// vertices: 顶点坐标数组
// indices: 三角面片数组
// normals: 顶点法向量数组
// texcoords: 顶点纹理坐标数组
// tangents: 顶点切向量数组
// bitangents: 顶点副切向量数组
for (size_t i = 0; i < num_vertices; i++) {
    tangents[i] = glm::vec3(0.0f);
    bitangents[i] = glm::vec3(0.0f);
}
for (size_t i = 0; i < num_indices; i += 3) {
    glm::vec3 v0 = vertices[indices[i]];
    glm::vec3 v1 = vertices[indices[i + 1]];
    glm::vec3 v2 = vertices[indices[i + 2]];
    glm::vec2 uv0 = texcoords[indices[i]];
    glm::vec2 uv1 = texcoords[indices[i + 1]];
    glm::vec2 uv2 = texcoords[indices[i + 2]];
    glm::vec3 e1 = v1 - v0;
    glm::vec3 e2 = v2 - v0;
    glm::vec2 delta_uv1 = uv1 - uv0;
    glm::vec2 delta_uv2 = uv2 - uv0;
    float f = 1.0f / (delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y);
    glm::vec3 tangent;
    tangent.x = f * (delta_uv2.y * e1.x - delta_uv1.y * e2.x);
    tangent.y = f * (delta_uv2.y * e1.y - delta_uv1.y * e2.y);
    tangent.z = f * (delta_uv2.y * e1.z - delta_uv1.y * e2.z);
    glm::vec3 bitangent;
    bitangent.x = f * (-delta_uv2.x * e1.x + delta_uv1.x * e2.x);
    bitangent.y = f * (-delta_uv2.x * e1.y + delta_uv1.x * e2.y);
    bitangent.z = f * (-delta_uv2.x * e1.z + delta_uv1.x * e2.z);
    glm::vec3 n = glm::normalize(glm::cross(e1, e2));
    tangent = glm::normalize(tangent - n * glm::dot(n, tangent));
    bitangent = glm::normalize(bitangent - n * glm::dot(n, bitangent));
    for (size_t j = 0; j < 3; j++) {
        size_t index = indices[i + j];
        tangents[index] += tangent;
        bitangents[index] += bitangent;
    }
}
for (size_t i = 0; i < num_vertices; i++) {
    glm::vec3 n = normals[i];
    glm::vec3 t = tangents[i];
    glm::vec3 b = bitangents[i];
    t = glm::normalize(t - n * glm::dot(n, t));
    if (glm::dot(glm::cross(n, t), b) < 0.0f) {
        t = -t;
    }
    tangents[i] = t;
    bitangents[i] = bitangent;
}

这段代码中,使用 glm 库计算向量叉积和单位向量,其中 vertices 存放顶点坐标,indices 存放三角形面片的顶点索引,normals 存放顶点法向量,texcoords 存放顶点纹理坐标,tangents 存放顶点切向量,bitangents 存放顶点副切向量。可以看到,这段代码首先遍历所有的面,对于每个面,计算该面的切向量和副切向量,并将其对应的顶点上的切向量和副切向量累加。然后,对于每个顶点,将其切向量和副切向量与法向量一起套用 Gram-Schmidt 方法归一化。最后,如果 $\vec{n} \times \vec{t} \cdot \vec{b} < 0$,则将切向量取反。