Search code examples
c++opengltexture-mappingassimp

Texture mapping issues with Assimp and OpenGL: Some meshes have incorrect UV mapping


I am trying to load and render 3D models using C++, modern OpenGL (glfw 3.4.0, glad, glm), and the newest Assimp. My model loader is mostly working, but I am encountering an issue where some meshes are not textured correctly.

For example, in this glTF aircraft model, most of the parts are mapped correctly, but the canopy texture is mapped incorrect. Instead of using the correct UV mapping, it appears as if the entire texture for the model is being applied incorrectly. I have disabled blending right now, but the issue persists with or without blending. Also, I only load the diffuse textures right now.

Here is the code of model.cpp:

#include "model.h"

Model::Model(const std::string& file_path) 
{
    load_model(file_path);
}

void Model::load_model(const std::string& file_path) 
{
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(file_path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
        std::cerr << "Assimp error: " << importer.GetErrorString() << std::endl;
        return;
    }

    directory = file_path.substr(0, file_path.find_last_of("/"));
    process_node(scene->mRootNode, scene);
}

void Model::process_node(aiNode* node, const aiScene* scene) 
{
    for (unsigned int i = 0; i < node->mNumMeshes; i++) {
        aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];

        meshes.push_back(process_mesh(mesh, scene));
    }
    for (unsigned int i = 0; i < node->mNumChildren; i++) {
        process_node(node->mChildren[i], scene);
    }
}

Mesh Model::process_mesh(aiMesh* mesh, const aiScene* scene) 
{
    std::vector<Vertex> vertices;
    std::vector<GLuint> indices;
    std::vector<Texture> textures;

    for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
        Vertex vertex;
        vertex.position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);

        if (mesh->HasNormals()) {
            vertex.normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
        }

        if (mesh->mTextureCoords[0]) {
            vertex.tex_coords = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
        } else {
            vertex.tex_coords = glm::vec2(0.0f, 0.0f);
        }

        vertices.push_back(vertex);
    }

    for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
        aiFace face = mesh->mFaces[i];
        for (unsigned int j = 0; j < face.mNumIndices; j++) {
            indices.push_back(face.mIndices[j]);
        }
    }

    aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
    std::vector<Texture> diffuse_maps = load_material_textures(material, aiTextureType_DIFFUSE, "diffuse");
    textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
    
    return Mesh(vertices, indices, textures);
}

std::vector<Texture> Model::load_material_textures(aiMaterial* material, aiTextureType type, const std::string& type_name) 
{
    std::vector<Texture> textures;
    
    for (unsigned int i = 0; i < material->GetTextureCount(type); i++) {

        aiString str;
        material->GetTexture(type, i, &str);

        bool skip = false;
        for (const auto& loaded_texture : textures_loaded) {
            if (std::strcmp(loaded_texture.path.data(), str.C_Str()) == 0) {
                textures.push_back(loaded_texture);
                skip = true;
                break;
            }
        }
        if (!skip) {
            Texture texture;
            texture.id = load_texture_from_file(str.C_Str(), directory);
            texture.type = type_name;
            texture.path = str.C_Str();
            textures.push_back(texture);
            textures_loaded.push_back(texture);
        }
    }
    return textures;
}

void Model::render(Shader& shader, Camera& camera) {
    for (auto& mesh : meshes) {
        mesh.render(shader, camera);
    }
}

GLuint load_texture_from_file(const char* file_path, const std::string& directory) 
{
    std::string file_name = directory + "/" + file_path;
    GLuint texture_id;
    glGenTextures(1, &texture_id);

    int width, height, number_of_color_channels;
    unsigned char* data = stbi_load(file_name.c_str(), &width, &height, &number_of_color_channels, 0);
    if (data) {
        GLenum format = (number_of_color_channels == 1) ? GL_RED : (number_of_color_channels == 3) ? GL_RGB : GL_RGBA;
        glBindTexture(GL_TEXTURE_2D, texture_id);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        stbi_image_free(data);
    } else {
        std::cerr << "Texture failed to load: " << file_path << std::endl;
        stbi_image_free(data);
    }

    std::cout << file_name << "\n";

    return texture_id;
}

And here is the code of mesh.cpp:

#include "mesh.h"
#include <iostream>

Mesh::Mesh(std::vector <Vertex>& vertices, std::vector <GLuint>& indices, std::vector <Texture>& textures) 
{
    Mesh::vertices = vertices;
    Mesh::indices = indices;
    Mesh::textures = textures;

    GLuint vbo_id, ebo_id;

    glGenVertexArrays(1, &vao_id);
    glGenBuffers(1, &vbo_id);
    glGenBuffers(1, &ebo_id);

    glBindVertexArray(vao_id);

    glBindBuffer(GL_ARRAY_BUFFER, vbo_id);

    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);  

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_id);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);   
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);

    glEnableVertexAttribArray(1);   
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));

    glEnableVertexAttribArray(2);   
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
}

void Mesh::render(Shader& shader_program, Camera& camera) 
{
    shader_program.use();

    unsigned int diffuse_number = 0;
    unsigned int specular_number = 0;
    unsigned int normal_number = 0;

    for (unsigned int i = 0;  i < textures.size(); i++)
    {
        glActiveTexture(GL_TEXTURE0 + i);
        
        std::string number;
        std::string type = textures[i].type;

        if (type == "diffuse")
        {
            number = std::to_string(diffuse_number++);  
        }

        else if (type == "specular")
        {
           number = std::to_string(specular_number++);
        }

        else if (type == "normal")
        {
           number = std::to_string(normal_number++);
        }

        glUniform1i(glGetUniformLocation(shader_program.id, (type + number).c_str()), i);
        glBindTexture(GL_TEXTURE_2D, textures[i].id);
    }

    glBindVertexArray(vao_id);
    glDrawElements(GL_TRIANGLES, static_cast<unsigned int>(indices.size()), GL_UNSIGNED_INT, 0);

    glBindVertexArray(0);
    glActiveTexture(GL_TEXTURE0);
}

Vertex shader:

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;

out vec3 crntPos;
out vec3 Normal;
out vec2 texCoord;

uniform mat4 view;
uniform mat4 proj;
uniform mat4 model;

void main()
{
    crntPos = vec3(model * vec4(aPos, 1.0f));

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

    texCoord = aTexCoord;

    gl_Position = proj * view * model * vec4(aPos, 1.0);
}

Fragment shader:

#version 330 core

out vec4 FragColor;

in vec3 crntPos;
in vec3 Normal;
in vec2 texCoord;

uniform sampler2D diffuse0;

uniform vec4 lightColor;
uniform vec3 camPos;

void main()
{
    float ambient = 0.20f;
    vec3 lightDirection = vec3(0.0f, 1.0f, 0.0f);

    vec3 normal = normalize(Normal);

    float diffuse = max(dot(normal, lightDirection), 0.0f);

    FragColor = texture(diffuse0, texCoord) * lightColor * (diffuse + ambient);
}

I have tried to change the flags from assimp like aiProcess_FlipUVs or aiProcess_GenUVCoords, but nothing worked. I also tried GL_CLAMP_TO_EDGE instead of GL_REPEAT. I think that the correct texture files are loaded.


Solution

  • The canopy in your F-22 model uses the following material:

    {
      "alphaMode": "BLEND",
      "doubleSided": true,
      "name": "Glass",
      "pbrMetallicRoughness": {
        "baseColorFactor": [
          1.0,
          0.5019285408987041,
          0.1381190545019856,
          0.5644862352091373
        ],
        "roughnessFactor": 0.0
      }
    }
    

    which does not reference any texture. Therefore, your Mesh::render function does not update the diffuse0 uniform and renders the mesh using the same material that was used previously.

    As a quick way to move forward you could synthesize a 1x1 texture using the RGBA components given in baseColorFactor (no idea what that corresponds to in assimp) and put that in the Mesh::textures array.

    Note that the usual caveats apply about rendering transparent objects, so make sure the canopy is rendered last.