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

news2024/11/24 4:08:10

目录

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

功能分析(需求分析)

  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、创建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 <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QMouseEvent>
#include <QTimer>
#include <QQuaternion>

#include <vector>
#include <QKeyEvent>

#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

在这里插入图片描述

Cayman_GT

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1191299.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【被面试官吊打系列】啥,你没说面试要考智力题呀 (上) ?

你好&#xff0c;我是安然无虞。 文章目录 1. 二进制问题分金条问题毒药问题 2. 先手必胜问题轮流拿石子抢30的必胜策略Nim游戏 3. 水桶问题5L和6L的水桶怎么量出3L的水&#xff1f;3L和5L的水桶怎么量出4L的水&#xff1f;一个装了10L水的桶&#xff0c;一个7L的空桶还有一个…

2.【自动驾驶与机器人中的SLAM技术】左乘模型推导ESKF

目录 1. 证明题 证明&#xff1a;若某个高斯随机变量为零均值&#xff0c;协方差为对角线矩阵且大小相同&#xff08;各向同性&#xff09;&#xff0c;那么在乘任意旋转矩阵以后&#xff0c;其均值仍为零&#xff0c;且协方差不变&#xff1b; 2. 代码实现运动方程将F矩阵…

FreeRTOS_内存管理

目录 1. 内存管理简介 2. 内存碎片 3. heap_1 内存分配方法 3.1 分配方法简介 4. heap_2 内存分配方法 4.1 分配方法简介 4.2 内存块详解 5. heap_4 内存分配方法 6. FreeRTOS 内存管理实验 6.1 实验程序 内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量…

计算机考研408有多难?25考研经验贴,开个好头很有必要

前言 大家好&#xff0c;我是陈橘又青&#xff0c;相信关注我的各位小伙伴们中&#xff0c;大多都是在计算机专业的大学生吧&#xff01; 每天都有许多人在后台私信我&#xff0c;问我要不要考研&#xff0c;我想说这个东西是因人而异的&#xff0c;像我本人就选择了就业&…

istio 学习笔记

参考&#xff1a;istio简介和基础组件原理&#xff08;服务网格Service Mesh&#xff09;-CSDN博客 Istio 微服务框架 服务治理。 Istio的关键功能: HTTP/1.1&#xff0c;HTTP/2&#xff0c;gRPC和TCP流量的自动区域感知负载平衡和故障切换。 通过丰富的路由规则&#xf…

96 前缀树Trie

前缀树 题解1 STL题解2 参考官方 Trie&#xff08;发音类似 “try”&#xff09;或者说 前缀树 是一种树形数据结构&#xff0c;用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景&#xff0c;例如自动补完和拼写检查。 请你实现 Trie 类&#xff1a; …

php冒泡算法实现倒序和正序排列

冒泡排序是一种简单的排序算法&#xff0c;其主要思想是比较相邻的两个元素&#xff0c;根据需要交换位置&#xff0c;将较大&#xff08;或较小&#xff09;的元素逐渐冒泡到数组的一端&#xff0c;从而实现排序。 1、从小到大排序 function bubbleSort($arr) {$len count(…

Antv/G2 图表背景实线改为虚线

坐标轴 - Axis 文档 绘图属性 - ShapeAttrs 文档 图表背景实线改为虚线代码示例&#xff1a; chart.axis("value", {grid: {// 背景网格刻度线样式line: {style: {lineWidth: 0.5,lineDash: [5, 2], //虚线},},}, });未设置前页面效果&#xff1a; 添加代码配置&…

MQTT协议消息代理服务公网远程连接

文章目录 前言1. Linux 搭建 Mosquitto2. Linux 安装Cpolar3. 创建MQTT服务公网连接地址4. 客户端远程连接MQTT服务5. 代码调用MQTT服务6. 固定连接TCP公网地址7. 固定地址连接测试 前言 Mosquitto是一个开源的消息代理&#xff0c;它实现了MQTT协议版本3.1和3.1.1。它可以在不…

linux os cpufreq 调频

amd ubuntu os 调频&#xff1a; sudo apt install linux-intel-iotg-5.15-tools-common # version 5.15.0-1043.49~20.04.1, or sudo apt install linux-oem-5.6-tools-common # version 5.6.0-1017.17 sudo apt install linux-tools-common …

城市内涝积水监测,万宾科技内涝预警监测系统

每一个城市的排水体系都是一个复杂的网络系统&#xff0c;需要多个部分配合协调&#xff0c;预防城市排水管网带来安全隐患&#xff0c;也因此才能在一定程度上缓解城市内涝带来的安全问题。在海绵城市建设过程中不仅要解决大部分道路硬化导致的积水无法渗透等问题&#xff0c;…

Java 设计模式——访问者模式

目录 1.概述2.结构3.案例实现3.1.抽象访问者类3.2.抽象元素类3.3.具体元素类3.4.具体访问者类3.5.对象结构类3.6.测试 4.优缺点5.使用场景6.扩展6.1.分派6.2.动态分配6.3.静态分配6.4.双分派 1.概述 访问者模式 (Visitor Pattern) 是一种行为型设计模式&#xff0c;它用于将数…

潼南柠檬产业发展大会举行 这三个场景“柠”聚了人气

华龙网讯&#xff08;首席记者 羊华&#xff09;今&#xff08;6&#xff09;日下午&#xff0c;2023中国潼南柠檬产业发展大会正式举行。潼南区准备了“大礼包”&#xff0c;既有专业论坛峰会&#xff0c;也首发了“柠檬产业大脑”&#xff0c;还进行了招商引资集中签约&#…

Linux安装git和maven——拉取代码 --> mvn打包成jar包 --->运行jar包

前言 我们知道最后的代码都是要运行在Linux系统中的&#xff0c;所以就需要在Linux中安装git&#xff0c;从而能够拉取代码&#xff0c;安装maven&#xff0c;从而能够进行项目的打包。 本篇博客以centos为例&#xff0c;介绍如何安装git&#xff0c;安装maven3.8&#xff0c…

【LLMs】从大语言模型到表征再到知识图谱

从大语言模型到表征再到知识图谱 InstructGLMLLM如何学习拓扑&#xff1f;构建InstructGLM泛化InstructGLM补充参考资料 2023年8月14日&#xff0c;张永峰等人的论文《Natural Language is All a Graph Needs》登上arXiv街头&#xff0c;轰动一时&#xff01;本论文概述了一个名…

界面组件DevExpress ASP.NET Core v23.1 - 进一步升级UI组件

DevExpress ASP.NET Core Controls使用强大的混合方法&#xff0c;结合现代企业Web开发工具所期望的所有功能。该套件通过ASP.NET Razor标记和服务器端ASP.NET Core Web API的生产力和简便性&#xff0c;提供客户端JavaScript的性能和灵活性。ThemeBuilder工具和集成的Material…

为什么推荐从Linux开始了解IT技术

IT是什么&#xff0c;是干什么的呢&#xff1f; 说起物联网&#xff0c;云计算&#xff0c;大数据&#xff0c;或许大家听过。但是&#xff0c;你知道&#xff0c;像云计算的底层基座是什么呢&#xff1f;就是我们现在说的Linux操作系统。而云计算就是跑在Linux操作系统上的一个…

管理驾驶舱这么做,领导都点赞(附方案下载)

你是否知道你的企业是否充分利用了可用的数据资源&#xff1f; 著名的著名的质量管理专家&#xff0c;威廉爱德华德莱克&#xff08;William Edwards Deming&#xff09;曾说过&#xff1a;"数据不是权力&#xff0c;能够理解数据的能力才是真正的权力。" 企业在经营…

Unity Mirror学习(一) SyncVars属性使用

官网中所说的网络对象&#xff0c;指的是挂了 NetworkIdentity组件的对象 官网中所说的玩家对象&#xff0c;指的是NetworkManager脚本上的PlayerPrefab预制体 这个概念对阅读官网文档很重要&#xff0c;我刚开始并不理解&#xff0c;走了歪路 SyncVars&#xff08;同步变量&a…

ssm+vue的论文管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的论文管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项目介绍&#xff1a; 采用M&am…