OpenGL骨骼动画3D模型光照失效:如何传递正确法线?
骨骼动画模型的法线计算与光照修复问题
问题描述
按照骨骼动画教程实现后,模型动画正常,但运动时光照完全失效(显示黑色),静止时光照正常。核心问题出在顶点着色器中未对法线应用骨骼变换,同时世界空间位置也未更新。
错误根源分析
当前顶点着色器存在三个关键错误:
- 直接将原始法线
norm传递给片元着色器,完全忽略骨骼动画对顶点的变换,导致运动时法线方向与实际模型表面方向完全不符,光照计算的点积结果为负或0,最终显示黑色。 FragPos使用原始顶点位置pos,而非骨骼变换后的世界空间位置,导致光照计算中的视角方向、光源方向全部错误。- 代码中计算了
localNormal但未进行权重累加,该值完全未被使用。
修正方案
需要对法线和世界空间位置都应用骨骼的权重变换,具体步骤如下:
- 初始化累加变量存储变换后的法线,和顶点位置一样按骨骼权重累加。
- 对每个影响骨骼,使用骨骼矩阵的逆转置矩阵变换法线(确保法线在缩放变换后仍保持垂直于表面),再乘以对应权重累加。
- 将骨骼变换后的顶点位置转换到世界空间,作为
FragPos传递给片元着色器。 - 若模型矩阵包含非均匀缩放,需对变换后的法线额外应用模型矩阵的逆转置。
修正后的顶点着色器代码
#version 330 core layout(location = 0) in vec3 pos; layout(location = 1) in vec3 norm; layout(location = 2) in vec2 tex; layout(location = 5) in ivec4 boneIds; layout(location = 6) in vec4 weights; uniform mat4 projection; uniform mat4 view; uniform mat4 model; const int MAX_BONES = 100; const int MAX_BONE_INFLUENCE = 4; uniform mat4 finalBonesMatrices[MAX_BONES]; out vec2 TexCoords; out vec3 FragPos; out vec3 Normal; void main() { vec4 totalPosition = vec4(0.0f); vec3 totalNormal = vec3(0.0f); for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) { if(boneIds[i] == -1) continue; if(boneIds[i] >= MAX_BONES) { totalPosition = vec4(pos, 1.0f); totalNormal = norm; break; } // 计算骨骼变换后的顶点位置并累加权重 mat4 boneMatrix = finalBonesMatrices[boneIds[i]]; totalPosition += boneMatrix * vec4(pos, 1.0f) * weights[i]; // 计算骨骼变换后的法线并累加权重:使用逆转置矩阵确保法线正确变换 mat3 normalMatrix = transpose(inverse(mat3(boneMatrix))); totalNormal += normalMatrix * norm * weights[i]; } // 计算世界空间位置 vec4 worldPos = model * totalPosition; FragPos = worldPos.xyz; // 应用模型矩阵的法线变换(如果model有非均匀缩放,必须用逆转置) mat3 modelNormalMatrix = transpose(inverse(mat3(model))); Normal = modelNormalMatrix * totalNormal; mat4 viewModel = view * model; gl_Position = projection * viewModel * totalPosition; TexCoords = tex; }
额外优化建议
- 为提升性能,可在CPU端预计算每个骨骼的法线变换矩阵(即骨骼矩阵逆转置的3x3部分),直接传递给GPU,避免在顶点着色器中重复计算
transpose(inverse)。 - 片元着色器中已对
Normal做归一化,顶点着色器无需提前归一化,插值后再归一化能得到更准确的法线方向。
内容的提问来源于stack exchange,提问作者Juroza
相关产品推荐
相关产品推荐

