You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android平台OpenGL渲染FBX/DAE文件时纹理闪烁问题排查

Android平台OpenGL渲染FBX/DAE模型纹理闪烁问题排查与修复

问题背景

在Android平台使用OpenGL渲染FBX或DAE格式模型时出现纹理闪烁现象,已配置每个顶点最多受8根骨骼影响。

问题根源分析

从提供的着色器代码来看,核心问题集中在骨骼蒙皮的不完整处理、法线计算逻辑错误以及光照参数计算失误:

  1. 骨骼蒙皮仅处理顶点位置:原始代码只对顶点位置做了骨骼变换,但法线、切线、副切线仍使用未蒙皮的模型矩阵变换,动画时法线方向完全错误,导致光照计算混乱,引发纹理闪烁。
  2. 权重未做归一化:8根骨骼的权重总和若不为1,会导致顶点位置偏移、模型变形异常,进而出现闪烁。
  3. 片元着色器法线逻辑混乱:多余的normal * 2.0 - 1.0转换(法线本身已是[-1,1]范围),加上错误的TBN矩阵乘法,导致法线方向完全失效。
  4. 高光计算参数错误:原始代码用法线与反射方向的点积计算高光,正确逻辑应使用视线方向与反射方向的点积。

修复后着色器代码

顶点着色器

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:15:27