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 和 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