Qt OpenGL 3D 编程入门

Qt 提供了强大的 OpenGL 集成功能,使得在 Qt 应用中实现 3D 图形变得更加简单。以下是使用 Qt 进行 OpenGL 3D 编程的基础知识。

1. 环境配置

创建 Qt 项目

  1. 新建 Qt Widgets Application 项目

  2. .pro 文件中添加 OpenGL 模块:

qmake

复制代码
QT       += core gui opengl

基本 OpenGL 窗口类

Qt 提供了 QOpenGLWidget 作为 OpenGL 渲染的基础组件。

openglwidget.h

复制代码
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTime>

class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit OpenGLWidget(QWidget *parent = nullptr);
    ~OpenGLWidget();

protected:
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;

    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    QOpenGLVertexArrayObject vao;
    QOpenGLBuffer vbo{QOpenGLBuffer::VertexBuffer};
    QOpenGLBuffer ebo{QOpenGLBuffer::IndexBuffer};
    QOpenGLShaderProgram shaderProgram;
    QOpenGLTexture *texture;
    QTime time;
    QPoint lastMousePos;
    float xRot = 0.0f;
    float yRot = 0.0f;
    float zRot = 0.0f;
    float zoom = -5.0f;
};

#endif // OPENGLWIDGET_H

2. 基本框架实现

openglwidget.cpp

复制代码
#include "openglwidget.h"
#include <QDebug>
#include <QMouseEvent>
#include <QImage>

OpenGLWidget::OpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);
    setMouseTracking(true);
    time.start();
}

OpenGLWidget::~OpenGLWidget()
{
    makeCurrent();
    vao.destroy();
    vbo.destroy();
    ebo.destroy();
    delete texture;
    doneCurrent();
}

void OpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);

    // 立方体顶点数据 (位置 + 颜色 + 纹理坐标)
    float scale = 2.0f;
    float vertices[] = {
        // 前面
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  1.0f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  0.0f, 1.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  0.0f, 0.0f, 1.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 1.0f, 0.0f, 1.0f,  0.0f, 1.0f,

        // 后面
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  1.0f, 0.0f, 1.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale, -0.5f*scale,  0.0f, 1.0f, 1.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 1.0f, 1.0f, 1.0f,  0.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  0.5f, 0.5f, 0.5f, 1.0f,  1.0f, 1.0f,

        // 左面
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  0.0f, 1.0f,

        // 右面
        0.5f*scale, -0.5f*scale, -0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  0.0f, 1.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  1.0f, 1.0f,

        // 顶面
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  1.0f, 0.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  0.0f, 1.0f,

        // 底面
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale, -0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  0.0f, 1.0f
    };

    // 索引数据保持不变
    unsigned int indices[] = {
        0, 1, 2, 2, 3, 0,
        4, 5, 6, 6, 7, 4,
        8, 9, 10, 10, 11, 8,
        12, 13, 14, 14, 15, 12,
        16, 17, 18, 18, 19, 16,
        20, 21, 22, 22, 23, 20
    };

    // 初始化 VAO, VBO 和 EBO
    vao.create();
    vbo.create();
    ebo.create();

    vao.bind();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));
    ebo.bind();
    ebo.allocate(indices, sizeof(indices));

    // 顶点着色器
    const char *vertexShaderSource = R"(
        #version 330 core
        layout(location = 0) in vec3 aPos;
        layout(location = 1) in vec4 aColor;
        layout(location = 2) in vec2 aTexCoord;
        out vec4 ourColor;
        out vec2 TexCoord;
        uniform mat4 model;
        uniform mat4 view;
        uniform mat4 projection;
        void main()
        {
            gl_Position = projection * view * model * vec4(aPos, 1.0);
            ourColor = aColor;
            TexCoord = aTexCoord;
        }
    )";

    // 片段着色器
    const char *fragmentShaderSource = R"(
        #version 330 core
        in vec4 ourColor;
        in vec2 TexCoord;
        out vec4 FragColor;
        uniform sampler2D texture1;
        void main()
        {
            vec4 texColor = texture(texture1, TexCoord);
            FragColor = texColor * ourColor;
        }
    )";

    // 编译着色器
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    if (!shaderProgram.link()) {
        qDebug() << "Shader Program Link Error:" << shaderProgram.log();
        return;
    }

    shaderProgram.bind();

    // 设置顶点属性指针
    shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(0);
    shaderProgram.setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 4, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(1);
    shaderProgram.setAttributeBuffer(2, GL_FLOAT, 7 * sizeof(float), 2, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(2);

    // 加载纹理
    QImage img(":/textures/test.jpeg");
    if (img.isNull()) {
        qDebug() << "Failed to load texture image!";
        img = QImage(2, 2, QImage::Format_RGB32);
        img.fill(Qt::red);
    }

    texture = new QOpenGLTexture(img.mirrored());
    texture->setMinificationFilter(QOpenGLTexture::Linear);
    texture->setMagnificationFilter(QOpenGLTexture::Linear);
    texture->setWrapMode(QOpenGLTexture::Repeat);

    shaderProgram.setUniformValue("texture1", 0);

    vao.release();
    shaderProgram.release();
}

void OpenGLWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void OpenGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shaderProgram.bind();
    vao.bind();
    texture->bind(0);

    QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;

    model.rotate(xRot, 1.0f, 0.0f, 0.0f);
    model.rotate(yRot, 0.0f, 1.0f, 0.0f);
    model.rotate(zRot, 0.0f, 0.0f, 1.0f);
    view.translate(0.0f, 0.0f, zoom);
    projection.perspective(45.0f, width() / float(height()), 0.1f, 100.0f);

    shaderProgram.setUniformValue("model", model);
    shaderProgram.setUniformValue("view", view);
    shaderProgram.setUniformValue("projection", projection);

    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

    texture->release();
    vao.release();
    shaderProgram.release();

    zRot += 0.5f;
    if (zRot > 360.0f) zRot -= 360.0f;
    //update();
}

void OpenGLWidget::mousePressEvent(QMouseEvent *event)
{
    lastMousePos = event->pos();
}

void OpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        float dx = event->pos().x() - lastMousePos.x();
        float dy = event->pos().y() - lastMousePos.y();
        xRot += dy;
        yRot += dx;
        lastMousePos = event->pos();
        update();
    }
}

void OpenGLWidget::wheelEvent(QWheelEvent *event)
{
    float delta = event->angleDelta().y() / 120.0f;
    zoom += delta * 0.5f;
    zoom = qBound(-10.0f, zoom, -1.0f);
    update();
}

3. 渲染 3D 立方体

准备顶点数据

复制代码
  // 立方体顶点数据 (位置 + 颜色 + 纹理坐标)
    float scale = 2.0f;
    float vertices[] = {
        // 前面
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  1.0f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  0.0f, 1.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  0.0f, 0.0f, 1.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 1.0f, 0.0f, 1.0f,  0.0f, 1.0f,

        // 后面
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  1.0f, 0.0f, 1.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale, -0.5f*scale,  0.0f, 1.0f, 1.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 1.0f, 1.0f, 1.0f,  0.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  0.5f, 0.5f, 0.5f, 1.0f,  1.0f, 1.0f,

        // 左面
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  0.6f, 0.0f, 0.0f, 1.0f,  0.0f, 1.0f,

        // 右面
        0.5f*scale, -0.5f*scale, -0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  0.0f, 1.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.2f, 0.2f, 1.0f,  1.0f, 1.0f,

        // 顶面
        -0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  0.0f, 0.0f,
        0.5f*scale,  0.5f*scale,  0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  1.0f, 0.0f,
        0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale,  0.5f*scale, -0.5f*scale,  1.0f, 0.1f, 0.1f, 1.0f,  0.0f, 1.0f,

        // 底面
        -0.5f*scale, -0.5f*scale,  0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  0.0f, 0.0f,
        0.5f*scale, -0.5f*scale,  0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  1.0f, 0.0f,
        0.5f*scale, -0.5f*scale, -0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
        -0.5f*scale, -0.5f*scale, -0.5f*scale,  0.5f, 0.0f, 0.0f, 1.0f,  0.0f, 1.0f
    };

    // 索引数据保持不变
    unsigned int indices[] = {
        0, 1, 2, 2, 3, 0,
        4, 5, 6, 6, 7, 4,
        8, 9, 10, 10, 11, 8,
        12, 13, 14, 14, 15, 12,
        16, 17, 18, 18, 19, 16,
        20, 21, 22, 22, 23, 20
    };

初始化 VAO, VBO 和 EBO

复制代码
    // 初始化 VAO, VBO 和 EBO
    vao.create();
    vbo.create();
    ebo.create();

    vao.bind();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));
    ebo.bind();
    ebo.allocate(indices, sizeof(indices));

创建着色器程序

复制代码
 // 顶点着色器
    const char *vertexShaderSource = R"(
        #version 330 core
        layout(location = 0) in vec3 aPos;
        layout(location = 1) in vec4 aColor;
        layout(location = 2) in vec2 aTexCoord;
        out vec4 ourColor;
        out vec2 TexCoord;
        uniform mat4 model;
        uniform mat4 view;
        uniform mat4 projection;
        void main()
        {
            gl_Position = projection * view * model * vec4(aPos, 1.0);
            ourColor = aColor;
            TexCoord = aTexCoord;
        }
    )";

    // 编译着色器
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);

 // 片段着色器
    const char *fragmentShaderSource = R"(
        #version 330 core
        in vec4 ourColor;
        in vec2 TexCoord;
        out vec4 FragColor;
        uniform sampler2D texture1;
        void main()
        {
            vec4 texColor = texture(texture1, TexCoord);
            FragColor = texColor * ourColor;
        }
    )";

 shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);

4. 实现 3D 渲染

复制代码
void OpenGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shaderProgram.bind();
    vao.bind();
    texture->bind(0);

    QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;

    model.rotate(xRot, 1.0f, 0.0f, 0.0f);
    model.rotate(yRot, 0.0f, 1.0f, 0.0f);
    model.rotate(zRot, 0.0f, 0.0f, 1.0f);
    view.translate(0.0f, 0.0f, zoom);
    projection.perspective(45.0f, width() / float(height()), 0.1f, 100.0f);

    shaderProgram.setUniformValue("model", model);
    shaderProgram.setUniformValue("view", view);
    shaderProgram.setUniformValue("projection", projection);

    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

    texture->release();
    vao.release();
    shaderProgram.release();

    zRot += 0.5f;
    if (zRot > 360.0f) zRot -= 360.0f;
}

5. 添加纹理

加载纹理

复制代码
 // 加载纹理
    QImage img(":/textures/test.jpeg");
    if (img.isNull()) {
        qDebug() << "Failed to load texture image!";
        img = QImage(2, 2, QImage::Format_RGB32);
        img.fill(Qt::red);
    }

    texture = new QOpenGLTexture(img.mirrored());
    texture->setMinificationFilter(QOpenGLTexture::Linear);
    texture->setMagnificationFilter(QOpenGLTexture::Linear);
    texture->setWrapMode(QOpenGLTexture::Repeat);

    shaderProgram.setUniformValue("texture1", 0);

更新顶点属性指针

复制代码
    // 设置顶点属性指针
    shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(0);
    shaderProgram.setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 4, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(1);
    shaderProgram.setAttributeBuffer(2, GL_FLOAT, 7 * sizeof(float), 2, 9 * sizeof(float));
    shaderProgram.enableAttributeArray(2);

完整工程代码:

https://gitee.com/byxdaz/opengl3d-instance

运行效果图:

相关推荐
小小码农Come on2 分钟前
Qt::WA_StyledBackground属性的作用
开发语言·qt
秋田君6 分钟前
2026 前端新出路:掌握 C++ 核心语法,无缝衔接 QT 桌面开发
前端·c++·qt
走好每一步1 小时前
0、VDK编译 CMake + VS2017 x64
qt·vtk·三维图像
cjzcjl2 小时前
在Android中使用opengl 片元shader实现在录像过程对人脸进行识别并自动打马赛
opengl·马赛克·人脸·人脸追踪·执法仪
雪的季节3 小时前
Qt多窗口架构设计需求简介
开发语言·qt
jingshaoqi_ccc13 小时前
windows 10系统下QT的安装及在Visual studio中的扩展安装
windows·qt·visual studio
Starry-sky(jing)15 小时前
# Linux 下 Qt 应用无障碍自动化:记一次wx无人值守系统的架构演进
linux·qt·自动化
必须得开心呀20 小时前
QT 5.11.1使用QMetaObject::invokeMethod调用函数失败,提示提示 “no such method ***”
qt
誰能久伴不乏1 天前
工业级 Modbus 上位机架构:基于滴答引擎与状态锁的高并发调度器
c++·qt·架构
代钦塔拉1 天前
Qt信号槽参数类型全解:原生类型、结构体、enum class强枚举注册与传参实战
开发语言·qt