【QOpenGL】更多属性

一、概述

在顶点着色器中传入顶点的位置数据和颜色数据,生成一个彩色的矩形,效果如下:
在这里插入图片描述

二、前置知识

在 OpenGL 里,当你给顶点着色器的每个顶点赋予不同颜色,并且将这些颜色输出到片段着色器时,最终渲染出彩色的结果,这是因为 OpenGL 采用了插值算法。下面为你详细解释其原理:

1. 顶点着色器

顶点着色器会对每个顶点进行处理。在这个过程中,你可以给每个顶点指定不同的颜色。例如:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 ourColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
}

在上述代码里,aColor 是输入的顶点颜色,ourColor 则是输出到片段着色器的颜色。

2. 光栅化

在顶点着色器处理完所有顶点之后,OpenGL 会对这些顶点构成的图元(像三角形、线段等)进行光栅化操作。光栅化的作用是把图元转换为一个个片段(可以理解成屏幕上的像素)。

3. 插值

在光栅化阶段,OpenGL 会对顶点属性(包含颜色)进行插值。以三角形为例,当你为三角形的三个顶点分别指定不同颜色时,在三角形内部的每个片段的颜色,会依据其在三角形中的位置,对三个顶点的颜色进行线性插值计算得出。

4. 片段着色器

经过插值得到的颜色会被传递到片段着色器中。片段着色器会对每个片段进行处理,并且使用插值后的颜色来确定该片段最终的颜色。例如:

#version 330 core
in vec3 ourColor;
out vec4 FragColor;

void main()
{
    FragColor = vec4(ourColor, 1.0);
}

在上述代码中,ourColor 是经过插值后的颜色,FragColor 则是该片段最终的颜色。

5. 渲染结果

由于每个片段的颜色是通过对顶点颜色进行插值得到的,所以在图元内部,颜色会从一个顶点的颜色平滑过渡到另一个顶点的颜色,最终呈现出彩色的效果。

三、实现流程

实现GLSL源码

  • 顶点着色器

需要实现两个layout,其中一个接受位置坐标,另一个接受颜色坐标

#version 330 core
layout (location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1

out vec3 ourColor; // 向片段着色器输出一个颜色

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}

  • 片段着色器

片段着色器只需要接收来自顶点着色器的ourColor变量,直接作为输出变量即可:

#version 330 core
out vec4 FragColor;
in vec3 ourColor;

void main()
{
    FragColor = vec4(ourColor, 1.0);
}

添加顶点数据

添加顶点数据,分别是坐标数据和颜色数据:

// 顶点数据
float vertices[] = {
     0.5f,  0.5f, 0.0f,    1.0f, 0.0f, 0.0f,   // 右上角 0
     0.5f, -0.5f, 0.0f,    0.0f, 1.0f, 0.0f,  // 右下角 1
    -0.5f, -0.5f, 0.0f,    0.0f, 0.0f, 1.0f, // 左下角 2
    -0.5f,  0.5f, 0.0f,    0.5f, 0.5f, 0.5f   // 左上角 3
};

传入顶点数据

  • 位置数据,和之前一样,每个位置3个数据,每个顶点6个数据,没有偏移量
this->shader_program_.bind();  // 如果使用 QShaderProgram,那么最好在获取顶点属性位置前,先 bind()
GLint aPosLocation = this->shader_program_.attributeLocation("aPos");  // 获取顶点着色器中顶点属性 aPos 的位置
glVertexAttribPointer(aPosLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);  // 手动传入第几个属性
glEnableVertexAttribArray(aPosLocation);
  • 颜色数据,存在3个float单位的偏移量
this->shader_program_.bind();
GLint aColorLocation = this->shader_program_.attributeLocation("aColor");
glVertexAttribPointer(aColorLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(aColorLocation);

整体代码

foxopenglwidget.h

#ifndef FOXOPENGLWIDGET_H
#define FOXOPENGLWIDGET_H

#include <QOpenGLWidget>  // 相当于GLFW
#include <QOpenGLFunctions_4_5_Core>  // 相当于 GLAD
#include <QOpenGLShaderProgram>
#include <QTimer>

class FoxOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    enum Shape
    {
        None,
        Rect,
        Circle,
        Triangle
    };

    explicit FoxOpenGLWidget(QWidget *parent = nullptr);
    ~FoxOpenGLWidget();

    void drawShape(Shape shape);
    void setWirefame(bool wirefame);


protected:
    /* 需要重载的 QOpenGLWidget 中的三个函数 */
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

signals:

public slots:
    void changeColorWithTime();

private:
    Shape current_shape_;  // 记录当前绘制的图形
    QOpenGLShaderProgram shader_program_;  // 【重点】使用 Qt 提供的对象进行编译和链接
    QTimer timer_;
};

#endif // FOXOPENGLWIDGET_H

foxopenglwidget.cpp

#include <QDebug>
#include <QTime>
#include "foxopenglwidget.h"

// 顶点数据
float vertices[] = {
     0.5f,  0.5f, 0.0f,    1.0f, 0.0f, 0.0f,   // 右上角 0
     0.5f, -0.5f, 0.0f,    0.0f, 1.0f, 0.0f,  // 右下角 1
    -0.5f, -0.5f, 0.0f,    0.0f, 0.0f, 1.0f, // 左下角 2
    -0.5f,  0.5f, 0.0f,    0.5f, 0.5f, 0.5f   // 左上角 3
};

unsigned int indices[] = {
    // 注意索引从0开始!
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

// 创建 VAO 和 VBO 对象并且赋予 ID
unsigned int VBO, VAO;

// 创建 EBO 元素缓冲对象
unsigned int EBO;

// 顶点着色器的源代码,顶点着色器就是把 xyz 原封不动的送出去
const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";


// 片段着色器的源代码,片段着色器就是给一个固定的颜色
const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}\n\0";

FoxOpenGLWidget::FoxOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    this->current_shape_ = Shape::None;

    /* 每隔1ms取一次时间(发送一次信号) */
//    this->timer_.start(1);
//    connect(&this->timer_, SIGNAL(timeout()),
//            this, SLOT(changeColorWithTime()));

}

FoxOpenGLWidget::~FoxOpenGLWidget()
{
    if (!isValid()) return;  // 如果 paintGL 没有执行,下面的代码不存在(着色器 VAO VBO之类的),所以避免出错。如果他们没有执行就直接 return

    makeCurrent();

    /* 对象的回收 */
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);

    doneCurrent();
    update();
}


/* 首要要执行初始化过程,将函数指针指向显卡内的函数 */
void FoxOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();  // 【重点】初始化OpenGL函数,将 Qt 里的函数指针指向显卡的函数(头文件 QOpenGLFunctions_X_X_Core)


    // ===================== 顶点着色器 =====================
//    this->shader_program_.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);  // 通过字符串对象添加
    this->shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/ShaderSource/source.vert");  // 通过资源文件

    // ===================== 片段着色器 =====================
//    this->shader_program_.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    this->shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/ShaderSource/source.frag");

    // ===================== 链接着色器 =====================
    bool success = this->shader_program_.link();

    if (!success)
    {
        qDebug() << "ERROR: " << this->shader_program_.log();
    }


    // ===================== VAO | VBO =====================
    // VAO 和 VBO 对象赋予 ID
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    // 绑定 VAO、VBO 对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    /* 为当前绑定到 target 的缓冲区对象创建一个新的数据存储(在 GPU 上创建对应的存储区域,并将内存中的数据发送过去)
        如果 data 不是 NULL,则使用来自此指针的数据初始化数据存储
        void glBufferData(GLenum target,  // 需要在 GPU 上创建的目标
                                            GLsizeipter size,  // 创建的显存大小
                                            const GLvoid* data,  // 数据
                                            GLenum usage)  // 创建在 GPU 上的哪一片区域(显存上的每个区域的性能是不一样的)https://registry.khronos.org/OpenGL-Refpages/es3.0/
    */
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

#if 1
    /* 告知显卡如何解析缓冲区里面的属性值
        void glVertexAttribPointer(
                                    GLuint index,  // VAO 中的第几个属性(VAO 属性的索引)
                                    GLint size,  // VAO 中的第几个属性中对应的位置放几份数据
                                    GLEnum type,  // 存放数据的数据类型
                                    GLboolean normalized,  // 是否标准化
                                    GLsizei stride,  // 步长
                                    const void* offset  // 偏移量
        )
    */
    this->shader_program_.bind();  // 如果使用 QShaderProgram,那么最好在获取顶点属性位置前,先 bind()
    GLint aPosLocation = this->shader_program_.attributeLocation("aPos");  // 获取顶点着色器中顶点属性 aPos 的位置
    glVertexAttribPointer(aPosLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);  // 手动传入第几个属性
    glEnableVertexAttribArray(aPosLocation); // 开始 VAO 管理的第一个属性值

    this->shader_program_.bind();
    GLint aColorLocation = this->shader_program_.attributeLocation("aColor");
    glVertexAttribPointer(aColorLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(aColorLocation);
#endif

#if 0
    /* 当我们在顶点着色器中没有写 layout 时,也可以在此处代码根据名字手动指定某个顶点属性的位置 */
    this->shader_program_.bind();
    GLint aPosLocation = 2;
    this->shader_program_.bindAttributeLocation("aPos", aPosLocation);
    glVertexAttribPointer(aPosLocation, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(aPosLocation);

#endif


    // ===================== EBO =====================
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);  // EBO/IBO 是储存顶点【索引】的


    // 解绑 VAO 和 VBO,注意先解绑 VAO再解绑EBO
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);  // 注意 VAO 不参与管理 VBO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);


    /* 用线条填充,默认是 GL_FILL */
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

void FoxOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

void FoxOpenGLWidget::paintGL()
{
    /* 设置 OpenGLWidget 控件背景颜色为深青色 */
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);  // set方法【重点】如果没有 initializeGL,目前是一个空指针状态,没有指向显卡里面的函数,会报错
    glClear(GL_COLOR_BUFFER_BIT);  // use方法

    /* 重新绑定 VAO */
    glBindVertexArray(VAO);

    /* 【重点】使用 QOpenGLShaderProgram 进行着色器绑定 */
    this->shader_program_.bind();

    /* 绘制三角形 */
//    glDrawArrays(GL_TRIANGLES, 0, 6);
//    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);  // 6 代表6个点,因为一个矩形是2个三角形构成的,一个三角形有3个点
//    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, &indices);  // 直接到索引数组里去绘制,如果VAO没有绑定EBO的话

    // 通过 this->current_shape_ 确定当前需要绘制的图形
    switch (this->current_shape_)
    {
    case Shape::None:
        break;

    case Shape::Rect:
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        break;

    case Shape::Circle:
        break;

    case Shape::Triangle:
        break;

    default:
        break;

    }
}

void FoxOpenGLWidget::drawShape(FoxOpenGLWidget::Shape shape)
{
    this->current_shape_ = shape;
    update();  // 【重点】注意使用 update() 进行重绘,也就是这条语句会重新调用 paintGL()
}

void FoxOpenGLWidget::setWirefame(bool wirefame)
{
    makeCurrent();

    if (true == wirefame)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    else
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    doneCurrent();
    update();  // 【重点】注意使用 update() 进行重绘,也就是这条语句会重新调用 paintGL()
}

void FoxOpenGLWidget::changeColorWithTime()
{
    if (this->current_shape_ == Shape::None) return;

    makeCurrent();

    int current_sec = QTime::currentTime().second();  // 取到秒
    float greenValue = sin(current_sec);
    this->shader_program_.setUniformValue("ourColor", 0.0f, greenValue, 0.0f, 1.0f);

    doneCurrent();
    update();
}

更多资料:https://github.com/0voice

《餐馆点餐管理系统——基于Java和MySQL的课程设计解析》 在信息技术日益发达的今天,餐饮行业的数字化管理已经成为一种趋势。本次课程设计的主题是“餐馆点餐管理系统”,它结合了编程语言Java和数据库管理系统MySQL,旨在帮助初学者理解如何构建一个实际的、具有基本功能的餐饮管理软件。下面,我们将深入探讨这个系统的实现细节及其所涉及的关键知识点。 我们要关注的是数据库设计。在“res_db.sql”文件中,我们可以看到数据库的结构,可能包括菜品表、订单表、顾客信息表等。在MySQL中,我们需要创建这些表格并定义相应的字段,如菜品ID、名称、价格、库存等。此外,还要设置主键、外键来保证数据的一致性和完整性。例如,菜品ID作为主键,确保每个菜品的唯一性;订单表中的顾客ID和菜品ID则作为外键,与顾客信息表和菜品表关联,形成数据间的联系。 接下来,我们来看Java部分。在这个系统中,Java主要负责前端界面的展示和后端逻辑的处理。使用Java Swing或JavaFX库可以创建用户友好的图形用户界面(GUI),让顾客能够方便地浏览菜单、下单。同时,Java还负责与MySQL数据库进行交互,通过JDBC(Java Database Connectivity)API实现数据的增删查改操作。在程序中,我们需要编写SQL语句,比如INSERT用于添加新的菜品信息,SELECT用于查询所有菜品,UPDATE用于更新菜品的价格,DELETE用于删除不再提供的菜品。 在系统设计中,我们还需要考虑一些关键功能的实现。例如,“新增菜品和价格”的功能,需要用户输入菜品信息,然后通过Java程序将这些信息存储到数据库中。在显示所有菜品的功能上,程序需要从数据库获取所有菜品数据,然后在界面上动态生成列表或者表格展示。同时,为了提高用户体验,可能还需要实现搜索和排序功能,允许用户根据菜品名称或价格进行筛选。 另外,安全性也是系统设计的重要一环。在连接数据库时,要避免SQL注入攻击,可以通过预编译的PreparedStatement对象来执行SQL命令。对于用户输入的数据,需要进行验证和过滤,防止非法字符和异常值。 这个“餐馆点餐管理系统”项目涵盖了Java编程、数据库设计与管理、用户界面设计等多个方面,是一个很好的学习实践平台。通过这个项目,初学者不仅可以提升编程技能,还能对数据库管理和软件工程有更深入的理解。在实际开发过程中,还会遇到调试、测试、优化等挑战,这些都是成长为专业开发者不可或缺的经验积累
<think>好的,用户想在QOpenGL中使用多线程绘制点云图,我需要根据提供的引用和系统指令来生成回答。首先,我需要回忆QOpenGL多线程编程的基本知识,然后结合点云图的绘制方法。 用户提供的引用中提到了Python、Matlab和C#的点云绘制方法,但问题涉及的是QOpenGL和C++。因此,我需要将这些概念转换到Qt框架中。例如,引用[2]和[4]提到使用Matplotlib的3D散点图,而在QOpenGL中可能需要使用顶点缓冲对象(VBO)来高效渲染大量点。 多线程在OpenGL中的使用需要注意上下文管理,因为OpenGL上下文是线程相关的。通常的做法是在主线程创建上下文,在辅助线程处理数据加载和更新,然后主线程负责渲染。引用[5]提到C#的云图算法,可能涉及到数据处理与渲染分离,这同样适用于QOpenGL的多线程策略。 接下来,我需要整理实现步骤。首先,分离数据加载和渲染线程,使用Qt的线程类如QThread。然后,确保OpenGL操作在正确的线程中,比如在主线程初始化OpenGL资源和渲染,而数据生成在另一个线程。需要使用互斥锁保护共享资源,如点云数据。此外,双缓冲技术可以减少渲染时的数据冲突。 还需要考虑如何高效绘制大量点,使用VBO或顶点数组对象(VAO)来提升性能。引用[2]中的Python代码使用了NumPy生成数据,类似地,在C++中可以使用Qt的容器或数组来存储点数据,并传递给VBO。 可能的问题包括线程间信号槽的通信、上下文共享、资源同步。需要确保辅助线程完成数据处理后,通过信号通知主线程更新VBO。同时,使用Qt的OpenGL相关类如QOpenGLBuffer、QOpenGLVertexArrayObject来管理资源。 最后,验证步骤和示例代码结构,确保用户能够理解如何整合多线程和OpenGL绘制。可能的相关问题会涉及性能优化、数据同步或特定错误处理,需要提前预判并准备回答。</think>### QOpenGL多线程绘制点云图实现方法 在Qt框架中实现多线程点云绘制,需要结合QOpenGL的多线程特性和Qt的线程管理机制。以下是关键实现步骤: #### 1. 上下文共享配置 - 主线程创建QOpenGLContext后,通过`setShareContext()`建立共享上下文 - 辅助线程需继承QOpenGLContext并调用`setShareContext(mainContext)` - 使用`moveToThread()`将辅助线程的OpenGL资源移动到渲染线程 ```cpp // 主线程创建共享上下文 QOpenGLContext* mainContext = new QOpenGLContext; mainContext->setFormat(requestedFormat); mainContext->create(); // 辅助线程初始化 workerContext = new QOpenGLContext; workerContext->setFormat(mainContext->format()); workerContext->setShareContext(mainContext); workerContext->create(); ``` #### 2. 数据线程与渲染线程分离 - **数据加载线程**:负责读取/生成点云数据(如XYZ文件解析) - **渲染线程**:专门处理OpenGL绘制命令 - 使用`QReadWriteLock`实现线程安全的数据访问 ```cpp // 数据容器定义 struct PointCloud { QVector<QVector3D> vertices; QReadWriteLock lock; }; // 数据更新示例 void DataThread::updatePoints() { QWriteLocker locker(&cloud.lock); cloud.vertices.append(newPoints); } ``` #### 3. 多缓冲技术实现 - 采用双缓冲机制避免资源竞争 - 使用`QOpenGLBuffer`创建两个顶点缓冲对象(VBO) - 通过原子标志位控制缓冲切换 ```cpp // 双VBO定义 QOpenGLBuffer vbo[2]; int activeBuffer = 0; // 渲染循环中 glBindBuffer(GL_ARRAY_BUFFER, vbo[activeBuffer]); // 执行绘制命令... activeBuffer = 1 - activeBuffer; // 切换缓冲 ``` #### 4. 点云渲染优化 - 使用实例化渲染技术提升性能 - 结合几何着色器实现LOD(细节层次)控制 - 采用SSBO(Shader Storage Buffer)处理超大规模数据 ```glsl // 顶点着色器示例 #version 450 core layout(location = 0) in vec3 vertexPosition; uniform mat4 mvpMatrix; void main() { gl_Position = mvpMatrix * vec4(vertexPosition, 1.0); gl_PointSize = 2.0; // 控制点大小 } ``` #### 5. 实现步骤 1. 创建共享的OpenGL上下文 2. 启动数据加载线程读取点云 3. 在主线程初始化OpenGL资源和着色器 4. 建立线程间通信机制(信号/槽) 5. 实现双缓冲数据更新 6. 在paintGL()中执行同步渲染 #### 完整示例框架 ```cpp class GLWidget : public QOpenGLWidget { Q_OBJECT public: PointCloud cloud; QOpenGLBuffer vbo[2]; QThread dataThread; protected: void initializeGL() { initializeOpenGLFunctions(); // 初始化VBO/VAO } void paintGL() { QReadLocker locker(&cloud.lock); // 绑定当前VBO执行绘制 glDrawArrays(GL_POINTS, 0, cloud.vertices.size()); } }; // 数据线程类 class DataLoader : public QObject { Q_OBJECT public slots: void loadData() { while(!stopFlag) { // 生成/加载新数据 QWriteLocker locker(&widget->cloud.lock); widget->cloud.vertices.update(newData); emit dataUpdated(); } } signals: void dataUpdated(); }; ``` #### 注意事项 1. OpenGL上下文操作必须在其所属线程执行 2. 资源销毁需通过`deleteLater()`跨线程处理 3. 使用`QSurfaceFormat::setSwapInterval(0)`关闭垂直同步提升帧率 4. 推荐使用`QOpenGLTimerQuery`进行性能分析 ### 验证方法 1. 通过`glGetError()`检查OpenGL状态 2. 使用`QElapsedTimer`测量帧时间 3. 逐步增加点云规模验证内存管理 4. 检查线程亲和性`QThread::currentThreadId()` ### 性能优化建议 - 采用GPU计算着色器进行数据预处理 - 实现基于八叉树的空间分割 - 使用异步像素缓冲对象(PBO) - 开启深度测试和多重采样抗锯齿
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值