基于QT使用OpenGL,加载obj模型,进行鼠标交互

目录

  • 功能分析(需求分析)
  • 技术点分析
    • OpenGL
      • 立即渲染模式
      • 可编程渲染管线模式
    • QOpenGLWidget
      • 派生类 glwidget逻辑
        • glwidget.h
        • glwidget.cpp
    • 鼠标交互功能
    • obj格式介绍
  • 效果
    • bunny
    • Cayman_GT

功能分析(需求分析)

  1. 基于QT平台,使用OpenGL进行obj文件加载显示;
  2. 使用鼠标对场景进行缩放、移动、旋转交互;

技术点分析

OpenGL

  OpenGL是基于C的,学习曲线比较抖,但是总的来说就是下面一幅图,
基于QT使用OpenGL,加载obj模型,进行鼠标交互_第1张图片

  用语言简单的描述(个人理解,可能不太准确)是把cpu里内存里的3D数据,传输到显卡的内存里,以及如何转换成2D平面上像素点显示(也就是矩阵变换,在好多开源的框架里都进行了进一步封装,形成了渲染器、场景、相机等)。这个数据是顶点坐标、颜色等,传输跟送快递一样,除了要有数据本身之外,还要有其他信息,也就是要有个约定,到显卡拿到数据之后怎么解析。
  有个网站比较有名learnopengl,可以对着学一遍,在网上找资料的时候,需要注意是立即渲染模式,还是可编程渲染管线模式,现在官方推荐是使用可编程渲染管线模式开发。

立即渲染模式

glBegin(GL_TRIANGLES)
glTranslatef(1,2,3);
// 其他操作
// glVertex*() 设置顶点坐标
// glColor*() 设置当前颜色
// glIndex*() 设置当前颜色表
// glNormal*() 设置法向坐标
// glEvalCoord*() 产生坐标
// glTexCoord*() 设置纹理坐标
// glEdgeFlag*() 控制边界绘制
// glMaterial*() 设置材质
glEnd()

可编程渲染管线模式

1、创建VBO顶点数据对象
	GLuint VBO;
	glGenBuffers(1,&VBO);
2、VBO与显卡缓存绑定
	glBindBuffer(GL_ARRAY_BUFFER, VBO)
3、绑定数据缓存对象
	glBufferData(GL_ARRAY_BUFFER,sizeof(vertexs),vertex,)
4、数据格式
	glVertexAttribPoint(0,3,GL_FLOAT,)
5、启用/绘制
	glEnableVertexAttribArray(0)
	glDrawArrays(GL_TRIANGLES, 0, 10);
6、最后关闭
	glBindBuffer(GL_ARRAY_BUFFER,0)
	glBindVertexArray(0);

  以上两段代码只是展示两种模式区别,很明显立即渲染模式容易理解,但是性能有限制,可编程渲染管线模式理解和使用门槛高,但更能接触底层。

QOpenGLWidget

  QT对OpenGL进行了封装,提供了QOpenGLWidget类,只需要对其继承,在initialzeGL()resizeGL()paintGL()逻辑下进行业务实现,包括VBO、VAO的创建与绑定,着色器程序的编译与链接等。

派生类 glwidget逻辑

  先简单介绍派生类的写法,当然为了方便扩展和使用,抽象出了相机类、渲染器类、物体类,完整的工程代码资源可以下载参考,具体的可以根据自己要实现的功能进行编写。

glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "utils/Common.h"
#include "genericRender.h"
#include "grid.h"
#include "coorsystem.h"
#include "camera.h"

class glwidget :public QOpenGLWidget, QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    glwidget(QWidget * parent = nullptr);
    //鼠标交互事件重写
    void mouseMoveEvent(QMouseEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;
    //键盘交互
    void keyReleaseEvent(QKeyEvent *event) override; //按键释放事件
    void keyPressEvent(QKeyEvent *event) override;   //按键按下事件
    void slotTimeOut();
protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
public:
    GenericRender m_render;//渲染器
    Camera m_camera;//相机
    grid m_grid;//网格平面xy
    coorSystem m_coordsys;//坐标系
    QTimer tm_;
public:
    QList<int> keys;
    QTimer* keyRespondTimer;	//头文件中添加成员
    static int mouse_button;
    static int modifier_key;
public:
    //鼠标变量
    static double mouse_pos_x_old;
    static double mouse_pos_y_old;
    bool changeview_ = false;            //改变视角的标志位
    float fov = 2.0f;          //视野范围
};
#endif // GLWIDGET_H
glwidget.cpp
#include "glwidget.h"
#include "box.h"
int glwidget::mouse_button = -1;
int glwidget::modifier_key = 0;
double glwidget::mouse_pos_x_old = 0;
double glwidget::mouse_pos_y_old = 0;
glwidget::glwidget(QWidget * parent):QOpenGLWidget(parent)
{
    keyRespondTimer = new QTimer(this);	//构造函数中创建定时器对象,并连接信号槽
    connect(keyRespondTimer, &QTimer::timeout, this, &glwidget::slotTimeOut);
    setFocusPolicy(Qt::StrongFocus);//否则进入不了键盘事件监听
}

void glwidget::initializeGL()
{
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    m_render.init();
    m_camera.setMatPro(fov);
    QStringList renderfiles={":/obj/data/Cayman_GT.obj", ":/obj/data/bunny_10k.obj"};
    for(QString file : renderfiles){
        Box* b1 = new Box();
        b1->load(file);
        m_render.addBox(b1);
    }
    m_grid.initize();
    m_coordsys.initize();
}

void glwidget::resizeGL(int w, int h)
{
    m_camera.setRatio((float)width() / (float)height());
}

void glwidget::paintGL()
{
    glClearColor(1.0f, 1.0f, 1.0f, 0.1f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPolygonMode(GL_FRONT, GL_LINE);
    QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
    QMatrix4x4 mMatrix;
    m_camera.setMatPro(fov);
    m_grid.paint(f,m_camera.getMatPro(),m_camera.getMatView(),mMatrix);
    m_coordsys.paint(f,m_camera.getMatPro(),m_camera.getMatView(),mMatrix);
    m_render.render(f,m_camera,mMatrix);
}

void glwidget::mousePressEvent(QMouseEvent *event){
    mouse_pos_x_old = event->pos().x();
    mouse_pos_y_old = event->pos().y();
    mouse_button = event->button();
}

void glwidget::mouseMoveEvent(QMouseEvent *event){
    int x = event->pos().x();
    int y = event->pos().y();
    double w = width(), h = height();
    double d_x = (mouse_pos_x_old - x)/w;
    double d_y = (mouse_pos_y_old - y)/h;
    if (mouse_button == Qt::LeftButton)
    {
        foreach (int key, keys) {
            switch (key) {
            case Qt::Key_Control:
                m_camera.rotateY(-d_x*180*5);
                m_camera.rotateX(-d_y*180*5);
                break;
            case Qt::Key_Shift:
                m_camera.move(5*d_x, 5*d_y, 0);
                break;
            default:
                break;
            }
        }
    }
    mouse_pos_x_old = x;
    mouse_pos_y_old = y;
    this->repaint();
}

void glwidget::mouseReleaseEvent(QMouseEvent *event){  //鼠标左键松开禁止改变相机视角
    changeview_ = false;
}

void glwidget::wheelEvent(QWheelEvent *event){
    if (event->delta() > 0)
        fov-=2.0f;
    else
        fov+=2.0f;
    if (fov<0.50f)
        fov = 0.5f;
    if (fov>=200.f)
        fov = 200.0;
    this->repaint();
}

void glwidget::keyReleaseEvent(QKeyEvent *event)
{
    if(!event->isAutoRepeat())  //判断如果不是长按时自动触发的释放,就将key值从容器中删除
        keys.removeAll(event->key());
    if(keys.isEmpty()) //容器空了,关闭定时器
        keyRespondTimer->stop();
}

void glwidget::keyPressEvent(QKeyEvent *event)
{
    if(!event->isAutoRepeat())  //判断如果不是长按时自动触发的按下,就将key值加入容器
        keys.append(event->key());
    if(!keyRespondTimer->isActive()) //如果定时器不在运行,就启动一下
        keyRespondTimer->start(4);
}

void glwidget::slotTimeOut(){
    foreach (int key, keys) {
        switch (key) {
        case Qt::Key_D:
            break;
        case Qt::Key_Shift:
            modifier_key = Qt::Key_Shift;
            break;
        default:
            break;
        }
    }
}

鼠标交互功能

  交互的功能实现是重载QOpenGLWidget的鼠标事件函数,修改相应的矩阵,也就对应最上面的相机变化、灯光变化等;

obj格式介绍

  OBJ文件(.obj)包含有关3D对象的几何体的信息,下面是一个长方体的obj格式文件;

#
# Object file
#
mtllib Cube3x3x10.mtl
# Cube3x3x10\实体
v 0 0 0
v 3.00000002607703 0 0
v 3.00000002607703 0 9.99999977648258
v 0 0 9.99999977648258
v 3.00000002607703 3.00000002607703 9.99999977648258
v 0 3.00000002607703 9.99999977648258
v 0 0 9.99999977648258
v 3.00000002607703 0 9.99999977648258
v 0 3.00000002607703 9.99999977648258
v 0 3.00000002607703 0
v 0 0 0
v 0 0 9.99999977648258
v 0 3.00000002607703 0
v 3.00000002607703 3.00000002607703 0
v 3.00000002607703 0 0
v 0 0 0
v 3.00000002607703 3.00000002607703 0
v 3.00000002607703 3.00000002607703 9.99999977648258
v 3.00000002607703 0 9.99999977648258
v 3.00000002607703 0 0
v 3.00000002607703 3.00000002607703 9.99999977648258
v 3.00000002607703 3.00000002607703 0
v 0 3.00000002607703 0
v 0 3.00000002607703 9.99999977648258
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vt 0 0
vt 0 0.00300000002607703
vt 0.00999999977648258 0.00300000002607703
vt 0.00999999977648258 0
vt 0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0
vt 0.00150000001303852 0
vt 0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0
vt 0.00499999988824129 0
vt 0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0
vt 0.00150000001303852 0
vt 0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0
vt 0.00499999988824129 0
vt 0.00999999977648258 0.00300000002607703
vt 0 0.00300000002607703
vt 0 0
vt 0.00999999977648258 0
o Cube3x3x10\实体
s off
# face 0
f 2/2/2 3/3/3 1/1/1
f 1/1/1 3/3/3 4/4/4
# face 1
f 5/5/5 6/6/6 8/8/8
f 8/8/8 6/6/6 7/7/7
# face 2
f 9/9/9 10/10/10 12/12/12
f 12/12/12 10/10/10 11/11/11
# face 3
f 13/13/13 14/14/14 16/16/16
f 16/16/16 14/14/14 15/15/15
# face 4
f 17/17/17 18/18/18 20/20/20
f 20/20/20 18/18/18 19/19/19
# face 5
f 21/21/21 22/22/22 24/24/24
f 24/24/24 22/22/22 23/23/23

  其中

  • mtllib Cube3x3x10.mtl   表示引用的材质文件的文件名
  • v 0 0 0          表示一个点的xyz坐标,使用空格隔开
  • vn 0 -1 0        表示一个点的法向量,使用空格隔开
  • vt 0.09 0        表示uv纹理坐标,使用空格隔开
  • f 2/2/2 3/3/3 1/1/1    表示一个面, 2/2/2依次为顶点索引,纹理坐标索引,法向索引,因为是三角面片,所以是三组,也有超多三个点的,自己造轮子的时候要注意
  • o Cube      表示指定了模型名称为Cube
  • s off       表示关闭光滑组

void ObjLoader::load(QString filename)
{
    // 打开文件
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly)){
        qDebug()<<"[Error] fail to open file: "<< filename;
        return;
    }
    // 读取文件
    QTextStream ts(&file);
    // 临时存储
    QVector<QVector3D> v;
    QVector<QVector3D> vn;
    QVector<QVector3D> vt;
    QVector<QStringList> str_faces;
    QVector<Face> faces;
    while(!ts.atEnd()){
        QStringList list = ts.readLine().split(QRegExp("(\\s+)"));
        list.removeAll(" ");
        if(list.size() == 0 )
            break;
        if(list[0] == "v")
            v.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),list[3].toFloat()));
        if(list[0] == "vn")
            vn.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),list[3].toFloat()));
        if(list[0] == "vt")
            vt.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),0));
        if(list[0] == "f")
            str_faces.push_back(list);
    }

    for( int i =0; i< str_faces.size(); i++)
    {
        Face face;
        for( int j = 1; j<=3; j++)  //obj中顶点索引是从1开始
        {
            QStringList list = str_faces[i][j].split("/");
            face.v[j-1] = list[0].toInt() -1;
            face.t[j-1] = list[1].toInt() -1;
            face.n[j-1] = list[2].toInt() -1;
        }
        faces.push_back(face);
    }

    for( int i = 0; i<faces.size();i++)
    {
        QVector3D a,b,c,na,nb,nc;
        a = v[faces[i].v[0]];
        b = v[faces[i].v[1]];
        c = v[faces[i].v[2]];
        na = vn[faces[i].n[0]];
        nb = vn[faces[i].n[1]];
        nc = vn[faces[i].n[2]];

        mv.push_back(a.x());
        mv.push_back(a.y());
        mv.push_back(a.z());
        mv.push_back(b.x());
        mv.push_back(b.y());
        mv.push_back(b.z());
        mv.push_back(c.x());
        mv.push_back(c.y());
        mv.push_back(c.z());
        
        mn.push_back(na.x());
        mn.push_back(na.y());
        mn.push_back(na.z());
        mn.push_back(nb.x());
        mn.push_back(nb.y());
        mn.push_back(nb.z());
        mn.push_back(nc.x());
        mn.push_back(nc.y());
        mn.push_back(nc.z());
    }
    file.close();
    std::cout<< filename.toStdString() <<" id succeded!\t model_size:"<<mv.size()/3<<std::endl;
}

效果

 暂时还不会弄视频或者动态图,先看个静态效果吧

bunny

基于QT使用OpenGL,加载obj模型,进行鼠标交互_第2张图片

Cayman_GT

基于QT使用OpenGL,加载obj模型,进行鼠标交互_第3张图片

你可能感兴趣的:(QT/OpenGL,qt,计算机外设,交互)