与世界 Space 的切线 space(TBN 矩阵)
Tangent space to World Space (TBN Matrix)
我正在 CPU 中使用光栅化和深度缓冲构建一个渲染器,现在我已经包含了法线贴图。一切正常,如下图所示:
问题是,即使它有效,我也不明白为什么!实施与我的想法相反。这是获取每个片段法线的代码:
const Vector3D TexturedMaterial::getNormal(const Triangle3D& triangle_world, const Vector2D& text_coords) const {
Vector3D tangent, bitangent;
calculateTangentSpace(tangent, bitangent, triangle_world);
// Gets the normal from a RGB Texture [0,1] and maps it to [-1, 1]
const Vector3D normal_tangent = (Vector3D) getTextureColor(m_texture_normal, m_texture_normal_width, m_texture_normal_height, text_coords);
const Vector3D normal_world = TangentToWorld(normal_tangent, tangent, bitangent, normal_tangent);
return normal_world;
}
void TexturedMaterial::calculateTangentSpace(Vector3D& tangent, Vector3D& bitangent, const Triangle3D& triangle_world) const {
const Vector3D q1 = triangle_world.v2.position - triangle_world.v1.position;
const Vector3D q2 = triangle_world.v3.position - triangle_world.v2.position;
const double s1 = triangle_world.v2.texture_coords.x - triangle_world.v1.texture_coords.x;
const double s2 = triangle_world.v3.texture_coords.x - triangle_world.v2.texture_coords.x;
const double t1 = triangle_world.v2.texture_coords.y - triangle_world.v1.texture_coords.y;
const double t2 = triangle_world.v3.texture_coords.y - triangle_world.v2.texture_coords.y;
tangent = t2 * q1 - t1 * q2;
bitangent = -s2 * q1 + s1 * q2;
tangent.normalize();
bitangent.normalize();
}
我的困惑在这里:
const Vector3D TexturedMaterial::TangentToWorld(const Vector3D& v, const Vector3D& tangent, const Vector3D& bitangent, const Vector3D& normal) const {
const int handness = -1; // Left coordinate system
// Vworld = Vtangent * TBN
Vector3D v_world = {
v.x * tangent.x + v.y * bitangent.x + v.z * normal.x,
v.x * tangent.y + v.y * bitangent.y + v.z * normal.y,
v.x * tangent.z + v.y * bitangent.z + v.z * normal.z,
};
// Vworld = Vtangent * TBN(-1) = V * TBN(T)
Vector3D v_world2 = {
v.x * tangent.x + v.y * tangent.y + v.z * tangent.z,
v.x * bitangent.x + v.y * bitangent.y + v.z * bitangent.z,
v.x * normal.x + v.y * normal.y + v.z * normal.z,
};
v_world2.normalize();
// return handness * v_world; --> DOES NOT WORK
return handness * v_world2; --> WORKS
}
假设我正在使用行向量:
V = (Vx, Vy, Vz)
[Tx Ty Tz]
TBN = [Bx By Bz]
[Nx Ny Nz]
[Tx Bx Nx]
TBN(-1) = [Ty By Ny] // Assume basis are orthogonal TBN(-1) = TBN(T)
[Tz Bz Nz]
那么,如果 T、B 和 N 是在 world 坐标系中表示的 TBN 的基向量,则变换应为:
Vworld = Vtangent * TBN
Vtangent = Vworld * TBN(-1)
但是,在我的代码中,我做的恰恰相反。要将正切法线 space 转换为世界 space,我乘以 TBN 的倒数。
我遗漏或误解了什么? T、B、N用世界坐标系表示的假设是不是错了?
谢谢!
你的推理是正确的——第二个版本是错误的。一种更直观的方法是分析当切线 space 法线为 (0, 0, 1)
时会发生什么。在这种情况下,您显然想使用三角形法线,这正是第一个版本应该做的。
但是,您输入了错误的参数:
const Vector3D normal_world = TangentToWorld(normal_tangent,
tangent, bitangent, normal_tangent);
最后一个参数需要是三角形法线,而不是你从纹理中获取的法线。
我正在 CPU 中使用光栅化和深度缓冲构建一个渲染器,现在我已经包含了法线贴图。一切正常,如下图所示:
问题是,即使它有效,我也不明白为什么!实施与我的想法相反。这是获取每个片段法线的代码:
const Vector3D TexturedMaterial::getNormal(const Triangle3D& triangle_world, const Vector2D& text_coords) const {
Vector3D tangent, bitangent;
calculateTangentSpace(tangent, bitangent, triangle_world);
// Gets the normal from a RGB Texture [0,1] and maps it to [-1, 1]
const Vector3D normal_tangent = (Vector3D) getTextureColor(m_texture_normal, m_texture_normal_width, m_texture_normal_height, text_coords);
const Vector3D normal_world = TangentToWorld(normal_tangent, tangent, bitangent, normal_tangent);
return normal_world;
}
void TexturedMaterial::calculateTangentSpace(Vector3D& tangent, Vector3D& bitangent, const Triangle3D& triangle_world) const {
const Vector3D q1 = triangle_world.v2.position - triangle_world.v1.position;
const Vector3D q2 = triangle_world.v3.position - triangle_world.v2.position;
const double s1 = triangle_world.v2.texture_coords.x - triangle_world.v1.texture_coords.x;
const double s2 = triangle_world.v3.texture_coords.x - triangle_world.v2.texture_coords.x;
const double t1 = triangle_world.v2.texture_coords.y - triangle_world.v1.texture_coords.y;
const double t2 = triangle_world.v3.texture_coords.y - triangle_world.v2.texture_coords.y;
tangent = t2 * q1 - t1 * q2;
bitangent = -s2 * q1 + s1 * q2;
tangent.normalize();
bitangent.normalize();
}
我的困惑在这里:
const Vector3D TexturedMaterial::TangentToWorld(const Vector3D& v, const Vector3D& tangent, const Vector3D& bitangent, const Vector3D& normal) const {
const int handness = -1; // Left coordinate system
// Vworld = Vtangent * TBN
Vector3D v_world = {
v.x * tangent.x + v.y * bitangent.x + v.z * normal.x,
v.x * tangent.y + v.y * bitangent.y + v.z * normal.y,
v.x * tangent.z + v.y * bitangent.z + v.z * normal.z,
};
// Vworld = Vtangent * TBN(-1) = V * TBN(T)
Vector3D v_world2 = {
v.x * tangent.x + v.y * tangent.y + v.z * tangent.z,
v.x * bitangent.x + v.y * bitangent.y + v.z * bitangent.z,
v.x * normal.x + v.y * normal.y + v.z * normal.z,
};
v_world2.normalize();
// return handness * v_world; --> DOES NOT WORK
return handness * v_world2; --> WORKS
}
假设我正在使用行向量:
V = (Vx, Vy, Vz)
[Tx Ty Tz]
TBN = [Bx By Bz]
[Nx Ny Nz]
[Tx Bx Nx]
TBN(-1) = [Ty By Ny] // Assume basis are orthogonal TBN(-1) = TBN(T)
[Tz Bz Nz]
那么,如果 T、B 和 N 是在 world 坐标系中表示的 TBN 的基向量,则变换应为:
Vworld = Vtangent * TBN
Vtangent = Vworld * TBN(-1)
但是,在我的代码中,我做的恰恰相反。要将正切法线 space 转换为世界 space,我乘以 TBN 的倒数。
我遗漏或误解了什么? T、B、N用世界坐标系表示的假设是不是错了?
谢谢!
你的推理是正确的——第二个版本是错误的。一种更直观的方法是分析当切线 space 法线为 (0, 0, 1)
时会发生什么。在这种情况下,您显然想使用三角形法线,这正是第一个版本应该做的。
但是,您输入了错误的参数:
const Vector3D normal_world = TangentToWorld(normal_tangent,
tangent, bitangent, normal_tangent);
最后一个参数需要是三角形法线,而不是你从纹理中获取的法线。