Qt OpenGL 3D 编程入门
Qt 提供了强大的 OpenGL 集成功能,使得在 Qt 应用中实现 3D 图形变得更加简单。以下是使用 Qt 进行 OpenGL 3D 编程的基础知识。
1. 环境配置
创建 Qt 项目
-
新建 Qt Widgets Application 项目
-
在
.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 和 EBOvao.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 corelayout(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 corein 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 和 EBOvao.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 corelayout(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 corein 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