1.4.1 着色器

着色器(Shader)是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特定部分而运行,从基本意义上来说,着色器只是一种把输入转化为输出的程序。

一、着色器类QOpenGLShaderProgram

QOpenGLShaderProgram是Qt中对着色器类进行的封装。下面使用 QOpenGLShaderProgram替换掉顶点着色器、片段着色器和着色器程序shaderProgram相关代码。

在项目目录下新建两个文件分别命名为shapes.frag(存储片段着色器程序)和shapes.vert(存储顶点着色器程序)

给项目添加资源文件,右击项目名称,选择“添加新文件”

选择Qt->Qt Resource File,点击“Choose”

输入资源文件名,如:res,点击“下一步”

默认,点击“完成”

在资源管理界面,点击添加->添加前缀

输入前缀名称,如:/shaders

再点击添加->添加文件

选择刚刚创建的两个文件,点击“打开”

Ctrl+S,保存,两个文件被添加到项目文件树中

把顶点着色器代码拷贝到shapes.vert文件中,并进行更改

把片段着色器代码拷贝到shapes.frag文件中,并进行更改

更改myopenglwidget.h代码如下,包含QOpenGLShaderProgram头文件并创建一个QOpenGLShaderProgram类对象;新建一个析构函数,用于资源释放(对上一节代码的优化)

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>

class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
    
private:
    QOpenGLShaderProgram m_shaderProgram;
};

#endif // MYOPENGLWIDGET_H

更改myopenglwidget.cpp代码如下:

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

//全局的顶点缓冲对象和顶点数组对象
unsigned int VBO;
unsigned int VAO;

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{

}

MyOpenGLWidget::~MyOpenGLWidget()
{
    makeCurrent();
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    doneCurrent();
}

void MyOpenGLWidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //1.创建VBO,并赋予ID
    glGenBuffers(1, &VBO);
    //绑定VBO对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //顶点数据
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };
    //把顶点数据复制到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //2.创建VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    //绑定VAO对象
    glBindVertexArray(VAO);
    //告知显卡如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);
    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //解绑VAO
    glBindVertexArray(0);

    //3.创建一个程序对象
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = m_shaderProgram.link();
    if(!success)
        qDebug()<<"ERR:" << m_shaderProgram.log();
}

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

void MyOpenGLWidget::paintGL()
{
    //设置墨绿色背景
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //状态设置
    glClear(GL_COLOR_BUFFER_BIT); //状态使用

    //4.绘制三角形
    m_shaderProgram.bind(); //激活程序对象
    glBindVertexArray(VAO); //绑定VAO
    glDrawArrays(GL_TRIANGLES, 0, 3); //绘图
}

在上面的程序中,用如下几行代码替换掉了顶点着色器、片段着色器和着色器程序shaderProgram相关代码:

m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
bool success = m_shaderProgram.link();
if(!success)
    qDebug()<<"ERR:" << m_shaderProgram.log();

运行结果如下:

二、GLSL

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。

一个典型的着色器有下面的结构:

#version version_number
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
    //处理输入并进行一些图形操作
    ...
    //输出处理过的结果到输出变量
   out_variable_name = weird_stuff_we_processed;
}

每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件允许更多的顶点属性,可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:

int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
qDebug() << "Maximun nr of vertex attributes supported: " << nrAttributes;

通常情况下它至少会返回16个,大部分情况下是够用的。

三、数据类型

GLSL中包含的基础数据类型:int、float、double、uint和bool。GLSL有两个容器类型:向量(Vector)和矩阵(Matrix)。

GLSL中的向量是一个可以包含有2/3或4个分量的容器,分量的类型可以是前面默认基础类型的任意一个,它们可以是下面的形式(n代表分量的数量):

类型含义
vecn包含n个float分量的默认向量
bvecn包含n个bool分量的向量
ivecn包含n个int分量的向量
uvecn包含n个unsigned int分量的向量
dvecn包含n个double分量的向量

一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量,可以分别使用.x、.y、.z和.w来获取它们的第1/2/3/4个分量;GLSL也允许对颜色使用rgba,对纹理坐标使用stpq访问相同的分量。

向量这一数据类型允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许下面这样的语法:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

四、输入与输出(in-out)

每个着色器使用in和out这两个关键字设定输入和输出,只要一个输出变量与下一个着色器的输入匹配,它就会传递下去。

顶点着色器的特殊点是它的输入从顶点数据中直接接收。为了定义顶点数据该如何管理,使用location这一元数据指定输入变量,这样才可以在CPU上配置顶点属性。顶点着色器需要为它的输入提供一个额外的layout标识,这样才能把它链接到顶点数据:layout(location = 0);也可以忽略layout(location = 0)标识符,通过OpenGL代码中使用glGetAttribLocation查询属性位置值(Location)。

片段着色器的特殊点是需要一个vec4类型的颜色输出变量。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。

如果我们打算从一个着色器向另一个着色器发送数据,必须在发送方着色器中声明一个输出,在接收方着色器中声明一个输入,当类型和名字都一样时,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。

对之前的程序进行改动,让顶点着色器为片段着色器设置颜色。

更改shapes.vert文件中的代码如下:

#version 330 core

layout (location = 0) in vec3 aPos;

out vec4 vertexColor; //为片段着色器指定一个颜色

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); //暗红色
}

更改shapes.frag文件中的代码如下:

#version 330 core

out vec4 FragColor;

in vec4 vertexColor; //从顶点着色器传来的变量(类型和名称相同)

void main()
{
    FragColor = vertexColor;
}

在顶点着色器中声明了一个vec4类型变量vertexColor作为输出,并在片段着色器中声明一个vec4类型变量vertexColor作为输入,由于它们类型和名称相同,两个变量会被链接上,在顶点着色器中将颜色设置为深红色,最终会把深红色传给片段着色器,运行结果如下:

五、Uniform

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global),全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。其次,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

下面通过uniform来设置三角形的颜色:

删除掉shapes.vert文件中变量vertexColor相关代码如下:

#version 330 core

layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

在shapes.frag文件中定义uniform变量:

#version 330 core

out vec4 FragColor;

uniform vec4 ourColor; //从CPU中传值

void main()
{
    FragColor = ourColor;
}

我们在片段着色器中声明了一个uniform vec4的ourColor,并把片段着色器的输出颜色设置为uniform值的内容。因为uniform是全局变量,我们可以在任何着色器中定义他们,而无需通过顶点着色器作为中介。如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误。

下面给这个uniform添加数据,在void MyOpenGLWidget::initializeGL()函数中添加如下代码:

m_shaderProgram.bind();
m_shaderProgram.setUniformValue("vertexColor", 0.0, 1.0, 0.0, 1.0);

更新一个uniform之前你必须先激活着色器程序(调用m_shaderProgram.bind()),因为它是在当前激活的着色器程序中设置uniform值。使用setUniformValue函数更新uniform值(setUniformValue是重载函数,鼠标放到setUniformValue函数上,摁F2可以看到setUniformValue函数的所有重载形式)。

运行结果如下:

uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的工具,但是假如我们打算为每个顶点设置一个颜色的时候该怎么办呢?下面使用更多属性值来解决这个问题。

六、更多属性

之前顶点数据中只有位置数据,现在把颜色数据也加进去,把三角形的三个角分别指定为红色、绿色和蓝色:

float vertices[] = {
    // 位置              // 颜色
    0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下
   -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下
    0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部
};

由于现在有更多的数据要发送给顶点着色器,必须去调整一下顶点着色器,使它能够接收颜色值作为一个顶点属性输入。需要注意的是用layout标识符来把aColor属性的位置值设置为1:

更改shapes.vert文件中的代码如下:

#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设置为我们从顶点数据那里得到的输入颜色
}

由于我们不再使用uniform来传递片段的颜色,现在使用ourColor输出变量,我们必须再修改一下片段着色器:

更改shapes.frag文件中的代码如下:

#version 330 core

out vec4 FragColor;

in vec3 ourColor;

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

因为我们添加了另一个顶点属性,并且更新了VBO的内存,我们就必须重新配置顶点属性指针。更新后的VBO内存中的数据看起来像这样:

使用glVertexAttribPointer函数更新顶点格式:

//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); //开启VAO管理的第一个属性值

//颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); //开启VAO管理的第二个属性值

由于现在有两个顶点属性,需要重新计算步长值。为获得数据队列中下一个属性值我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值,这使得步长值为6乘以float的字节数。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0,颜色属性在后,偏移量是3*sizeof(float)。

运行结果如下:

我们只提供了3个颜色,结果却看到了大调色板,这是在片段着色器中进行所谓的片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Resterization)阶段通常会造成比原指定顶点更多的片段,光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置,基于这些位置,它会插值(Interpolation)所有片段着色器的输入变量,比如:我们有一个线段,上面端点是绿色,下面端点是蓝色,如果一个片段着色器在线段的70%的位置运行,它的颜色输出属性就会是一个绿色和蓝色的线性结合,更精确地说是70%绿+30%蓝。

注:观看OpenGL中文官网(https://learnopengl-cn.github.io/)和阿西拜的现代OpenGL入门(https://ke.qq.com/course/3999604#term_id=104150693)学习OpenGL

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/491574.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

thinkadmin 新版安装步骤

1.通过 Composer 安装: ( 推荐方式,默认只安装 admin 模块 ) ### 创建项目( 需要在英文目录下面执行 ) composer create-project zoujingli/thinkadmin### 进入项目根目录 cd thinkadmin### 数据库初始化并安装 ### 默认使用 Sqlite 数据库,若使用其他数据库请按第二步修…

I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

我们在前面学习 I2C 和 SPI 驱动的时候&#xff0c;针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码&#xff0c;但是这些本质上都是对寄存器的操作&#xff0c;所以为了方便内核开发人员统一访问 I2C/S…

当当狸智能激光雕刻机 多种材质自由雕刻,轻松打造独一无二的作品

提及“激光雕刻”&#xff0c;大多数人的印象一般都是&#xff1a;笨重巨大、价格昂贵、操作复杂、使用门槛较高、调试难度大...不是普通人能够随意操作的&#xff0c;让人望尘莫及。 而小米有品上新的这台「当当狸桌面智能激光雕刻机L1」&#xff0c;将超乎你的想象&#xff…

ABAP - 上传文件模板到SMW0,并从SMW0上下载模板

upload file template to SMW0 and download the template from it 首先上传文件到tcode SMW0 选择新建后,输入文件名和描述,再选择想要上传的文件 上传完成后: 在表WWWPARAMS, WWWDATA里就会有信息存进去 然后就可以程序里写代码了: 屏幕上的效果:

界面控件DevExpress WinForms/WPF v23.2 - 电子表格支持表单控件

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

yolov9报错:AttributeError: ‘list‘ object has no attribute ‘view‘的两种解决方法

1. 报错问题 In loss_tal.py: pred_distri, pred_scores torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split( (self.reg_max * 4, self.nc), 1) The error is as follows&#xff1a; AttributeError: list object has no attribute vie…

5.递归分治——1.递归与函数调用

程序运行 指令 指令的划分以函数为单位&#xff0c;调用函数本质上是使用call指令将pc指针移动到被调用的函数函数调用完成&#xff0c;需要使用return返回&#xff0c;本质是pc移回调用函数位置 数据 函数调用时&#xff0c;在堆栈区域要申请一片栈帧&#xff0c;函数的局…

linux用户管理命令2

useradd可以创建用户&#xff0c;其执行具体表现为在home文件夹下创建对应文件 那么有了useradd添加用户&#xff0c;自然有passwd添加用户密码 userdel可以删除用户&#xff0c;其中-r命令删除用户及其文件&#xff0c;-f命令可以强制删除用户&#xff0c;即使用户当前正在登录…

LeetCode 面试经典150题 205.同构字符串

题目&#xff1a; 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一个字…

【MATLAB源码-第16期】基于matlab的MSK定是同步仿真,采用gardner算法和锁相环。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 **锁相环&#xff08;PLL&#xff09;** 是一种控制系统&#xff0c;用于将一个参考信号的相位与一个输入信号的相位同步。它在许多领域中都有应用&#xff0c;如通信、无线电、音频、视频和计算机系统。锁相环通常由以下几个…

什么是公网IP?

公网IP&#xff0c;即公开网络IP地址&#xff0c;是指在互联网中公开可见、可访问的IP地址。每个设备在连接互联网时&#xff0c;都需要一个唯一的公网IP地址&#xff0c;以便其他设备可以定位并与之通信。 尽管公网IP在网络通信中具有重要作用&#xff0c;但它也带来了一些安全…

机器学习之聚类算法、随机森林

文章目录 随机森林决策树基础特征值问题&#xff1f; 聚类算法 随机森林 决策树 基础 概念&#xff1a;从根节点一步步走到叶子节点&#xff08;决策&#xff09;&#xff1b; 组成&#xff1a;根节点第一个选择的节点&#xff1b;叶子节点最终的决策结果&#xff1b;非叶子…

汽车电子行业知识:智能汽车电子架构

文章目录 3.智能汽车电子架构3.1.汽车电子概念及发展3.2.汽车电子架构类型3.2.1.博世汽车电子架构3.2.2.联合电子未来汽车电子架构3.2.3.安波福汽车电子架构3.2.4.丰田汽车电子架构3.2.5.华为汽车电子架构 3.智能汽车电子架构 3.1.汽车电子概念及发展 汽车电子是车体汽车电子…

LeetCode146:LRU缓存

leetCode&#xff1a;146. LRU 缓存 题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#x…

达梦数据库自动备份(全库)+还原(全库) 控制台

一 前提 1.安装达梦数据库DB8(请参照以前文章) 我的数据库安装目录是 /app/dmDB8 2.已创建实例 (请参照上一篇文章) 二 准备测试数据 三 自动备份步骤 1.开启归档模式 开启DM管理工具管理控制台 弹不出来工具的 输入命令 xhost 第一步 将服务器转换为配置状态 右键-&g…

基于SpringBoot和Vue的车辆管理系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的车辆管理系统的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&#x1f…

数据结构——链表(单链表)

大家好&#xff0c;又是我&#xff08;小锋&#xff09;&#xff0c;今天给大家带了一个比较有挑战的章节&#xff08;链表&#xff09;&#xff0c;但是不用担心&#xff0c;小锋会陪大家一起度过。 顺序表的思考与问题 1. 中间/头部的插入删除&#xff0c;时间复杂度为O(N) …

java-springboot实现图片的上传

我们在resources目录下创建image目录来存放上传的图片 service层懒的写&#xff0c;就都写controller层了。 RestController RequestMapping("/upload") public class upload {PostMapping("/pic")public String upLoad(RequestParam("multipartFile…

EPSON推出的实时时钟模块RX8130CE功耗低至300nA、从容应对各种使用场景

随着科技的进步和消费者需求的不断变化&#xff0c;笔记本电脑市场继续展现出强劲的发展势头一方面移动性和轻薄性成为主流&#xff0c;另外一方面性能在不断提升&#xff0c;功能也日益丰富。实时时钟模组&#xff0c;作为提供时间和定时功能的单元模块&#xff0c;是笔记本电…

esp单片机下arduino_gfx不相干显示驱动优化对flash空间的占用对比

一般情况下&#xff0c;很多esp32或者esp8266下的tft模块驱动都会包含很多种&#xff0c;而我们只需要其中一种&#xff0c;那就有个疑问这些被编译进的显示驱动到底占用了多少空间&#xff0c;是否需要把他优化掉&#xff1f; 这是默认的驱动列表&#xff1a; 84个文件&…