从 Assimp 加载 Collada (dae) 模型显示不正确的法线

Posted

技术标签:

【中文标题】从 Assimp 加载 Collada (dae) 模型显示不正确的法线【英文标题】:Loading a Collada (dae) model from Assimp shows incorrect normals 【发布时间】:2020-05-07 01:46:22 【问题描述】:

解决方案:

感谢 Rabbid76,我需要使用模型矩阵更新顶点着色器中的 Normal 向量。更新了顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;
out vec3 Normal;

uniform mat4 projection; 
uniform mat4 view; 
uniform mat4 model;

uniform float scale;

void main()

    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = vec3(model * vec4(aNormal, 0.0));
    gl_Position = projection * view * vec4(FragPos, 1.0);

问题

我正在尝试在 Assimp 中正确加载 collada (dae) 文件,但法线似乎出错了。我想帮助解决这个问题。我感觉这与我如何处理转换矩阵有关。例如,以下是 OpenGL 应用程序加载 obj 文件的屏幕截图:

在上面的屏幕截图中,灯光直接位于模型上方 x=0 和 z=0 处。法线显示正确。当我加载 dae 文件时,我得到以下信息:

灯光位置似乎来自-z侧。

这是我目前必须加载模型的代码:

    加载模型文件,并调用包含aiMatrix4x4()processNode()方法
void Model::loadModel(std::string filename)

    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(filename, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace | aiProcess_GenBoundingBoxes);

    if (!scene || !scene->mRootNode) 
        std::cout << "ERROR::ASSIMP Could not load model: " << importer.GetErrorString() << std::endl;
    
    else 
        this->directory = filename.substr(0, filename.find_last_of('/'));

        this->processNode(scene->mRootNode, scene, aiMatrix4x4());
    


    processNode() 是一种递归方法,它主要迭代 node-&gt;mMeshes 我乘以转换。
void Model::processNode(aiNode* node, const aiScene* scene, aiMatrix4x4 transformation)
 
    for (unsigned int i = 0; i < node->mNumMeshes; i++) 
        aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];

        // only apply transformation on meshs not entities such as lights or camera.
        transformation *= node->mTransformation;

        this->meshes.push_back(processMesh(mesh, scene, transformation));
    

    for (unsigned int i = 0; i < node->mNumChildren; i++)
    
        processNode(node->mChildren[i], scene, transformation);
    

    processMesh() 处理收集所有网格数据(顶点、索引等)
Mesh Model::processMesh(aiMesh* mesh, const aiScene* scene, aiMatrix4x4 transformation)

    glm::vec3 extents;
    glm::vec3 origin;

    std::vector<Vertex> vertices = this->vertices(mesh, extents, origin, transformation);
    std::vector<unsigned int> indices = this->indices(mesh);
    std::vector<Texture> textures = this->textures(mesh, scene);

    return Mesh(
        vertices,
        indices,
        textures,
        extents,
        origin,
        mesh->mName
    );

    接下来调用vertices() 方法来获取所有顶点。它通过变换矩阵。在这里,我将顶点与矩阵 (transformation * mesh-&gt;mVertices[i];) 相乘。我有一种强烈的感觉,我没有在这里做某事,我错过了一些东西。
std::vector<Vertex> Model::vertices(aiMesh* mesh, glm::vec3& extents, glm::vec3 &origin, aiMatrix4x4 transformation)

    std::vector<Vertex> vertices;

    for (unsigned int i = 0; i < mesh->mNumVertices; i++) 
        Vertex vertex;

        glm::vec3 vector3;

        aiVector3D v = transformation * mesh->mVertices[i];

        // Vertices
        vector3.x = v.x;
        vector3.y = v.y;
        vector3.z = v.z;

        vertex.position = vector3;

        // Normals
        if (mesh->mNormals) 
            vector3.x = mesh->mNormals[i].x;
            vector3.y = mesh->mNormals[i].y;
            vector3.z = mesh->mNormals[i].z;
            vertex.normal = vector3;
        


        // Texture coordinates
        if (mesh->mTextureCoords[0]) 
            glm::vec2 vector2;

            vector2.x = mesh->mTextureCoords[0][i].x;
            vector2.y = mesh->mTextureCoords[0][i].y;
            vertex.texCoord = vector2;
        
        else 
            vertex.texCoord = glm::vec2(0, 0);
        

        if (mesh->mTangents) 
            vector3.x = mesh->mTangents[i].x;
            vector3.y = mesh->mTangents[i].y;
            vector3.z = mesh->mTangents[i].z;
            vertex.tangent = vector3;
        

        // Bitangent
        if (mesh->mBitangents) 
            vector3.x = mesh->mBitangents[i].x;
            vector3.y = mesh->mBitangents[i].y;
            vector3.z = mesh->mBitangents[i].z;
            vertex.bitangent = vector3;
        


        vertices.push_back(vertex);
    

    glm::vec3 min = glm::vec3(mesh->mAABB.mMin.x, mesh->mAABB.mMin.y, mesh->mAABB.mMin.z);
    glm::vec3 max = glm::vec3(mesh->mAABB.mMax.x, mesh->mAABB.mMax.y, mesh->mAABB.mMax.z);

    extents = (max - min) * 0.5f;
    origin = glm::vec3((min.x + max.x) / 2.0f, (min.y + max.y) / 2.0f, (min.z + max.z) / 2.0f);

    printf("%f,%f,%f\n", origin.x, origin.y, origin.z);

    return vertices;

作为补充说明,如果有帮助,这里是我在模型上使用的片段着色器:

#version 330 core
out vec4 FragColor;

in vec3 Normal;  
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 viewPos;

vec3 lightColor = vec3(1,1,1);
vec3 objectColor = vec3(0.6, 0.6, 0.6);
uniform float shininess = 32.0f;
uniform vec3 material_specular = vec3(0.1f, 0.1f, 0.1f);
uniform vec3 light_specular = vec3(0.5f, 0.5f, 0.5f);

void main()

    // ambient
    float ambientStrength = 0.2;
    vec3 ambient = ambientStrength * lightColor;

    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
    vec3 specular = light_specular * (spec * material_specular);  

    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = vec4(result, 1.0);

这是顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;
out vec3 Normal;

uniform mat4 projection; 
uniform mat4 view; 
uniform mat4 model;

uniform float scale;

void main()

    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;  
    gl_Position = projection * view * vec4(FragPos, 1.0);

【问题讨论】:

什么是FragPosNormal?他们都在世界空间吗?您是否将FragPosNormal 从模型空间转换为世界空间? @Rabbid76 我已经添加了顶点着色器。 FragPos 在世界空间中。统一的model 是一个单位矩阵(glm::mat4(1))。 你确定model 是所有 3 个对象的单位矩阵。这 3 个对象中的每一个都不是模型转换吗? 【参考方案1】:

FragPos是世界空间中的一个位置,因为它是模型矩阵变换后的顶点位置。 lightPosviewPos 似乎也是世界空间中的位置。 所以必须将法线向量aNormal,从模型空间转换到世界空间。

您必须通过 4*4 模型矩阵的左上 3*3 的 inverse transposed 来转换法线向量:

Normal = transpose(inverse(mat3(model))) * aNormal;

可能通过 4*4 模型矩阵的左上角 3*3 进行变换就足够了: (见In which cases is the inverse matrix equal to the transpose?)

Normal = mat3(model) * aNormal;

另见:Why is the transposed inverse of the model view matrix used to transform the normal vectors?Why transforming normals with the transpose of the inverse of the modelview matrix?

【讨论】:

谢谢,我明白我做错了什么。我通过计算法线向量的世界空间来修复它:Normal = vec3(model * vec4(aNormal, 0.0));。这修复了照明。

以上是关于从 Assimp 加载 Collada (dae) 模型显示不正确的法线的主要内容,如果未能解决你的问题,请参考以下文章

将 collada (dae) 文件加载到 SCNNode (Swift - SceneKit)

从 A-Frame 或 JS 更新 Collada (.dae) 文件代码

使用 SceneKit 从 DAE/COLLADA 中提取动画顶点

带有骨骼的 Assimp + COLLADA 模型 = 不正确的顶点位置

Assimp 动画骨骼变换

Android 将 FBX 或 Collada(.dae) 加载到 Android Studio Activity 中