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_OBJECTpublic: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,并赋予IDglGenBuffers(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对象,并赋予IDglGenVertexArrays(1, &VAO);//绑定VAO对象glBindVertexArray(VAO);//告知显卡如何解析缓冲里的属性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//开启VAO管理的第一个属性值glEnableVertexAttribArray(0);//解绑VBOglBindBuffer(GL_ARRAY_BUFFER, 0);//解绑VAOglBindVertexArray(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); //绑定VAOglDrawArrays(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 corelayout (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 coreout 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 corelayout (location = 0) in vec3 aPos;void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

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

#version 330 coreout 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; //颜色变量的属性位置值为1out vec3 ourColor; //向片段着色器输出一个颜色void main()
{gl_Position = vec4(aPos, 1.0);ourColor = aColor; //将ourColor设置为我们从顶点数据那里得到的输入颜色
}

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

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

#version 330 coreout 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于vite+vue3引入tailwind框架的正确方式

首先可以明确的知道,只按照官网的配置是会导致样式不加载或者加载不生效等问题的。 正确的处理方案 1.首先按照官网的指示安装 npm install -D tailwindcss postcss autoprefixer npx tailwindcss init2.然后项目根目录创建postcss.config.js文件 module.exports {plugins: …

如何部署cobbler批量安装的服务端

cobbler批量安装 cobbler简介 Cobbler是一个Linux服务器安装的服务&#xff0c;可以通过网络启动(PXE)的方式来快速安装、重装物理服务器和虚拟机&#xff0c;同时还可以管理DHCP&#xff0c;DNS等。 Cobbler可以使用命令行方式管理&#xff0c;也提供了基于Web的界面管理工…

C++ 迭代器与反向迭代器

目录 一&#xff0c;什么是迭代器 1&#xff0c;定义 2&#xff0c;迭代器的设计思维 3&#xff0c;迭代器种类 二&#xff0c;迭代器与容器 1&#xff0c;容器中的迭代器 2&#xff0c;迭代器失效问题 三&#xff0c;迭代器的类型萃取&#xff08;traits&#xff09; …

Python函数、参数变量详细介绍

Python函数概述 在Python中&#xff0c;函数是组织好的、可重复使用的代码块&#xff0c;用于实现单一或相关联功能。函数能提高应用的模块性和代码的重复利用率。除了内建函数外&#xff0c;Python还支持用户自定义函数。 定义函数 自定义函数使用 def 关键字开始定义。函数…

sdwan本地组网分析

随着数字化转型的深入发展&#xff0c;企业对网络架构的要求也不断提高。SDWAN&#xff08;软件定义广域网&#xff09;作为一种创新的网络技术&#xff0c;正在逐渐受到企业的关注和采用。SDWAN本地组网技术可以帮助企业快速搭建高效稳定的企业网络架构&#xff0c;提升企业的…

Android kotlin全局悬浮窗全屏功能和锁屏页面全屏悬浮窗功能一

1.前言 在进行app应用开发中,在实现某些功能中要求实现悬浮窗功能,分为应用内悬浮窗 ,全局悬浮窗和 锁屏页面悬浮窗功能 等,接下来就来实现这些悬浮窗全屏功能,首选看下第一部分功能实现 2.kotlin全局悬浮窗全屏功能和锁屏页面全屏悬浮窗功能一分析 悬浮窗是属于Androi…

API接口开发lazada获得lazada商品详情API采集商品详情页实时数据、实时销量、库存等参数接入演示

要获取Lazada商品详情页的实时数据、实时销量和库存等参数&#xff0c;你需要使用Lazada的API接口。以下是一个简单的Python示例&#xff0c;展示了如何使用requests库调用Lazada API并解析返回的数据&#xff1a; # coding:utf-8 """ Compatible for python2.…

Linux:基础IO

回顾C文件接口 stdin & stdout & stderr C 默认会打开三个输入输出流&#xff0c;分别是 stdin, stdout, stderr 仔细观察发现&#xff0c;这三个流的类型都是 FILE*, fopen 返回值类型&#xff0c;文件指针 系统文件I/O 接口介绍 open man open #include <sy…

TSINGSEE青犀推出县域治理视频基座数字化、智慧化解决方案

一、方案背景 县域治理方案是我国地方治理体系的重要组成部分&#xff0c;对于促进县域经济社会发展、维护社会稳定、推进全面深化改革具有重要意义。随着科技的不断进步&#xff0c;视频监管已经成为了现代社会治理的重要手段之一。县域治理视频监管方案是通过视频监控、数据…

鸿蒙OS开发实例:【装饰器-@BuilderParam】

背景 这是一个基础概念&#xff0c;其实没有什么原因&#xff0c;练习过程中&#xff0c;自然可以感受到其用法&#xff0c;后期加上真实项目的演练&#xff0c;会形成习惯 功能核心理念 “在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法&#xff0c;将会…

电商系列之优惠券

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

python解决序列重叠问题

tblastn比对出来候选HSP区段&#xff0c;我们需要根据一定的基因长度范围来进行区域延伸去重叠&#xff0c;然后进行下一步操作。对HSP区域的延伸要考虑基因的长度以及目标基因组scafflod or chromosome长度&#xff0c;不是一件容易的事情。 这里采用了dataclass以及改写slot…

安全上网,防止上网被记录(v2ray实现加密通信)

近期听一位亲威说&#xff0c;她在公司休闲的时候上了哪个网站&#xff0c;浏览了过的网站IT部门的人都会知道&#xff0c;这是因为现在大多数网络设备&#xff0c;像路由与交换机都有记录访问网站地址记录功能&#xff0c;涉及还可以设置成记录到交互的内容。要想保密&#xf…

【QT学习笔记】目录 (不定时更新)

解析 Qt消息机制和事件 Qt消息机制和事件--2 qt::WA_QuitOnClose 类库及用法 QString::number用法_qstring::number表示整数 emit用法 QString用法 QFile 用法 QPair用法 | 如何定义一个函数返回两个值 QFileDialog用法&#xff08;选择文件弹出框&#xff09; QFileI…

js 分割号查找内容

如果您想要在JavaScript中使用分隔符查找字符串中的内容&#xff0c;您可以使用String.prototype.split方法来分割字符串&#xff0c;然后使用数组的相关方法来查找特定内容。 以下是一个简单的例子&#xff0c;它使用逗号作为分隔符&#xff0c;查找字符串数组中的特定内容&a…

java openCV4-专栏目录

专栏简介 &#x1f492;个人主页 &#x1f4d6;说明&#x1f4d6;本专栏为java openCV的入门专栏 openCV4.x 目录 &#x1f4e2;前言&#x1f4e2;场景&#x1f43c;附言&#x1f4d6;目录 &#x1f4e2;前言 本专栏所有示例采用openCV4.8.0版本&#xff0c;你也可以采用其它…

MySQL面试汇总(一)

MySQL 如何定位慢查询 如何优化慢查询 索引及其底层实现 索引是一个数据结构&#xff0c;可以帮助MySQL高效获取数据。 聚簇索引和非聚簇索引 覆盖索引 索引创建原则 联合索引

AcWing 92. 递归实现指数型枚举

Problem: AcWing 92. 递归实现指数型枚举 文章目录 思路解题方法复杂度Code 思路 这是一个经典的递归问题&#xff0c;我们需要实现指数型枚举。这意味着我们需要找出所有可能的组合。在这个问题中&#xff0c;我们需要找出1到n的所有可能的组合。 解题方法 我们使用一个递归函…

Linux系统-----------MySQL 数据类型

目录 MySQL 数据类型 一、数值类型 二、日期和时间类型 三、字符串类型 &#xff08;1&#xff09;CHAR类型 &#xff08;2&#xff09;VARCHAR类型 &#xff08;3&#xff09;CHAR和VARACHAR的比较及其应用场景 MySQL 数据类型 MySQL 中定义数据字段的类型对你数据库的…

代码随想录 Day-25

力扣题目 509.斐波那契数 思路 很理所当然的&#xff0c;可以使用递归的方式其次是用动态规划的方式&#xff0c;动态规划的核心就是递推公式。 那么递推和递归一字之差&#xff0c;有什么区别呢&#xff1f;&#xff08;递推和递归的区别&#xff09; 1、递归 class Solutio…