在 Qt 中,OpenGL 主要用于 3D 渲染和图形处理。它允许开发者创建复杂的图形应用程序,特别是在需要渲染高效且高度可定制的 3D 图形时。Qt 提供了丰富的工具和类库来与 OpenGL 配合使用,以支持 3D 图形的渲染、交互和优化。在Qt+PCL的联合开发中,数据的可视化就是关键一环,其背后的使用的就是基于OpenGL来实现的,因此掌握基本的OpenGL相关知识是十分有必要的,下面我们就进行学习吧!
以下是关于 Qt 中 OpenGL 及 3D 渲染的基础知识。
一、OpenGL 概述
OpenGL (Open Graphics Library) 是一个跨平台的图形 API,用于渲染 2D 和 3D 图形,如下图。它被广泛应用于各种类型的图形应用程序中,如视频游戏、CAD 软件、虚拟现实应用等。OpenGL 提供了与硬件直接交互的接口,用于处理图形的渲染、变换和渲染优化。
在 Qt 中,可以通过 QOpenGLWidget
或者更底层的 QOpenGLFunctions
类来实现 OpenGL 渲染。
二、Qt 中 OpenGL 的实现
1. QOpenGLWidget
QOpenGLWidget
是 Qt 用于渲染 OpenGL 内容的标准窗口小部件。它提供了一个 OpenGL 渲染上下文,并且可以处理与 OpenGL 相关的事件,如初始化、绘制和更新。
QOpenGLWidget 使用基本步骤
- 创建一个继承自
QOpenGLWidget
的类,并重写必要的方法。 - 实现 OpenGL 的初始化、渲染和清除操作。
- 通过重写
paintGL()
和initializeGL()
方法来处理绘制和初始化。
代码示例:
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QPainter>
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
OpenGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {}
protected:
// OpenGL 初始化
void initializeGL() override {
initializeOpenGLFunctions(); // 初始化 OpenGL 函数
glClearColor(0.0, 0.0, 0.0, 1.0); // 设置背景颜色
}
// 绘制 OpenGL 内容
void paintGL() override {
glClear(GL_COLOR_BUFFER_BIT); // 清除屏幕
// 绘制简单的图形,例如三角形
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0); // 红色
glVertex2f(-0.6f, -0.6f);
glColor3f(0.0, 1.0, 0.0); // 绿色
glVertex2f(0.6f, -0.6f);
glColor3f(0.0, 0.0, 1.0); // 蓝色
glVertex2f(0.0f, 0.6f);
glEnd();
}
// 更新 OpenGL 视图
void resizeGL(int w, int h) override {
glViewport(0, 0, w, h); // 设置视口
glMatrixMode(GL_PROJECTION); // 切换到投影矩阵模式
glLoadIdentity(); // 重置投影矩阵
gluPerspective(45.0, (double)w / (double)h, 0.1, 100.0); // 设置透视投影
}
};
综合应用示例-绘制一个三角面片:
下面是一个使用 QOpenGLWidget
的最小可运行 Qt 示例,它展示了如何在 Qt 中使用 OpenGL 绘制一个彩色的三角形。
项目结构
QOpenGLWidgetExample/
├── main.cpp
├── mainwindow.h
├── mainwindow.cpp
├── glwidget.h
├── glwidget.cpp
mainOpenGL.cpp
#include "mainwindow.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets/QMainWindow>
class GLWidget;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
GLWidget *glWidget;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "glwidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) {
glWidget = new GLWidget(this);
setCentralWidget(glWidget);
resize(800, 600);
}
MainWindow::~MainWindow() {}
glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QtGui/QOpenGLFunctions>
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
GLWidget(QWidget *parent = nullptr);
~GLWidget();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
};
#endif // GLWIDGET_H
glwidget.cpp
#include "glwidget.h"
#include <QtGui/QOpenGLShaderProgram>
GLWidget::GLWidget(QWidget *parent)
: QOpenGLWidget(parent) {
}
GLWidget::~GLWidget() {}
void GLWidget::initializeGL() {
initializeOpenGLFunctions();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}
void GLWidget::resizeGL(int w, int h) {
glViewport(0, 0, w, h);
}
void GLWidget::paintGL() {
glClear(GL_COLOR_BUFFER_BIT);
// 使用原始 OpenGL(较旧方式)画一个三角形
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f( 0.0f, 0.5f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(-0.5f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f( 0.5f, -0.5f);
glEnd();
}
最终运行的结果如下:
2. QOpenGLFunctions
QOpenGLFunctions
是一个接口类,它提供了与 OpenGL 相关的功能,如加载 OpenGL 函数,设置视口,控制渲染过程等。QOpenGLWidget
继承自 QOpenGLFunctions
,因此可以在自己的 initializeGL()
、paintGL()
和 resizeGL()
等函数中直接调用这些 OpenGL 函数。
三、3D 渲染基础
在 3D 渲染中,OpenGL 主要用于处理几何体的变换、着色、纹理映射等操作。以下是一些重要的 3D 渲染基础概念:
1. 坐标系与变换
- 世界坐标系(World Coordinate System):全局坐标系,用来描述场景中的物体。
- 视图坐标系(View Coordinate System):由摄像机定义的坐标系,用来表示从摄像机的角度来看物体。
- 投影坐标系(Projection Coordinate System):通过投影变换将 3D 场景映射到 2D 屏幕。
OpenGL 通过矩阵变换来控制对象的位置、旋转和缩放等操作。这些变换通常使用以下几种矩阵:
- 模型变换(Model Transformation):将物体从局部坐标系转换到世界坐标系。
- 视图变换(View Transformation):将物体从世界坐标系转换到视图坐标系。
- 投影变换(Projection Transformation):将物体从视图坐标系转换到屏幕坐标系。
2. 着色器(Shaders)
着色器是 OpenGL 程序中的一个重要组成部分,通常用于处理顶点和像素(片段)的计算。OpenGL 中有两种主要类型的着色器:
- 顶点着色器(Vertex Shader):处理每个顶点的属性(如位置、颜色等),并将其传递到下一个渲染阶段。
- 片段着色器(Fragment Shader):处理每个像素的颜色,决定最终显示在屏幕上的颜色。
3. 纹理映射(Texture Mapping)
纹理映射是将图像(通常是 2D 图像)“贴”到 3D 对象表面的技术。这使得 3D 物体看起来更加复杂和真实。OpenGL 通过纹理坐标来映射纹理。
GLuint textureID;
glGenTextures(1, &textureID); // 生成纹理ID
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理
// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载纹理图像并生成纹理
4. 光照(Lighting)
光照模拟了物体表面与光源之间的相互作用,OpenGL 提供了多种光源类型和光照模型:
- 环境光(Ambient Light):无方向性的光源,均匀地照亮所有物体。
- 定向光(Directional Light):光源在一个特定方向上发出。
- 点光源(Point Light):从一点发出的光,照射范围有限。
- 聚光灯(Spotlight):点光源的特殊形式,光束在一个特定区域内发散。
光照计算通常会在片段着色器中进行,结合物体的材质属性来计算每个像素的颜色。
四、Qt 中的 3D 渲染
在 Qt 中,3D 渲染通常通过 QOpenGLWidget 与 OpenGL 结合来实现,具体步骤包括:
- 设置 OpenGL 环境:在
initializeGL()
中初始化 OpenGL 函数和状态。 - 渲染场景:在
paintGL()
中使用 OpenGL 函数渲染 3D 场景。 - 处理窗口调整:在
resizeGL()
中处理视口调整和投影设置。 - 交互处理:通过鼠标、键盘等事件进行 3D 视图的交互控制,如旋转、缩放等。
五、综合示例
下面是一个使用 现代 OpenGL + Qt (QOpenGLWidget
) 绘制一个 3D 球体(Sphere) 的综合示例,支持基本的鼠标旋转视角功能。该球体是通过代码生成顶点和三角形网格的方式构建的,不依赖外部模型文件。
示例功能概览
- 使用现代 OpenGL(VAO/VBO + Shader)
- 实时绘制 3D 球体
- 鼠标旋转视角
- 带 Phong 光照效果
项目结构简化
OpenGLSphere/
├── main.cpp
├── mainwindow.{h,cpp}
├── glwidget.{h,cpp}
├── shaders/
│ ├── vertex.glsl
│ └── fragment.glsl
├── sphere.h
顶点着色器(shaders/vertex.glsl)
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 mvp;
uniform mat4 model;
uniform mat3 normalMatrix;
out vec3 fragPos;
out vec3 fragNormal;
void main() {
fragPos = vec3(model * vec4(position, 1.0));
fragNormal = normalize(normalMatrix * normal);
gl_Position = mvp * vec4(position, 1.0);
}
片段着色器(shaders/fragment.glsl)
#version 330 core
in vec3 fragPos;
in vec3 fragNormal;
out vec4 FragColor;
uniform vec3 lightDir;
uniform vec3 viewPos;
uniform vec3 objectColor;
void main() {
vec3 norm = normalize(fragNormal);
vec3 lightColor = vec3(1.0);
// 环境光
vec3 ambient = 0.2 * lightColor;
// 漫反射
float diff = max(dot(norm, -lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// 镜面高光
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = spec * lightColor * 0.5;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
sphere.h — 球体网格生成
#ifndef SPHERE_H
#define SPHERE_H
#include <vector>
#include <cmath>
#define M_PI 3.14159265358979323846
// SphereMesh 类用于生成球体网格
class SphereMesh {
public:
std::vector<float> vertices;
std::vector<unsigned int> indices;
SphereMesh(int sectors = 36, int stacks = 18, float radius = 1.0f) {
generateMesh(sectors, stacks, radius);
}
private:
void generateMesh(int sectors, int stacks, float radius) {
for (int i = 0; i <= stacks; ++i) {
float stackAngle = M_PI / 2 - i * M_PI / stacks;
float xy = radius * cosf(stackAngle);
float z = radius * sinf(stackAngle);
for (int j = 0; j <= sectors; ++j) {
float sectorAngle = j * 2 * M_PI / sectors;
float x = xy * cosf(sectorAngle);
float y = xy * sinf(sectorAngle);
// Add vertex
vertices.push_back(x);
vertices.push_back(y);
vertices.push_back(z);
// 法线(单位化位置即为法线)
float lengthInv = 1.0f / radius;
vertices.push_back(x * lengthInv);
vertices.push_back(y * lengthInv);
vertices.push_back(z * lengthInv);
}
}
for (int i = 0; i < stacks; ++i) {
int k1 = i * (sectors + 1);
int k2 = k1 + sectors + 1;
for (int j = 0; j < sectors; ++j, ++k1, ++k2) {
if (i != 0) {
indices.push_back(k1);
indices.push_back(k2);
indices.push_back(k1 + 1);
}
if (i != (stacks - 1)) {
indices.push_back(k1 + 1);
indices.push_back(k2);
indices.push_back(k2 + 1);
}
}
}
}
};
#endif
glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QtGui/QOpenGLFunctions_3_3_Core>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QMatrix4x4>
#include <QtGui/QMouseEvent>
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core {
Q_OBJECT
public:
GLWidget(QWidget* parent = nullptr);
~GLWidget();
protected:
//OpenGL初始化
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
//鼠标事件
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private:
QOpenGLShaderProgram shader;
GLuint VAO, VBO, EBO;
QMatrix4x4 projection, view, model;
float yaw = 0.0f, pitch = 0.0f;
QPoint lastPos;
void updateViewMatrix();
};
#endif
glwidget.cpp
#include "glwidget.h"
#include "sphere.h"
GLWidget::GLWidget(QWidget* parent) : QOpenGLWidget(parent) {}
GLWidget::~GLWidget() {
makeCurrent();
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
doneCurrent();
}
void GLWidget::initializeGL() {
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
// Shader
//着色器
shader.addShaderFromSourceFile(QOpenGLShader::Vertex, "F:/Develop2/Qt+VTKTest/shaders/vertex.glsl");
shader.addShaderFromSourceFile(QOpenGLShader::Fragment, "F:/Develop2/Qt+VTKTest/shaders/fragment.glsl");
shader.link();
// Generate sphere
//生成球
SphereMesh sphere(40, 20, 1.0f);
const auto& verts = sphere.vertices;
const auto& inds = sphere.indices;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, inds.size() * sizeof(unsigned int), inds.data(), GL_STATIC_DRAW);
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
//glEnableVertexAttribArray(0);
//VAO 配置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); // 位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); // 法线
glEnableVertexAttribArray(1);
glBindVertexArray(0);
model.setToIdentity();
updateViewMatrix();
}
void GLWidget::resizeGL(int w, int h) {
projection.setToIdentity();
projection.perspective(45.0f, float(w) / h, 0.1f, 100.0f);
}
void GLWidget::paintGL() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.bind();
QMatrix4x4 mvp = projection * view * model;
shader.setUniformValue("mvp", mvp);
shader.setUniformValue("model", model);
shader.setUniformValue("normalMatrix", model.normalMatrix());
shader.setUniformValue("lightDir", QVector3D(0.5f, 1.0f, 0.3f).normalized());
shader.setUniformValue("viewPos", QVector3D(0.0f, 0.0f, 3.0f));
shader.setUniformValue("objectColor", QVector3D(0.2f, 0.6f, 0.9f));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 40 * 20 * 6, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
shader.release();
}
void GLWidget::mousePressEvent(QMouseEvent* event) {
lastPos = event->pos();
}
//鼠标按下事件
void GLWidget::mouseMoveEvent(QMouseEvent* event) {
float dx = event->x() - lastPos.x();
float dy = event->y() - lastPos.y();
lastPos = event->pos();
yaw += dx * 0.5f;
pitch += dy * 0.5f;
updateViewMatrix();
update();
}
//更新视图矩阵
void GLWidget::updateViewMatrix() {
view.setToIdentity();
view.translate(0, 0, -3);
view.rotate(pitch, 1, 0, 0);
view.rotate(yaw, 0, 1, 0);
}
其余组件
mainwindow.{h,cpp}
, main.cpp
前例保持一致,核心就是加载 GLWidget
作为主窗口中心控件。
效果展示(
将看到一个可交互旋转的 3D 球体,支持鼠标拖动视角旋转,着色为统一天蓝色,带 Phong 光照效果的 3D 球体,效果如下图。
六、总结
- OpenGL 在 Qt 中用于 3D 渲染,通过
QOpenGLWidget
来进行图形绘制,并利用QOpenGLFunctions
提供的 OpenGL 函数。 - 3D 渲染涉及多个概念,如坐标变换、着色器、纹理映射和光照等。
- Qt 与 OpenGL 配合能够创建复杂的图形和 3D 应用,特别适用于游戏开发、科学可视化、虚拟现实等应用场景。
通过掌握 Qt 和 OpenGL 的基本概念和工具,大家可以创建具有高效图形渲染的应用程序,为Qt+PCL联合开发打好基础。
至此完成第七讲的内容,欢迎喜欢朋友的关注订阅!