在上章2.通过QOpenGLWidget绘制三角形,我们学习绘制三角形还是单色的,本章将为三角形每个顶点着色.
 
1.着色器描述
着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。如果你不知道什么是uniform也不用担心,我们后面会进行讲解。一个典型的着色器有下面的结构:
#version version_number

in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;        //type:变量类型,是一个可以包含有1、2、3或者4个分量的容器,可以定义为float(vecn)、bool(bvecn)等类型,在第2节讲述

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);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;  //打印上限

2.数据类型

和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix),其中矩阵我们会在之后的教程里再讨论。
2.1 向量Vector
GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):
类型
含义
vecn
包含n个float分量的默认向量
bvecn
包含n个bool分量的向量
ivecn
包含n个int分量的向量
uvecn
包含n个unsigned int分量的向量
dvecn
包含n个double分量的向量
大多数时候我们使用vecn,因为float足够满足大多数要求了。
一个向量的分量可以通过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向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect = vec2(0.5, 0.7);      //初始化vect,设置第一分量为0.5、第二分量为0.7
vec4 result = vec4(vect, 0.0, 0.0); //初始化result,设置XYZW为0.5,0.7,0.0 ,0.0
vec4 otherResult = vec4(result.xyz, 1.0);

3.输入与输出

之前我们渲染的三角形只能固定设置一种颜色,比如我们想通过应用程序中根据不同情况来发送我们想渲染的颜色,该怎么办?使用uniform变量
3.1 Uniform
Uniform是一种从CPU中的应用向GPU中的(vertex和fragment)着色器发送数据的方式,但uniform和顶点属性有些不同。
首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。它不能被shader程序修改.(shader只能用,不能改,只能等外部程序重新重置或更新),Uniform变量通过application调用函数glUniform()函数赋值的.
而glUniform()函数分为很多种,因为OpenGL由C语言编写,但是C语言不支持函数重载,所以会有很多名字相同后缀不同的函数,glUniform大概格式为 :
glUniform{1,2,3,4}{i,f,ub,ui,uiv,dv,v}

比如glUniform1i()、glUniform4ui等,其中i表示32位整形,f表示32位浮点型,ub表示8位无符号byte,ui表示32位无符号整形,v表示指针类型。

比如:
glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);   //表示设置location位置的uniform变量值的xyzw分量
3.2 通过uniform设置三角形颜色
接下来,我们在上章的三角形程序片元着色器中添加uniform变量,然后通过外部app来随着时间动态设置三角形颜色.
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数 }
片元着色器
#version 330 core
out vec4 FragColor; uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量 void main()
{
FragColor = ourColor;
}
我们在片元着色器中声明了一个uniform vec4的ourColor,并把片元着色器的输出颜色设置为uniform值的内容。
外部app程序
在外部app程序中设置颜色如下:
float timeValue = glfwGetTime();//获取运行的秒数
float greenValue = (sin(timeValue) / 2.0f) + 0.5f; //让颜色在0.0到1.0之间改变
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//查询uniform ourColor的位置值
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); //设置颜色
然后一个随着时间动态变化的三角形就诞生了
 
4.实现一个渐变三角形
如下图所示:
要实现这个三角形,需要3个颜色:左下(绿色),右下(红色),顶部(蓝色),这次我们同样打算把颜色数据加进顶点数据中,所以顶点数据变为:
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 // 顶部
};
由于现在有更多的数据要发送到顶点着色器,我们有必要去调整一下顶点着色器,使它能够接收颜色值作为一个顶点属性输入,所以在顶点着色器代码中定义了一个aColor
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; //位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; //颜色变量(发送给fragment shader)的属性位置值为 1 out vec3 ourColor; // 向片段着色器输出一个颜色 void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}
片元着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor; //vertex shader 传入的数据
void main()
{
FragColor = vec4(ourColor, 1.0);
}

具体代码如下所示:

#include "myglwidget.h"
#include <QtDebug> //GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字
#define GL_VERSION "#version 330 core\n" #define GLCHA(x) #@x //加单引号
#define GLSTR(x) #x //加双引号
#define GET_GLSTR(x) GL_VERSION#x const char *vsrc = GET_GLSTR( 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;
}
); const char *fsrc =GET_GLSTR(
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
); myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent)
{
} void myGlWidget::paintGL()
{
// 绘制
// glViewport(0, 0, width(), height()); glClear(GL_COLOR_BUFFER_BIT); // 渲染Shader
vao.bind(); //绑定激活vao
glDrawArrays(GL_TRIANGLES, 0, 3); //绘制3个定点,样式为三角形
vao.release(); //解绑
}

void myGlWidget::initializeGL()
{ // 为当前环境初始化OpenGL函数
initializeOpenGLFunctions(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //设置背景色为白色 //1.创建着色器程序
program = new QOpenGLShaderProgram;
program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc);
program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc);
program->link();
program->bind();//激活Program对象 //2.初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放
float vertices[] = {
// 位置 // 颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 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 // 顶部 颜色对应蓝色
}; vbo.create();
vbo.bind(); //绑定到当前的OpenGL上下文,
vbo.allocate(vertices, 18*sizeof(GLfloat));
vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); //设置为一次修改,多次使用 //3.初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等)
vao.create();
vao.bind(); // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0);
program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 6 * sizeof(float)); //设置aPos顶点属性
program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 6 * sizeof(float)); //设置aColor顶点颜色
//offset:第一个数据的偏移量
//tupleSize:一个数据有多少个元素,比如位置为xyz,颜色为rgb,所以是3
//stride:步长,下个数据距离当前数据的之间距离,比如右下位置和左下位置之间间隔了:3个xyz值+3个rgb值,所以填入 6 * sizeof(float) program->enableAttributeArray(0); //使能aPos顶点属性
program->enableAttributeArray(1); //使能aColor顶点颜色 //4.解绑所有对象
vao.release();
vbo.release(); }
void myGlWidget::resizeEvent(QResizeEvent *e)
{ }
 

3.QOpenGLWidget-通过着色器来渲染渐变三角形的更多相关文章

  1. D3D三层Texture纹理经像素着色器实现渲染YUV420P

    简单记录一下这两天用Texture实现渲染YUV420P的一些要点. 在视频播放的过程中,有的时候解码出来的数据是YUV420P的.表面(surface)通过设置参数是可以渲染YUV420P的,但Te ...

  2. WebGL着色器渲染小游戏实战

    项目起因 经过对 GLSL 的了解,以及 shadertoy 上各种项目的洗礼,现在开发简单交互图形应该不是一个怎么困难的问题了.下面开始来对一些已有业务逻辑的项目做GLSL渲染器替换开发. 起因是看 ...

  3. OpenGl中使用着色器的基本步骤及GLSL渲染简单示例

    OpenGL着色语言(OpenGL Shading Language,GLSL)是用来在OpenGL中着色编程的语言,是一种具有C/C++风格的高级过程语言,同样也以main函数开始,只不过执行过程是 ...

  4. Turing渲染着色器网格技术分析

    Turing渲染着色器网格技术分析 图灵体系结构通过使用 网格着色器 引入了一种新的可编程几何着色管道.新的着色器将计算编程模型引入到图形管道中,因为协同使用线程在芯片上直接生成紧凑网格( meshl ...

  5. 关于着色器LinearGradient的使用

    LinearGradient我们可以将之译为线型渐变.线型渲染等,译成什么不重要,重要的是它的显示效果是什么样子,今天我们就一起来看看. 先来看看LinearGradient的构造方法: /** Cr ...

  6. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader) 代码工 ...

  7. LearnOpenGL学习笔记(二)——着色器简单理解

    着色器在OpenGL中发挥着重要作用,它就像一个画笔,将输入的数据流,转为数学坐标,再将三维坐标变成二维坐标(针对我们现在用的二维显示器,全息显示器肯是三维的),再把二维坐标实际的像素点位置(这里面肯 ...

  8. Shader基础(固定管线着色器)

    在Shader的编码中,要养成不加空格的习惯,否则会有时候出现一些错误 固定管线着色器: 优点:实现简单 缺点:处理的效果比较差 //设置Shader的路径 Shader "MyFixedS ...

  9. 在pixi中使用你的自定义着色器

    通过几天的学习,对openGL.shader有了一个大致的了解. 回到学习的初衷吧,在基于pixi.js重构D3项目的时候,因为精灵层级的问题,我得按照一定的先后顺序将不同类别的精灵添加到场景中去. ...

随机推荐

  1. 吴恩达《深度学习》-第一门课 (Neural Networks and Deep Learning)-第三周:浅层神经网络(Shallow neural networks) -课程笔记

    第三周:浅层神经网络(Shallow neural networks) 3.1 神经网络概述(Neural Network Overview) 使用符号$ ^{[

  2. 将HBase中的表加载到hive中

    两种方式加载hbase中的表到hive中,一是hive创建外部表关联hbase表数据,二是hive创建普通表将hbase的数据加载到本地 1. 创建外部表 hbase中已经有了一个test表,内容如下 ...

  3. Flutter 使用image_gallery_saver保存图片

    Flutter  使用image_gallery_saver保存图片 其实我们开发项目app的时候, 你会发现有很多问题, 比如保存图片功能时 ,不仅导入包依赖包: image_gallery_sav ...

  4. zookeeper(4) 网络

    zookeeper底层通过NIO进行网络传输,如果对NIO不是很熟悉,先参见java NIO.下面我们来逐步实现基于NIO的zookeeper实现,首先我们要定义应用层的通信协议. 传输协议 请求协议 ...

  5. java虚拟机5 字节码

    java字节码本质是java程序的格式化表示,便于机器处理.所以他是java程序的另一种表示,java程序包含的信息他都包含并且更加结构化. java虚拟机字节码格式: magic 魔数,标识该文件是 ...

  6. 【原创】一层Nginx反向代理K8S化部署实践

    目录: 1)背景介绍 2)方案分析 3)实现细节 4)监控告警 5)日志收集 6)测试 一.背景介绍     如下图所示,传统方式部署一层Nginx,随着业务扩大,维护管理变得复杂,繁琐,耗时耗力和易 ...

  7. 记一次"截图"功能的项目调研过程!

    目录 项目需求 功能调研 AWT Swing Html2Image PhantomJS Headless Chrome 实现方案 结论 项目需求 最近,项目接到了一个新需求,要求对指定URL进行后端模 ...

  8. Java8 新特性lambda表达式(一)初始

    本篇参考Richard Warburton的 java8 Lambdas :Functional Programming for the Masses 学习lambda表达式之前,需要知道什么是函数式 ...

  9. PHP正则表达式核心技术完全详解 第2节

    作者:极客小俊 一个专注于web技术的80后 我不用拼过聪明人,我只需要拼过那些懒人 我就一定会超越大部分人! CSDN@极客小俊,原创文章, B站技术分享 B站视频 : Bilibili.com 个 ...

  10. 神作!阿里首发并发编程神仙笔记,Github已星标92K

    Qzone 微信 神作!阿里首发并发编程神仙笔记,Github已星标92K JAVA超神编程 2020-09-23 21:04:06 JVM 无论什么级别的Java从业者,JVM都是进阶时必须迈过的坎 ...