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


    功能分析(需求分析)

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

    技术点分析

    OpenGL

      OpenGL是基于C的,学习曲线比较抖,但是总的来说就是下面一幅图,
    在这里插入图片描述

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

    立即渲染模式

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

    可编程渲染管线模式

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    鼠标交互功能

      交互的功能实现是重载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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

      其中

    • 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    效果

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

    bunny

    在这里插入图片描述

    Cayman_GT

    在这里插入图片描述

  • 相关阅读:
    Day5(和为s的两个数字)双指针
    图论17-有向图的强联通分量-Kosaraju算法
    【Java】Servlet API
    python+SQL sever+thinter学生宿舍管理系统
    Python类和对象创建过程分析与元类以及魔法函数
    msvcp120.dll缺失的解决方法与作用介绍
    扩展的以太网
    geotools实现坐标系转换
    web移动开发之flex(弹性盒子---知识点篇)
    DBCO-PEG-Insulin|二苯并环辛炔-聚乙二醇-胰岛素|DBCO-PEG-胰岛素
  • 原文地址:https://blog.csdn.net/gb11235/article/details/134311115