OPENGL读取OBJ模型(包围盒、法向等计算)附加源码与资源下载页面

本文介绍了如何在OpenGL中读取OBJ模型,详细讲解了OBJ模型格式,包括顶点、纹理坐标、法向量等,并提供了如何计算包围盒和处理无法向量模型的方法。此外,还涉及模型的平移和显示设置。
摘要由CSDN通过智能技术生成

OPENGL读取OBJ模型

标签(空格分隔): OPENGL/C++


哈哈,先贴出下载链接好吧。下载页面
首先大家不要害怕,读取obj模型听上去很高端很麻烦,其实当你真正了解obj模型的格式,以及OPENGL绘制模型的方式,你也可以很清晰地去绘制obj模型。下面就开始正题吧。

一、什么是OBJ模型

为了观察方便,你可以随便在网上下载一个OBJ模型,然后用txt或者editplus打开。然后你就会看到很整齐的数据。大致如下图所示。
image_1bg1034ojg1tio114gp1tud1tlk9.png-69kB
image_1bg105lekdrmtag17gfqp616egm.png-41kB
这些数据是什么意思呢。原来OBJ模型中记录的是一个一个的点,而一些点的组合可以组成一个面(可能是三角面片也可能是四角面片,因情况而议)OPENGL中将这些点全部画出来,然后根据组合规则连接成面,这样就可以表达出一个模型。这里给大家看一下具体例子。(盒子是我自己画的模型包围盒)
image_1bg10hijr1l0g1j1s1akc3m61b0013.png-24kB
image_1bg10hvm817beb5r11da1tqp2bh1g.png-70.2kB
清楚OPENGL如何绘制OBJ模型后,只要搞清楚OBJ中数据到底是怎么存储的就好办了。
在上图给出的OBJ模型数据中
‘#’代表着该行后面是注释。
v代表着该行后面数据记录着一个点的坐标,包含X/Y/Z。
除了v开头外,常见的还有两个。
vt开头代表着该行后面数据记录着该点的贴图坐标(与贴图有关),包含XT/YT/ZT。
vn开头代表着该行后面数据记录着该点的法向坐标(与光照有关),包含XN/YN/ZN。
f代表着该行后面的数据记录组成该面的点的索引值包含V1/V2/V3(三角面片)。
这里你可能会问到,上图中f后面数据明明是 1/2 3/4 5/6是六个数据。与你说的不符合呀。这是因为f的组成有很多形式:
①  f V1/VT1/VN1 V2/VT2/VN2 V3/VT3/VN3 (顶点/纹理坐标/法向)
② f V1/T1 V2/T2 V3/T3 (顶点/纹理坐标)
③ f V1 V2 V3 (顶点)
④ f V1//N1 V2//N2 V3//N3 (顶点//法向)
上述四种还只是三角面片的形式,面片还可能是四角、五角等。在链接给出的程序中只对上述三角面片的这四种形式做了讨论。

二、设计存储obj模型的相应类

设置好模型类。包含有顶点坐标、顶点纹理、顶点法向、面等信息和相应方法。然后按照C/C++读取文件方式去读取模型信息存入实例化类对象中。读取文件就不详述了,网上大把资料可以百度。
model.h

#include<gl/glut.h>
#include <math.h>
#include <iostream>
#include<string>
#include<fstream>
#include<sstream>
#include<vector>

using namespace std;

const float AngleToRadion = 3.14159f/180.0f;

class Objmodel {
private:
    //obj模型信息
    vector< vector<GLfloat>> vertex;
    vector< vector<GLfloat>> vertex_texture;
    vector< vector<GLfloat>> vertex_normal;
    vector< vector<int>> face_vertex;
    vector< vector<int>> face_texture;
    vector< vector<int>> face_normal;
    GLint v_num;
    GLint vt_num;
    GLint vn_num;
    GLint f_num;

    //保存obj边界点,计算模型包围盒
    vector<GLfloat > center;
    vector<GLfloat > one0fcatercorner;      //包围盒对角线中一个点
    vector<GLfloat > other0fcatercorner;        //包围盒对角线中另一个点
public:
    Objmodel();
    ~Objmodel();

    //从文件中读取数据
    void readFile(string path);

    //显示模型数据
    void showObj(GLint mode1,GLint mode2);

    //计算obj包围盒中心    并将中心平移到原点
    vector<GLfloat> getCenter();

    //计算模型顶点法向
    void calculateNormal();

    //画出模型的包围盒
    void drawBox();

};

三、实现功能

1、读取信息存入实例化对象中

C/C++读文件操作。

2、将模型移到视点前方适当位置

读入模型不一定会在你的视域内,你必须把它移动到视点前方来。这里我的思路是:
1)计算出物体的包围盒得到包围盒中心 XC/YC/ZC
 a.包围盒的根据遍历所有顶点后的Xmax、Ymax、Zmax和Xmin、Ymin、Zmin这两个点来确定。如果你害怕模型中有单独的孤立点,可以对X,Y,Z排序后取最大的十个、最小的十个来平均(程序中有实现)。
 b.计算出包围盒关键顶点,那么将它画出来也就很容易了。
2)将物体平移glTranslatef(-XC,-YC,-ZC)。
3)单单2)步骤还不够,可能还需要在Z方向上平移模型。这是需要跟你视点的角度范围θ和物体包围盒的较长的边长( ( Ymax - Ymin )或者 ( Xmax - Xmin )或者)来确定平移的距离d。如下图:
image_1bg1b1d297be1m4d1q8r3crulr9.png-59.1kB

3、对于没有法向VN的模型,计算其法向

顶点的法向在有光照的情况下对于模型的显示及其重要,因为在OPENGL默认流水线中,除了环境光,漫反射和高光都与法向有关。如果法向不正确,物体的显示会出现极大的偏差。如下图,在没有法向情况下,旋转不同的角度容易观察出模型的效果不正常(此时灯光在永远在模型前方)。
GIF.gif-1036.8kB
所以对于没有给出VN的模型,需要我们手动计算模型的法向。
那么法向如何计算呢。简单一点的方法,可以把一个面的法向当做所有组成该面的顶点的法向(使用向量叉乘求得,不记得向量叉乘几何意义的同学可以看这里

要在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文件中的各种情况进行判断和处理。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值