📅  最后修改于: 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}}$。我们还需要计算每个片段的法向量和切向量。
对于一个平面曲面片段,可以通过计算其每个面的法向量,最终得到该片段的法向量。具体的算法如下:
下面是代码片段:
// 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
存放顶点法向量。可以看到,这段代码首先遍历所有的面,并计算每个面的法向量,然后将该法向量累加到面上的每个顶点上,最终将每个顶点上的法向量单位化。
得到每个顶点上的法向量之后,可以通过计算每个顶点的切向量和副切向量来获得该片段的切线空间。具体的算法如下:
$$ \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}$ 为该顶点的法向量。
下面是代码片段:
// 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$,则将切向量取反。