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

运行效果图:

相关推荐
小小龙学IT1 分钟前
Qt Graphics View Framework(图形视图框架)深度解析:从 Scene/View/Item 到工业级 2D 场景
开发语言·数据库·qt
程与留9 分钟前
09_Qt 布局管理系统——QHBoxLayout、QVBoxLayout、QGridLayout
c++·qt
神仙别闹12 分钟前
基于QT(C++)实现旅游线路规划系统
c++·qt·旅游
≮傷£≯√26 分钟前
音量调节弹窗 自定义组件qt
开发语言·qt
小小龙学IT13 小时前
Qt 6 跨平台开发完全指南:从信号槽机制到实际项目落地
开发语言·qt
丰锋ff16 小时前
基于 Qt 的智能门禁系统
qt·学习
我不是疯子是傻子1 天前
用 DeepSeek 将 Modbus 协议文档自动化为 Qt 枚举与寄存器结构体?「文档解析+AI生成+编译验证」的代码工厂实践
人工智能·qt·自动化
程与留1 天前
08_对话框系统全解析——模态非模态、QMessageBox、QFileDialog
c++·qt
qq_401700411 天前
Qt UDP 通信详解
qt·udp
程与留2 天前
07_QMainWindow 与应用程序框架——菜单栏、工具栏、状态栏
c++·qt