手动计算切线异常求助:法线映射切线方向错误排查
手动计算OBJ模型切线实现法线映射时切线方向突变问题
我正在加载OBJ文件并手动计算切线以实现法线映射,参考LearnOpenGL的内容。目前切线显示异常,经TBN矩阵转换后的世界空间法线也错误,截图中可见多处(尤其是相邻共面三角面区域)切线方向突变。
切线计算代码
for (uint32_t i = 3; i < newMesh.indices.size(); i += 3) { uint32_t vertex0Index = newMesh.indices[i - 3]; uint32_t vertex1Index = newMesh.indices[i - 2]; uint32_t vertex2Index = newMesh.indices[i - 1]; auto& v0 = newMesh.vertices[vertex0Index]; auto& v1 = newMesh.vertices[vertex1Index]; auto& v2 = newMesh.vertices[vertex2Index]; glm::vec3 tangent(0.0f); glm::vec3 edge1 = v1.position - v0.position; glm::vec3 edge2 = v2.position - v0.position; glm::vec2 deltaUV1 = v1.uv - v0.uv; glm::vec2 deltaUV2 = v2.uv - v0.uv; float f; float denominator = deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y; if (std::fabs(denominator) <= std::numeric_limits<float>::epsilon()) { tangent = v1.position - v2.position; } else { f = 1.0f / denominator; tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); } assert(!isnan(tangent.z)); // Accumulate tangent v0.tangent += tangent; v1.tangent += tangent; v2.tangent += tangent; }
我已处理分母为0的情况避免除零错误,但无明显改善。
切线可视化着色器代码
顶点着色器
layout (location = 4) out vec3 tangent; ... // Build TBN matrix vec3 T = normalize(normalMatrix * vTangent); vec3 N = normalize(normalMatrix * vNormal); // Make sure the vectors are still orthogonal T = normalize(T - N * dot(T, N)); vec3 B = normalize(cross(N, T)); // Handedness if (dot(cross(N, T), B) < 0.0f) { T = T * -1.0f; } ... tangent = T;
片段着色器
layout (location = 4) in vec3 tangent; ... outFragColor = vec4(tangent, 1.0f);
问题排查方向与改进建议
- 检查顶点UV的唯一性:OBJ文件中常出现同一空间位置对应多个UV坐标的情况,若未将顶点按「位置+UV」组合拆分,多个三角面的切线会累加在同一个顶点上,导致方向冲突。确保每个唯一的(位置, UV)对对应独立顶点。
- 修正切线计算公式:当前切线计算的UV分量使用有误,标准公式应为:
错误的UV分量匹配会直接导致切线方向偏离预期。f = 1.0f / denominator; tangent.x = f * (deltaUV2.x * edge1.x - deltaUV1.x * edge2.x); tangent.y = f * (deltaUV2.x * edge1.y - deltaUV1.x * edge2.y); tangent.z = f * (deltaUV2.x * edge1.z - deltaUV1.x * edge2.z); - 添加累加后的切线归一化:当前仅对每个三角面的切线进行累加,未对顶点最终的切线向量做归一化处理。在所有三角面遍历完成后,执行
v.tangent = glm::normalize(v.tangent),确保向量方向统一。 - 优化分母为0时的 fallback 逻辑:当UV共线时,用
v1.position - v2.position作为切线无法保证与法线垂直,会引发后续正交化的方向突变。正确做法是生成与法线垂直的向量:glm::vec3 tmpTangent = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), v0.normal); if (glm::length(tmpTangent) < 1e-6) { tmpTangent = glm::cross(glm::vec3(1.0f, 0.0f, 0.0f), v0.normal); } tangent = glm::normalize(tmpTangent); - 调整TBN矩阵的 handedness 处理:当前着色器中翻转T的逻辑会破坏正交性,建议在CPU计算切线时存储 handedness 符号(比如存入tangent的w分量),在着色器中用该符号调整副切线B:
// CPU端计算时 float handedness = denominator < 0.0f ? -1.0f : 1.0f; v0.tangent.w = handedness; // 着色器中 vec3 B = normalize(cross(N, T)) * vTangent.w; - 验证法线矩阵的正确性:确保
normalMatrix是模型视图矩阵的逆转置,若模型存在非均匀缩放,直接使用模型矩阵的旋转部分会导致法线、切线变形。
内容的提问来源于stack exchange,提问作者bishopp
相关产品推荐
相关产品推荐

