LearnOpenGL 模型加载—模型(二 绘制模型)

本文详细介绍了在使用Assimp库加载3D模型时遇到的两个问题:纹理单元匹配错误和纹理坐标翻转。作者通过修改代码,解决了纹理采样器与纹理单元不一致导致的渲染问题,并调整了纹理坐标处理以避免重复翻转。此外,还展示了如何加载和绘制3D模型,并给出了源码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

原文链接。原文应该是github上的一个项目,本文主要用来记录一些知识点和自己遇到的问题。

和箱子模型告别

所以,让我们导入一个由真正的艺术家所创造的模型,替代我这个天才的作品(你要承认,这些箱子可能是你看过的最漂亮的立方体了),测试一下我们的实现吧。由于我不想让我占太多的功劳,我会偶尔让别的艺术家也加入我们,这次我们将会加载Crytek的游戏孤岛危机(Crysis)中的原版纳米装(Nanosuit)。这个模型被输出为一个.obj文件以及一个.mtl文件,.mtl文件包含了模型的漫反射、镜面光和法线贴图(这个会在后面学习到),你可以在这里下载到(稍微修改之后的)模型,注意所有的纹理和模型文件应该位于同一个目录下,以供加载纹理。
在这里插入图片描述
现在在代码中,声明一个Model对象,将模型的文件位置传入。接下来模型应该会自动加载并(如果没有错误的话)在渲染循环中使用它的Draw函数来绘制物体,这样就可以了。不再需要缓冲分配、属性指针和渲染指令,只需要一行代码就可以了(真爽!)。接下来如果你创建一系列着色器,其中片段着色器仅仅输出物体的漫反射纹理颜色,最终的结果看上去会是这样的:
在这里插入图片描述

你可以在这里找到完整的源码。

这里有一个很坑的问题,可能让你得不到正确结果……事实上我也费了很多时间来debug,下面来说一下。

坑点

主要是由0号纹理单元引起的……
在这里插入图片描述
原文这里的代码其实是有点问题的,我们看一下他这里给出的着色器代码:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture_diffuse1;

void main()
{
       
    FragColor = texture(texture_diffuse1, TexCoords);
}

再看我们在Mesh对象的Draw方法中设置的采样器名称:
在这里插入图片描述
很明显这里是对不上的,那么为什么我们还能看到模型呢?是因为0号纹理单元默认是一直激活的,在GLSL中的纹理采样器默认都会绑定到0号纹理单元,而我们在代码中恰好又绑定了纹理对象到这个纹理单元,所以能看到模型。那么我们可以改一下这个函数,让其激活并设置非零号纹理单元,就可以解决问题了——实际上并不能,你可以看一下texture对象的构造函数,其内调用了glBindTexture函数,也就是说只要使用了纹理对象,0号纹理单元上就一定绑定了某个纹理对象,那么在GLSL中进行采样时就一定要小心,采样的结果可能并不是黑色!出于这个原因我决定修改命名规则,我们使用sampler2D数组,同时传递两个整数给gpu,告诉它我们使用了多少个漫反射贴图和镜面光贴图。

第二个坑点是图片y轴的翻转问题。我们在纹理章节已经学过了:OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。那么我们利用这个函数翻转图片的y轴:
在这里插入图片描述
但是还记得我们在导入obj文件的时候设置了什么吗?
在这里插入图片描述
aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标。如果你感觉贴图很奇怪的话,可能就是这里的问题,两个地方都翻转了等于没有翻转。

回到主题

修改后的代码如下:

m e s h : mesh: mesh:

#pragma once
#ifndef MESH_H
#define MESH_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shader.h"
#include <vector>
#include "texture.h"
#include <string>
#include <iostream>
using std::vector;
using std::string;
using std::cout;
using std::max;

struct Vertex
{
   
	// 位置 法向量 纹理坐标
	glm::vec3 Position;
	glm::vec3 Normal;
	glm::vec2 TexCoords;
};

class Mesh
{
   
public:
	/* 网格数据 */
	vector<Vertex> vertices;
	vector<unsigned int> indices;
	vector<Texture> textures;
	/* 函数 */
	Mesh(const vector<Vertex>& vertices, const vector<unsigned int>& indices, const vector<Texture>& textures);
	void Draw(const Shader& shader);
private:
	/* 渲染数据 */
	unsigned int VAO, VBO, EBO;
	
	void setupMesh();
};

#endif // !MESH_H

#include "mesh.h"

Mesh::Mesh(const vector<Vertex>& vertices, const vector<unsigned int>& indices, const vector<Texture>& textures) :
	vertices(vertices), indices(indices), textures(textures)
{
   
	setupMesh();
}

void Mesh::setupMesh()
{
   
	// 初始化VAO VBO EBO
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);

	// 绑定VAO
	glBindVertexArray(VAO);
	// 绑定VBO 复制顶点数据
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
	// 绑定EBO 复制索引数据
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

	// 顶点位置
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
	glEnableVertexAttribArray(0);
	// 顶点法线
	// C++内置的offsetof函数 能自动返回结构对象中 某变量距离结构体对象首地址的偏移值:
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
	glEnableVertexAttribArray(1);
	// 顶点纹理坐标
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
	glEnableVertexAttribArray(2);
	
	// 解绑VAO
	glBindVertexArray(0);
}


unsigned int maxDiffuseNr = 0;
unsigned int maxSpecularNr = 0;

void Mesh::Draw(const Shader& shader)
{
   
	// 当前漫反射纹理和镜面光纹理的编号
	unsigned int diffuseNr = 0;
	unsigned int specularNr = 0;
	for (unsigned int i = 0; i < textures.size(); i++)
	{
   
		// 激活纹理单元 并绑定
		textures[i].use(i);
		// 获取纹理序号和类型
		string number;
		string type = textures[i].getName();
		if (type == "texture_diffuse")
			number = std::to_string(diffuseNr++);
		else if (type == "texture_specular")
			number = std::to_string(specularNr++);
		shader.setInt("material." + type + "[" + number + "]", i);
	}
	shader.setInt(string("material.") + "texture_diffuse_num", diffuseNr);
	shader.setInt(string("material.") + "texture_specular_num", specularNr);

	maxDiffuseNr = max(maxDiffuseNr, diffuseNr);
	maxSpecularNr = max(maxSpecularNr, specularNr);
	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
	// always good practice to set everything back to defaults once configured.
	glActiveTexture(GL_TEXTURE0);
}

m o d e l : model: model:

#pragma once
#ifndef MODEL_H
#define MODEL_H
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "mesh.h"
#include <unordered_map>

using std::unordered_map;

class Model
{
   
	/* 函数 */
public:
	Model(const string& path)
	{
   
		loadModel(path);
	}
	void Draw(const Shader& shader);
private:
	/* 模型数据 */
	vector<Mesh> meshes;
	/* 模型数据所在目录 */
	string directory;
	/* 记录已经加载过的纹理 */
	vector<Texture> texturesLoaded;
	/* 纹理文件到索引的哈希表 */
	unordered_map<string, unsigned int> texturesHashTable;

	void loadModel(const string& path);
	void processNode(aiNode* node, const aiScene* scene);
	Mesh processMesh(aiMesh* mesh, const aiScene* scene);
	vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, const string& typeName);
};

#endif // !MODEL_H

#include "model.h"
#include <iostream>
using std::cout;
using std::endl;

extern unsigned int maxDiffuseNr;
extern unsigned int maxSpecularNr;
bool firstDraw = false;

void Model::Draw(const Shader& shader)
{
   
	for (unsigned int i = 0; i < meshes.size(); i++)
		meshes[i].Draw(shader);

	if (!firstDraw)
	{
   
		cout << "First Draw finish\n";
		cout << "Max diffuse texture number is " << maxDiffuseNr << '\n';
		cout << "Max specular texture number is " << maxSpecularNr << '\n';
		firstDraw = 1;
	}
}

void Model::loadModel(const string& path)
{
   
	Assimp::Importer import;
	// 读取模型文件 第二个参数用于后期处理
	const aiScene* scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
	// 是否读取成功
	if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
	{
   
		cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl;
		return;
	}
	// 获取文件目录
	directory = path.substr(0, path.find_last_of('/'));
	// 从场景根节点开始递归处理所有节点
	processNode(scene->mRootNode, scene);

	// 打印obj的信息
	cout << "Loading success!\n";
	cout << "This model have " << meshes.size() << " meshes\n\n";
}

void Model::processNode(aiNode* node, const aiScene* scene)
{
   
	// 处理节点的所有网格
	for 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值