Qt OpenGL加载OBJ模型

在加载OBJ模型文件之前首先要对OBJ文件的内容有所了解,在3d模型网站https://free3d.com/3d-models/3d-printable-obj

随便找了一个模型

它的OBJ文件内容为:

# File exported by ZBrush version 4.4
# www.zbrush.com
#Vertex Count 20545
#UV Vertex Count 15953
#Face Count 20559
#Auto scale x=0.022208 y=0.022208 z=0.022208
#Auto offset x=-0.531189 y=-0.179415 z=0.083041
mtllib stickman2.mtl
usemtl defaultMat
v 14.23160078 -34.12469952 6.2026003
v 14.47159986 -34.17430379 6.32490063
v 14.4308002 -34.00520591 6.28000128
v 14.26249945 -33.91609966 6.17129972

...

vt 0.699 0.3513
vt 0.7029 0.352
vt 0.7295 0.3476

...

f 390/393 398/401 371/374 378/381
f 399/402 401/404 402/405 400/403
f 365/368 372/375 400/403 402/405

...

\n

注意:文件末尾会有一个\n字符。而3dsmax建模出的文件是\r\n..

其中最为关键的为开头为"v","vt","f"的几行数据,“v”代表了顶点数据,“vt”代表贴图坐标也及uv坐标数据,“f”代表了面数据,

其中f 1 2 3 4代表四边形顶点,f 1 2 3代表三角顶点,f 1/1 2/2 3/3 代表顶点索引/纹理索引,f 1//1 2//2 3//3 代表顶点索引//法线索引,f 1/1/1 2/2/2 3/3/3 代表顶点索引/纹理索引/法线索引.索引及代表这是第几个元素,如f 390/393 398/401 371/374 378/381

表示这个面有四个顶点分别是第390个顶点/393个uv ,第398个顶点/401个uv...。

先从最简单的读取单纯的顶点数据开始:

本文在Qt下使用OpenGL,选择使用QOpenGLExtraFunctions类采用OpenGL原生API进行编写。

读取数据部分:

bool ObjLoader::Load(QString fileName, QVector<float> &vPoints)
{
	if (fileName.mid(fileName.lastIndexOf('.')) != ".obj"&&fileName.mid(fileName.lastIndexOf('.')) != ".OBJ")
	{
		qDebug() << "file is not a obj file!";
		return false;
	}
	QFile objfile(fileName);
	if (!objfile.open(QIODevice::ReadOnly))
	{
		qDebug() << "open" << fileName << "failed";
		return false;
	}
	else
	{
		qDebug() << "open" << fileName << "success!";
	}
	QVector<float> vertextPoints, texturePoints;
	QVector<std::tuple< int, int>> facesIndexs;

	while (!objfile.atEnd())
	{
		QByteArray lineData = objfile.readLine();

		lineData = lineData.remove(lineData.count() - 2, 2);
		qDebug() << lineData;

		if (lineData == "")
			continue;
		QList<QByteArray> strValues = lineData.split(' ');
		strValues.removeAll("");
		QString dataType = strValues.takeFirst();
		if (dataType == "v")
		{
			for(int i=0;i<strValues.count();i++)
			{
				if (strValues[i] != "")
					vertextPoints.push_back( strValues[i].toFloat() );
			}

		}

		else if (dataType == "f")
		{
			if (strValues.size() == 4)
			{
				strValues.push_back(strValues.at(0));
				strValues.push_back(strValues.at(2));

			}
			std::transform(strValues.begin(), strValues.end(), std::back_inserter(facesIndexs), [](QByteArray &str) {
				QList<QByteArray> intStr = str.split('/');

				return std::make_tuple(intStr.first().toInt(), intStr.last().toInt());

			});

		}

	}
	if (vertextPoints.count() != 0)
	{
		qDebug() <<"vertpoints: "<< vertextPoints.count();
	}
	else
	{
		qDebug() << "none vert points";
		return false;
	}

	
	if (facesIndexs.count() != 0)
	{
		qDebug() << "facepoints: "<<facesIndexs.count();
	}
	else
	{
		qDebug() << "none faces";
		return false;
	}

	for (auto &verFaceInfo:facesIndexs)
	{
		int vIndex = std::get<0>(verFaceInfo);

		int vPointSizes = vertextPoints.count() / 3;
		//将顶点坐标放入
		vPoints << vertextPoints.at(vIndex * 3 - 3);
		vPoints << vertextPoints.at(vIndex * 3 - 2);
		vPoints << vertextPoints.at(vIndex * 3 - 1);
	}
	vertextPoints.clear();
	facesIndexs.clear();

	objfile.close();
	return true;
}

此代码改自https://blog.csdn.net/wanghualin033/article/details/84642286

整体思路便是 读取文件的每一行,删除掉最后的一个字符'\r\n' 并判断头个字符进行操作。先不管uv坐标.

有一个细节是此OBJ文件里的面有三个点成面的也有四个点成面的,我统一存储为三个点及把四个数据变为六个数据(顶点0 1 2 3 变为顶点 0 1 2 3 0 2)便于绘制.

将数据存储于容器中。在绘制函数中进行调用。

先初始化OpenGL绘制的环境:

void MyWidget::initializeGL()
{

	showNormal();
	setGeometry(0, 0, 800, 600);
	_program.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex, "vsrc.vert");
	_program.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment, "fsrc.frag");
	if (_program.link())
	{
		qDebug() << "link success!";
	}
	else
	{
		qDebug() << "link failed";
	}
	_objLoader.Load("C:/Users/Administrator/Desktop/obj/43n0m0fl66o0-stickman/stickman.OBJ", _vertPoints);
	qDebug() << _vertPoints.count();

	f = QOpenGLContext::currentContext()->extraFunctions();
	f->glGenVertexArrays(1, &VAO);
	f->glGenBuffers(1, &VBO);

	f->glBindBuffer(GL_ARRAY_BUFFER, VBO);
	f->glBufferData(GL_ARRAY_BUFFER, _vertPoints.size()*sizeof(float), &_vertPoints[0], GL_STATIC_DRAW);

	f->glBindVertexArray(VAO);

	f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0);
	f->glEnableVertexAttribArray(0);
	f->glBindVertexArray(0);

	vMatrix.lookAt(_cameraLocation, QVector3D(0.0, 0.0, 0.0), QVector3D(0.0, 1.0, 0));
}

此处又遇到了一个问题,    f->glBufferData(GL_ARRAY_BUFFER, _vertPoints.size()*sizeof(float), &_vertPoints[0], GL_STATIC_DRAW);其中的第二个参数 sizeof(vector)是不能返回一个vector的总数据大小的,是一个相当小的定值,以至于在想怎么把数据传进去时出了许多问题:int* arr = new int[10];

  sizeof(arr)返回的是arr[0]的大小而不是指针代表的数组的大小。

而int arr[10] 这种方式sizeof(arr)返回的就是数组的大小。

而第二种方式的数组的大小需要在编译时就确认也就是无法跟着读取数据大小的变化而改变,想了许久之后才发现,原来是sizeof(vector)这个地方出了问题,只传了一点数据进去,也就谈不上能绘制出来了。。改成_vertPoints.size()*sizeof(float)就解决了问题。

最后进行绘制:

void MyWidget::paintGL()
{
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	_program.bind();
	f->glBindVertexArray(VAO);
	_program.setUniformValue("uPMatrix", pMatrix);
	_program.setUniformValue("uVMatrix", vMatrix);
	_program.setUniformValue("uMMatrix", mMatrix);

	glDrawArrays(GL_TRIANGLES, 0, _vertPoints.size()/3);第三个参数是要绘制的顶点数
	f->glBindVertexArray(0);

	update();
}

加以最简单的shader:

fragshader

#version 330

out vec4 fragColor;
 
void main(void)
{
    fragColor = vec4(1.0f,1.0f,1.0f,1.0f);
}

vertexshader 

#version 330
uniform mat4 uPMatrix,uVMatrix,uMMatrix;
layout (location = 0) in vec3 aPosition;

void main(void)
{
    gl_Position = uPMatrix * uVMatrix * uMMatrix * vec4(aPosition,1);
}

出来吧:皮卡丘

感觉有点奇怪。

换种方式绘制试试:

    glDrawArrays(GL_POINTS, 0, _vertPoints.size() );

没想到就是加载个模型也是如此困难重重。。之后加上光影和uv再看看效果吧。

 

 

 

 

要在Qt OpenGL中读取OBJ模型文件,可以使用Qt自带的QOpenGLFunctions库。 首先,需要在项目文件中添加以下依赖: ``` QT += opengl ``` 然后,可以使用QOpenGLFunctions类来加载模型文件和绘制模型。 以下是一个简单的示例代码,可以读取和绘制一个OBJ模型文件: ```c++ #include <QOpenGLFunctions> #include <QOpenGLShaderProgram> #include <QOpenGLBuffer> #include <QVector3D> #include <QVector2D> #include <QFile> #include <QStringList> struct VertexData { QVector3D position; QVector2D texCoord; }; class ObjModel : protected QOpenGLFunctions { public: ObjModel(); virtual ~ObjModel(); void init(QString filename); void render(); private: QOpenGLShaderProgram m_program; QOpenGLBuffer m_vbo; int m_vertexCount; }; ObjModel::ObjModel() : m_vertexCount(0) { } ObjModel::~ObjModel() { m_vbo.destroy(); } void ObjModel::init(QString filename) { initializeOpenGLFunctions(); // Load OBJ file QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QVector<QVector3D> positions; QVector<QVector2D> texCoords; while (!file.atEnd()) { QByteArray line = file.readLine().trimmed(); QList<QByteArray> tokens = line.split(' '); if (tokens.isEmpty()) continue; if (tokens[0] == "v") { positions.append(QVector3D(tokens[1].toFloat(), tokens[2].toFloat(), tokens[3].toFloat())); } else if (tokens[0] == "vt") { texCoords.append(QVector2D(tokens[1].toFloat(), tokens[2].toFloat())); } else if (tokens[0] == "f") { for (int i = 1; i < tokens.size(); ++i) { QList<QByteArray> face = tokens[i].split('/'); VertexData data; data.position = positions[face[0].toInt() - 1]; data.texCoord = texCoords[face[1].toInt() - 1]; m_vertices.append(data); } } } m_vertexCount = m_vertices.size(); // Create VBO m_vbo.create(); m_vbo.bind(); m_vbo.allocate(m_vertices.constData(), m_vertexCount * sizeof(VertexData)); // Load shader program m_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/obj.vert"); m_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/obj.frag"); m_program.link(); } void ObjModel::render() { m_program.bind(); m_vbo.bind(); m_program.enableAttributeArray("position"); m_program.enableAttributeArray("texCoord"); m_program.setAttributeBuffer("position", GL_FLOAT, offsetof(VertexData, position), 3, sizeof(VertexData)); m_program.setAttributeBuffer("texCoord", GL_FLOAT, offsetof(VertexData, texCoord), 2, sizeof(VertexData)); glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); m_vbo.release(); m_program.release(); } ``` 在上面的示例代码中,我们使用QFile类来读取OBJ文件,然后使用QOpenGLBuffer类创建一个VBO,并将OBJ文件中的顶点数据存储到VBO中。最后,使用QOpenGLShaderProgram类加载并绑定着色器程序,并使用glDrawArrays函数绘制模型。 注意,上面的代码仅仅是一个简单的示例,不足以处理所有的OBJ文件。在实际开发中,还需要对OBJ文件中的各种情况进行判断和处理。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值