【QOpenGL】uniform介绍

一、概述

使用uniform变量在全局改变颜色,实现一个随时间变化颜色的矩形。

在这里插入图片描述

二、前置知识

uniform 变量类型

在GLSL(OpenGL Shading Language)里,uniform 是一种特殊的变量类型,用于在CPU和GPU之间传递数据,并且在整个图元(如三角形)处理过程中保持不变。

  • uniform 变量是全局的、只读的变量,其值由应用程序(CPU端)在渲染之前设置,并且在整个图元的处理过程中保持不变。也就是说,在一次绘制调用中,uniform 变量的值对于所有顶点或者片段都是相同的。
  • 在GLSL着色器代码中uniform 变量需要使用 uniform 关键字进行声明
  • uniform变量不能被layout设置
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;  // 声明一个4x4的变换矩阵作为uniform变量
void main()
{
    gl_Position = model * vec4(aPos, 1.0);
}

在这个例子中,model 是一个 uniform 变量,代表一个4x4的变换矩阵。在顶点着色器中,它用于对顶点位置进行变换。

片段着色器
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;  // 声明一个四维向量作为uniform变量
void main()
{
    FragColor = ourColor;
}

在这个例子中,ourColor 是一个 uniform 变量,代表一个四维向量(RGBA颜色)。在片段着色器中,它用于设置片段的颜色。

三、实现过程

GLSL设置unifrom变量

为了简化,我们直接在片段着色器中加入一个uniform变量outColor作为输出渲染的颜色

#version 330 core
out vec4 FragColor;
in vec4 vertexColor;

uniform vec4 ourColor;

void main()
{
    FragColor = ourColor;
}

设置定时器和持续时间

使用QTimerQElapsedTimer分别设置响应周期以及获得持续时间

  • 设置计时器
connect(&this->timer_, SIGNAL(timeout()),
            this, SLOT(changeColorWithTime()));
  • 完善changeColorWithTime函数,获取当前持续时间

这里使用sin函数,因为它是连续且周期的函数,因此可以有连续变化的效果,由于属性的范围为[0,1],因此这里取一个绝对值:

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

    makeCurrent();

    qint64 elapsed = this->elapsedTimer.elapsed(); // 获取从启动计时器到现在经过的毫秒数
    float greenValue = std::fabs(sin(elapsed / 1000.0)); // 将毫秒转换为秒
    this->shader_program_.setUniformValue("ourColor", 0.0f, greenValue, 0.0f, 1.0f);

    doneCurrent();
    update();
}

API:

  • [[setUniformValue]]]

四、整体代码

foxopenglwidget.h

#ifndef FOXOPENGLWIDGET_H
#define FOXOPENGLWIDGET_H

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

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_;
    QElapsedTimer elapsedTimer;
};

#endif // FOXOPENGLWIDGET_H

foxopenglwidget.cpp

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

// 顶点数据
float vertices[] = {
    0.5f,  0.5f, 0.0f,   // 右上角 0
    0.5f, -0.5f, 0.0f,  // 右下角 1
    -0.5f, -0.5f, 0.0f, // 左下角 2
    -0.5f,  0.5f, 0.0f   // 左上角 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;

FoxOpenGLWidget::FoxOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    this->current_shape_ = Shape::None;
    this->elapsedTimer.start(); // 启动计时器

    /* 每隔1ms取一次时间(发送一次信号) */
    this->timer_.start(16); // 约 60 FPS
    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, 3 * sizeof(float), (void*)0);  // 手动传入第几个属性
    glEnableVertexAttribArray(aPosLocation); // 开始 VAO 管理的第一个属性值
#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();

    qint64 elapsed = this->elapsedTimer.elapsed(); // 获取从启动计时器到现在经过的毫秒数
    float greenValue = std::fabs(sin(elapsed / 1000.0)); // 将毫秒转换为秒
    this->shader_program_.setUniformValue("ourColor", 0.0f, greenValue, 0.0f, 1.0f);

    doneCurrent();
    update();
}

API

setUniformValue

void QOpenGLShaderProgram::setUniformValue(int location, GLfloat value)

Description

Sets the uniform variable at location in the current context to value.

更多资料: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编程、数据库设计与管理、用户界面设计等多个方面,是一个很好的学习实践平台。通过这个项目,初学者不仅可以提升编程技能,还能对数据库管理和软件工程有更深入的理解。在实际开发过程中,还会遇到调试、测试、优化等挑战,这些都是成长为专业开发者不可或缺的经验积累
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值