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

Assimp与OpenGL纹理映射问题:部分Mesh存在UV映射错误

3D模型纹理映射异常排查与修复

我使用C++、现代OpenGL(GLFW 3.4.0、GLAD、GLM)和最新版Assimp加载并渲染3D模型,模型加载器基本可用,但部分Mesh的纹理映射不正确。例如某飞机glTF模型中,大部分部件纹理正常,但座舱盖的纹理映射错误,仿佛整个模型的纹理被错误应用。已禁用混合,无论是否开启问题都存在,目前仅加载漫反射纹理。

相关代码

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;
}

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);
}

顶点着色器

#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);
}

片段着色器

#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);
}

问题排查与修复方案

1. 纹理Uniform命名不匹配

Mesh渲染时会给漫反射纹理分配diffuse0、diffuse1等Uniform名称,但片段着色器仅声明了diffuse0。若模型中存在多个漫反射纹理,或某个Mesh无漫反射纹理,会导致采样错误。

修复:

  • 若每个Mesh最多一个漫反射纹理,确保片段着色器Uniform名称与Mesh传递的一致;若支持多纹理,需修改片段着色器逻辑。
  • 检查load_material_textures中的路径匹配逻辑,确认是否存在大小写敏感或路径拼接错误。

2. Assimp纹理坐标处理遗漏

当前代码仅读取mTextureCoords[0],若座舱盖的纹理坐标存储在其他索引(如mTextureCoords[1]),会导致映射错误。

修复:
修改process_mesh中的纹理坐标读取逻辑,遍历所有可用纹理通道:

for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
    Vertex vertex;
    // ... 处理position、normal ...
    bool hasTexCoords = false;
    for (unsigned int t = 0; t < AI_MAX_NUMBER_OF_TEXTURECOORDS; t++) {
        if (mesh->mTextureCoords[t]) {
            vertex.tex_coords = glm::vec2(mesh->mTextureCoords[t][i].x, mesh->mTextureCoords[t][i].y);
            hasTexCoords = true;
            break;
        }
    }
    if (!hasTexCoords) {
        vertex.tex_coords = glm::vec2(0.0f, 0.0f);
    }
    vertices.push_back(vertex);
}

3. 纹理加载格式问题

当前纹理加载时内部格式与格式混用,可能导致兼容性问题。同时glTF模型纹理可能需要强制RGBA通道加载。

修复:
修改load_texture_from_file中的格式处理:

int width, height, number_of_color_channels;
// 强制加载RGBA通道
unsigned char* data = stbi_load(file_name.c_str(), &width, &height, &number_of_color_channels, 4);
if (data) {
    GLenum internal_format = GL_RGBA8;
    GLenum format = GL_RGBA;
    if (number_of_color_channels == 1) {
        internal_format = GL_R8;
        format = GL_RED;
    } else if (number_of_color_channels == 3) {
        internal_format = GL_RGB8;
        format = GL_RGB;
    }
    glBindTexture(GL_TEXTURE_2D, texture_id);
    glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    // ... 纹理参数设置 ...
}

4. glTF纹理坐标Y轴翻转问题

虽然使用了aiProcess_FlipUVs,但部分glTF模型的纹理坐标仍需额外翻转。

修复:
在顶点着色器中翻转纹理Y坐标:

texCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);

5. Mesh缓冲区对象管理优化

Mesh构造函数中vbo_id和ebo_id为局部变量,建议改为类成员存储,避免潜在的缓冲区对象意外释放问题。

内容的提问来源于stack exchange,提问作者Leonard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:49:50