Android平台OpenGL渲染FBX/DAE文件时纹理闪烁问题排查
Android平台OpenGL渲染FBX/DAE模型纹理闪烁问题排查与修复
问题背景
在Android平台使用OpenGL渲染FBX或DAE格式模型时出现纹理闪烁现象,已配置每个顶点最多受8根骨骼影响。
问题根源分析
从提供的着色器代码来看,核心问题集中在骨骼蒙皮的不完整处理、法线计算逻辑错误以及光照参数计算失误:
- 骨骼蒙皮仅处理顶点位置:原始代码只对顶点位置做了骨骼变换,但法线、切线、副切线仍使用未蒙皮的模型矩阵变换,动画时法线方向完全错误,导致光照计算混乱,引发纹理闪烁。
- 权重未做归一化:8根骨骼的权重总和若不为1,会导致顶点位置偏移、模型变形异常,进而出现闪烁。
- 片元着色器法线逻辑混乱:多余的
normal * 2.0 - 1.0转换(法线本身已是[-1,1]范围),加上错误的TBN矩阵乘法,导致法线方向完全失效。 - 高光计算参数错误:原始代码用法线与反射方向的点积计算高光,正确逻辑应使用视线方向与反射方向的点积。
修复后着色器代码
顶点着色器
#version 300 es precision mediump float; layout(location = 0) in vec3 a_position; layout(location = 1) in vec3 a_normal; layout(location = 2) in vec2 a_texCoord; layout(location = 3) in vec3 tangent; layout(location = 4) in vec3 bitangent; layout(location = 5) in ivec4 boneIds; layout(location = 6) in vec4 weights; layout(location = 7) in ivec4 boneIds_plus; layout(location = 8) in vec4 weights_plus; uniform mat4 u_MVPMatrix; uniform mat4 u_ModelMatrix; const int MAX_BONES = 100; const int MAX_BONE_INFLUENCE = 4; uniform mat4 finalBonesMatrices[MAX_BONES]; out vec2 uv; out vec3 fragPos; out mat3 vary_tbnMatrix; out vec3 normal; void main() { uv = a_texCoord; vec4 position = vec4(0.0f); vec3 skinnedNormal = vec3(0.0f); vec3 skinnedTangent = vec3(0.0f); vec3 skinnedBitangent = vec3(0.0f); float totalWeight = 0.0f; // 处理前4根骨骼的蒙皮影响 for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) { if(boneIds[i] >= 0){ mat4 boneMat = finalBonesMatrices[boneIds[i]]; position += boneMat * vec4(a_position,1.0f) * weights[i]; // 法线使用骨骼矩阵的转置逆变换(方向向量专用变换) skinnedNormal += mat3(transpose(inverse(boneMat))) * a_normal * weights[i]; skinnedTangent += mat3(transpose(inverse(boneMat))) * tangent * weights[i]; skinnedBitangent += mat3(transpose(inverse(boneMat))) * bitangent * weights[i]; totalWeight += weights[i]; } } // 处理后4根骨骼的蒙皮影响 for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) { if(boneIds_plus[i] >= 0){ mat4 boneMat = finalBonesMatrices[boneIds_plus[i]]; position += boneMat * vec4(a_position,1.0f) * weights_plus[i]; skinnedNormal += mat3(transpose(inverse(boneMat))) * a_normal * weights_plus[i]; skinnedTangent += mat3(transpose(inverse(boneMat))) * tangent * weights_plus[i]; skinnedBitangent += mat3(transpose(inverse(boneMat))) * bitangent * weights_plus[i]; totalWeight += weights_plus[i]; } } // 权重归一化,避免总和不为1导致的位置偏差 if(totalWeight > 0.0f){ position /= totalWeight; skinnedNormal /= totalWeight; skinnedTangent /= totalWeight; skinnedBitangent /= totalWeight; } gl_Position = u_MVPMatrix * position; // 将蒙皮后的法线转换到世界空间 vec3 worldNormal = normalize(mat3(transpose(inverse(u_ModelMatrix))) * skinnedNormal); vec3 worldTangent = normalize(mat3(transpose(inverse(u_ModelMatrix))) * skinnedTangent); vec3 worldBitangent = normalize(mat3(transpose(inverse(u_ModelMatrix))) * skinnedBitangent); // TBN矩阵正交化,避免动画过程中矩阵拉伸 vec3 N = normalize(worldNormal); vec3 T = normalize(worldTangent - dot(worldTangent, N) * N); vec3 B = normalize(cross(N, T)); vary_tbnMatrix = mat3(T, B, N); fragPos = vec3(u_ModelMatrix * position); normal = worldNormal; }
片元着色器
#version 300 es precision mediump float; out vec4 outColor; in vec2 uv; in vec3 normal; in mat3 vary_tbnMatrix; in vec3 fragPos; uniform sampler2D texture_diffuse1; uniform sampler2D texture_specular1; uniform vec3 lightPos; uniform vec3 lightColor; uniform vec3 viewPos; vec3 CalcDirLight(vec3 normal_world,vec3 viewDir) { vec3 lightDir = normalize(lightPos - fragPos); vec3 diffuse = texture(texture_diffuse1,uv).rgb; // 环境光计算 float ambientStrength = 0.5; vec3 ambient_light = ambientStrength * diffuse * lightColor; // 漫反射光计算 float diffuseStrength = 0.5; float dif = max(dot(normal_world,lightDir),0.0); vec3 diffuse_light = diffuseStrength * dif * diffuse * lightColor; // 高光计算(修正为视线方向与反射方向的点积) float specularStrength = 0.3; vec3 specular = texture(texture_specular1,uv).rgb; vec3 reflectDir = reflect(-lightDir,normal_world); float spec = pow(max(dot(viewDir, reflectDir),0.0),16.0); vec3 specular_light = specularStrength * spec * specular * lightColor; return ambient_light + diffuse_light + specular_light; } void main() { vec3 worldNormal = normalize(normal); vec3 viewDir = normalize(viewPos - fragPos); vec3 result = CalcDirLight(worldNormal,viewDir); outColor = vec4(result,1.0); }
额外优化建议
- 确保纹理采样使用
GL_CLAMP_TO_EDGE模式,避免纹理边缘的采样异常。 - 检查模型导入时的骨骼数据是否完整,权重分配是否正确,避免无效骨骼ID导致的计算错误。
内容的提问来源于stack exchange,提问作者Mistletoe
相关产品推荐
相关产品推荐

