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
相关产品推荐
相关产品推荐

