Qt+PCL手把手教材(第7讲)——Qt 中的 OpenGL 及 3D 渲染基础 (附绘制3D球体C++代码)

在 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 使用基本步骤
  1. 创建一个继承自 QOpenGLWidget 的类,并重写必要的方法。
  2. 实现 OpenGL 的初始化、渲染和清除操作
  3. 通过重写 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 结合来实现,具体步骤包括:

  1. 设置 OpenGL 环境:在 initializeGL() 中初始化 OpenGL 函数和状态。
  2. 渲染场景:在 paintGL() 中使用 OpenGL 函数渲染 3D 场景。
  3. 处理窗口调整:在 resizeGL() 中处理视口调整和投影设置。
  4. 交互处理:通过鼠标、键盘等事件进行 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联合开发打好基础。


至此完成第七讲的内容,欢迎喜欢朋友的关注订阅!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值