目录

高级顶点着色器

在顶点着色器中进行物理模拟

几何着色器

直通几何着色器

在应用程序中使用几何着色器

在几何着色器中丢弃几何图形

在几何着色器中修改几何图形

在几何着色器中生成几何图形

在几何着色器中改变图元类型

由几何着色器引入的新图元类型

高级片段着色器

片段着色器中的后期处理——颜色校正

片段着色器中的后期处理——卷积

在片段着色器中生成图像数据

在片段着色器中丢弃工作

逐片段控制深度

更高级的着色器函数

插值和存储限定符

高级内建函数

统一缓冲区对象

建立统一块


高级顶点着色器

使用GPU通过变换反馈进行数据再循环,从而实现物理模拟。顶点着色器除了对顶点进行空间转换之外,还能够在一个循环中将结果进行循环传递,并在每一次循环过程中进行迭代和更新。这些数据不必是位置,顶点着色器的结果也不必进行直接渲染。下面将讲解几个示例来说明。

在顶点着色器中进行物理模拟

建立一个弹簧和物块组成的网状物的物理模拟,每个顶点代表一个重物,与其他4个相邻的重物通过弹性绳进行连接。示例会对顶点进行反复迭代,用一个顶点着色器对每一个顶点进行处理。我们会使用大量高级特性,示例中使用了一个TBO纹理缓冲区对象来保存顶点位置数据,以及一个常规属性数组。同一个缓冲区将被绑定到这个TBO上,以及与输入到顶点着色器的位置相关联的VBO(顶点缓冲区对象)上。这样就允许我们对系统中其他顶点的当前位置随意进行访问了。我们还使用了一个整数顶点属性来保存相邻顶点的索引。此外,我们还使用变换反馈来在每次数学迭代之间存储每个物块的位置和速度。

对于每个顶点来说,我们需要一个位置、一个速度和一个质量值。将位置和质量包装到一个顶点数组中,而将速度包装到另一个数组中。在顶点数组是一个vec4,(x,y,z)是顶点位置,w是质量;速度数组是一个vec3数组。使用一个ivec4数组存储关于将重物连接到一起的弹簧的信息,ivec4的每个分量代表重物所连接的其他4个顶点的索引号,称之为连接向量(connection vector),当没有哪个方向没有连接顶点时,我们将连接元素指向同一个顶点本身,例如:14号顶点的连接向量是 (9, 14, 19, 13)    9是上方顶点,14代表没有右方顶点,19是下方顶点,13是左边顶点。

可用索引-1代表顶点固定在原地不动,无论受到什么力作用,它的位置都不会被更新。如果连接向量的X分量为-1,那么更新顶点位置和速度的计算就会被跳过(仅跳过X分量代表的方向的物理计算情况?)

在每个顶点上,我们的顶点着色器使用常规顶点属性来运行和获取它本身的位置和连接向量。然后它将通过使用连接向量(同样也是常规顶点属性)的元素对TBO进行检索,从而对与它连接的顶点的当前位置进行查询。对于每个连接的顶点来说,它可以计算出这些顶点到它的距离,从而计算出它们之间的虚拟弹簧的伸长量。有了这个伸长量,就可以计算出弹簧施加在它上面的力,从而计算出对这个顶点物块产生的加速度,继而得到下一次迭代中使用的新位置的速度。这听起来好像很复杂,其实并非如此——这只是一些牛顿力学和胡克定律的内容罢了。(哇擦 贼装逼)

胡克定律的内容是: F = -kx

F:弹簧产生的力,k:弹簧的弹性系数(代表弹簧有多硬),x:弹簧的伸长量。弹簧伸长量是相对于弹簧自然长度(不受外力拉伸或压缩情况下的长度)而言的。

回到本例,我们会将所有弹簧的自然长度设置为相同值,并将它存储在一个统一值中,相对于弹簧的任何拉伸都会产生一个正的x值,任何压缩都会产生一个负的x值。弹簧的瞬时长度只不过是它从一端到另一端的向量的长度——这正是我们要在顶点着色器中进行计算的。我们通过将表示力大小的标量F乘以弹簧的方向来为这个力添加一个方向。引入变量d,它就是弹簧的标准化方向。

带方向的F = dF               [带方向的F是弹簧作用力]

这样,我们得到了由于弹簧的伸长或者压缩而作用在物块上的力。如果我们要简单地在物块上应用这个力,那么这个系统将会产生摆动,并且由于数值上存在误差,最终将会变得不稳定。所有真实的弹簧系统都会由于摩擦力的作用而产生能量损失,这种效果可以通过在力学方程中加入阻尼来进行建模。由于阻尼而产生的力就由这个方程式确定

带方向的Fd = -cv

带方向的Fd:阻尼力
c:阻尼系数,理想情况下应该为每个弹簧计算阻尼产生的力,但对于本例简单系统来说,有一个基于这个物块速度的力就够了。
v:初始速度,在每个时间步长计算时,使用一个固定初速来近似地代替这个方程中要用的连续微分。

在着色器中,我们通过计算阻尼力,然后将每个连接到这个物块的弹簧所施加给这个物块的力进行累加,从而对F进行初始化。

最后,我们可以将重力简单地视为在每个物块上增加一个作用力,从而将重力应用到这个系统。重力是一种不变的力,通常是沿着向下的方向作用的。我们只要将它加到作用在物块上的初始作用力上就可以了。

F(total) = G - dkx - cv

F(total):重力G + 弹簧作用力 + 弹簧阻尼力 的 合力

一旦得到了这个合力,我们就可以运用牛顿力学定律,计算出物块的加速度:

F = ma ,  则a = F/m

F可看成就是合力,m是质量,a是加速度。

给个定初始速度(从其他属性数组中获得),我们可以将它代入下面的运动方程式中,来计算我们的最终速度,以及在一定时间内会移动的距离。

v = u + at       s = u + a(t^2) / 2

u:初始速度,  v:最终速度  t:时间步长(由应用程序提供),s:移动距离

其中, a 、 u、 v 和 s 都是向量,剩下的事情就是编写着色器并将它连接到一个应用程序上。

坏事说前头,本例没有完整案例,几乎完全复制粘贴有的内容,后期看啥时候补充吧。

弹簧物块系统的顶点着色器内容:

#version 330
precision highp float;//这个输入向量在xyz中包含了顶点位置,而在w中则包含了顶点的数量
in vec4 position_mass;
//这是顶点的当前速度
in vec3 velocity;
//这是我们的连接向量
in ivec4 connection;//这是一个TBO 它将与position_mass输入属性被绑定到同一个缓冲区
uniform samplerBuffer tex_position_mass;//顶点着色器的输出与输入相同,只是包装在了一个接口模块中
out Vertex
{vec4 position_mass;vec3 velocity;
} vertex;//一个用来保存时间步长的Uniform值。应用程序可以对它进行更新
uniform float t;//全局弹性系数
uniform float k;//全局阻尼系数
uniform float c;//重力
const vec3 gravity = vec3(0.0, -0.03, 0.0);
//弹簧的自然长度
uniform float rest_length;//模型视图投影矩阵
uniform mat4 mvp;void main(void)
{vec3 p = position_mass.xyz; //p可以是位置float m = position_mass.w;  //m是顶点的质量vec3 u = velocity;          //初速度vec3 F;                     //F是物块受到的力vec3 v = u;                 //v是最终速度vec3 s = vec3(0.0);         //s是这一时间步长内的位移//检查这个顶点是否是“固定”顶点if (connection[0] != -1) {//使用重力和阻尼来对F进行初始化F = gravity - c * u;//考虑弹簧作用力对F的影响for (int i = 0; i < 4; i++) {if (connection[i] != gl_VertexID) {// q是另一个顶点的位置 不考虑其他顶点的质量 [即从顶点缓冲区tex_position_mass]根据索引值来拿到另一个连接方向的顶点位置xyzvec3 q = texture(tex_position_mass, connection[i]).xyz;vec3 d = p - q; //自身顶点指向q顶点的向量dfloat x = length(d); //距离xF += -k * (1.0 - x) * normalize(d); //弹簧作用力: -dkx//发现问题: 作者没用rest_length - x 而是 1.0 作为自然长度了???}}//至此合力F get~  开始计算位移和最终速度float a = F / m;//位移s = u * t + 0.5 * a * t * t;//最终速度v = u + a * t;}//对输出进行写操作vertex.position_mass = vec4(p + s, m);vertex.velocity = v;//更新gl_Position以便更够对点进行渲染gl_Position = mvp * vec4(p + s, 1.0);
}

我们需要构建缓冲区来保存位置、速度和连接信息。我们需要对位置和速度信息进行双重缓冲,以便能够在一次性从一组缓冲区中进行读取,并写入到另一组缓冲区中,然后进行缓冲区交换,以便数据能够在一个缓冲区和另一个缓冲区之间来回移动。链接信息在每次传递过程中都保持不变,所以它是常量。要完成这些工作需要使用2对VBO(顶点缓冲区对象)和一对VAO(顶点数组对象)。有一组位置和速度属性绑定到第一个VAO,随着通用连接信息指向第一对VBO。另一组位置和速度属性绑定到另一个VAO,同样随着通用连接信息指向第二对VBO。我们总共需要5个VBO——两个缓冲区用来保存位置,两个缓冲区用来保存速度,一个缓冲区用来包含连接向量。【感觉有问题,简单来说就是 速度和位置质量都占据1个缓冲区,但由于双重缓存策略 要再多一倍,即4个缓冲区,则常量的连接信息则只用一个】

除了VBO之外,我们还需要2个TBO。我们将每个缓冲区都同时作为一个VBO和一个TBO使用。这看起来似乎有些奇怪,但是在OpenGL中完全合理的——毕竟,我们只是在同一个缓冲区中通过两种不同的方法进行读取而已。为了对此进行设置,我们生成两个纹理,并将它们绑定到GL_BUFFER_TEXTURE绑定点,并且使用glTexBuffer将这些缓冲区连接到它们之上,这部分知识将在本书后面的内容进行讲解。在我们绑定顶点数组对象A时,还绑定了纹理A。在绑定顶点数组对象B时,还绑定了纹理B。这样,同一个数据就会同时出现在position顶点属性和tex_position samplerBuffer缓冲区纹理中。

完整实现在本书的网站中找到,示例应用程序包含创建和初始化缓冲区、执行双缓存和对结果进行可视化的代码。这个应用程序将两个顶点固定在了相应位置,所以整个系统不会掉落到屏幕的底部。一旦我们将所有这些缓冲区进行连接,我们就可以在系统中通过调用一次glDrawArrays来模拟一个时间步长了。系统中的每一个节点都有一个单独的GL_POINT图元来表示。如果我们对模型视图投影矩阵(mvp)进行初始化并让系统开始运行,我们将会看到如下图结果【因没示例没法截图】。

为了视觉效果更好,可将点的大小设置更大,或者使用glDrawElements和GL_LINES图元的索引化绘制来对节点间的连接进行可视化。如果我们不想在屏幕上绘制这些点,可启用GL_RASTERIZER_DISCARD

后期案例补充~

几何着色器

几何着色器是一种新的着色器类型,最初是以OpenGL扩展的形式引入的,然后又称为OpenGL核心规范(3.2)的一部分。与其他着色器类型相比,它与众不同在于,它一次性对整个图元(三角形、线或点)进行处理,并且实际上可以改变OpenGL管线中的数据量。一个顶点着色器一次可以处理一个顶点,它无法对其他顶点的信息进行访问,并且它是严格单进单出的。这就是说顶点着色器不能产生新的顶点,并不能阻止OpenGL对这个顶点进行进一步的处理。类似地片段着色器同理只能处理一个片段,不能创建新片段,并且只能通过丢弃片段来销毁它们。

几何着色器可以对一个图元(新的GL_TRIANGLES_ADJACENCY 和 GL_TRIANGLE_STRIP_ADJACENCY图元模式可以支持多达6个)中的所有顶点进行访问,可以改变一个图元的类型,甚至可以创建和销毁图元。

几何着色器和顶点着色器以及片段着色器的另一个区别是,几何着色器是OpenGL管线中的可选部分。几何着色器的输入是顶点着色器的输出,在顶点着色器之后可以没有几何着色器,几何着色器能够对顶点着色器的输出进行处理,例如 新增图元,并对图元进行变换,并承担起原顶点着色器进行的顶点数据插值并传递给片段着色器的工作。

直通几何着色器

#version 330precision highp float;layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;void main(void)
{int n;for (n = 0; n < gl_in.length(); n++) {gl_Position = gl_in[n].gl_Position;EmitVertex();}EndPrimitive();
}

precision highp float 设置着色器精度, triangles 作为输入, triangle_strip 作为输出 , 其中 max_vertices = 3 是设置最大顶点数为3个输出,即限制几何着色器的输出最大为3个顶点信息。在main函数里,gl_in是几何着色器特有的内建变量,用它来获取图元数量,并用过gl_in[n]来获取图元数据,其中我们访问了gl_Position顶点位置传给gl_Position内建变量输出。gl_in是顶点着色器的输出结构体数组。gl_in数组的长度由输入图元模式决定,而因为本例中的特定着色器中,输入图元模式为三角形,故gl_in的大小为3,循环迭代所有顶点位置赋值给gl_Position并调用EmitVertex()内建函数通知这个着色器已经完成了对这个顶点的工作,它应该将所有这些信息进行存储并准备开始设置下一个顶点。在循环之后还要调用EndPrimitive()内建函数,通知着色器已经完成了当前图元生成顶点的工作,可以开始处理下一个图元了。因为我们设置的是triangle_strip作为输出,着色器进行了输入图元顶点数(3)次循环调用3次EmitVertex(),如果执行了3次以上,OpenGL将继续向三角形带中添加三角形。如果想几何着色器增加多一个独立的三角形,不与原本的三角形连在一起的独立三角形,那么应该在它们之间调用EndPrimitive()来切断这个联系,如果我们不调用EndPrimitive(),图元连接将在着色器末尾才结束。

在应用程序中使用几何着色器

glCreateShader(GL_GEOMETRY_SHADER); 创建几何着色器

调用glShaderSource将着色器源码提交OpenGL,使用glCompileShader函数对着色器进行编译,并通过调用glAttachShader函数将它连接到一个程序对象,最后调用glLinkProgram函数进行连接。

调用glDrawArrays函数绘制几何图形时,顶点着色器将会为每个顶点运行一次,几何着色器也将为每个图元(点、线或三角形)运行一次,而片段着色器则将为每个片段运行一次。在我们将几何图形发送到OpenGL时所使用的图元模式,必须与几何着色器的输入图元模式相匹配。例如,如果几何着色器的模式为点,那么我们在调用glDrawArrays时只能用GL_POINTS。

如果几何着色器的输入图元模式是三角形,那课用GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN。

几何着色器输入模式允许的绘制模式
几何着色器输入模式 允许的绘制模式
points GL_POINTS
lines GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP
triangles GL_TRIANGLES、GL_TRIANGLE_FAN、GL_TRIANGLE_STRIP
lines_adjacency GL_LINES_ADJACENCY
triangles_adjacency GL_TRIANGLES_ADJACENCY

输入几何图形类型在几何着色器的程序体重使用layout限定符来指定。一般形式是:layout (primitive_type) in;

指定primitive_type为几何着色器将要处理的输入几何图形类型,而primitive_type必须是支持的几何图形模式中的一种,即points、lines、triangles、lines_adjacency或triangles_adjacency。几何着色器为每个图元运行一次。这就意味着对于不同模式分别是不同的运行次数,例如:points时是分别为每一个点运行一次,lines是则是线,triangles则是每个三角形。几何着色器的输入将以数组的形式表示,这个数组包含组成输入图元的所有顶点。

预定义的输入存储在一个叫做gl_in[]的内建数组中,这是一个由结构体组成的数组。

in gl_PerVertex
{vec4 gl_Position;float gl_PointSize;float gl_ClipDistance[];
} gl_in[];

这个结构体都是在顶点着色器进行赋值的输出,其中gl_Position是顶点位置,gl_PointSize是点大小,还有个gl_ClipDistance[]在第十二章节会介绍。这些变量在顶点着色器是以全局变量形式出现的,而在几何着色器将是以结构体成员出现。顶点着色器所写入的其他变量也会在几何着色器成为这样的数组形式出现。在独立varying变量(varying)的情况下,顶点着色器的输出会像通常一样进行声明,而几何着色器的输入则会有一个相似的声明,除非它们是数组。考虑一个顶点着色器将输出进行如下定义的情况。

out vec4 color;
out vec3 normal;

那么相应的几何着色器输入则如下所示。

in vec4 color[];
in vec3 normal[];

若有大量的数据从顶点着色器传递到几何着色器,可以包装成类似gl_in[]结构体形式,如下:

out VertexData
{vec4 color;vec3 normal;
} vertex;

几何着色器对应

in VertexData
{vec4 color;vec3 normal;
} vertex[];

使用vertex[n].color对几何着色器的逐顶点数据进行访问。几何着色器中输入数组的长度取决于将要处理的图元类型,例如 points 则是1个,triangles则是3个。

使用layout (points) in; 决定了是点输入形式,它的数组大小会自动设置为1,其他同理。

几何着色器输入数组的大小
输入图元类型 输入数组大小
points 1
lines 2
triangles 3
lines_adjacency 4
triangles_adjacency 6

同样,几何着色器输出也有限定符即,layout (primitive_type) out;  primitive_type允许可以是:points、line_strip和triangle_strip,并用out关键词。

注意:几何着色器只支持输出条带图元类型(不支持点带)

最后,还必须使用一个布局限定符来对几何着色器进行配置,它用max_vertices = n 来代表输出最大数量,来告诉OpenGL需要为此开辟最大多少空间。

layout (max_vertices = 3) out;  //最大3个顶点数据  这个最大数应该是能保证程序正常运行的最小数量,例如 计划接收点并一次生成一条线,那么可以将它设置为2,如何我们需要大量地进行创建新图元,那可以设置一个尽可能大的数字。一个几何着色器所支持的最大上限取决于OpenGL的实现,至少保证支持256个,但绝对的最大值无法确定,需通过调用以GL_MAX_GEOMETRY_OUTPUT_VERTICES为参数的glGetIntegerv来查询所支持的最大数量。

可用逗号来分隔布局限定符,来支持配置1个以上的布局限定符,如下:

layout (triangles_strip, max_vertices = n) out;

最基本的几何着色器要有 #version xxx,  布局限定符声明,以及一个空的main()函数,就能够正常进行编译和连接了,但这样它会丢弃我们发送给它的任何几何图形,而且应用程序将不会进行任何绘制。我们需要引入2个重要的函数:EmitVertex()和EndPrimitive()。如果不调用这2个函数,将不会绘制任何东西。

EmitVertex通知几何着色器我们已经完成了这个顶点所有信息的填充,注意是这个顶点,而不是几何着色器完成了任务,几何着色器是针对图元完成的,图元是点、线还是面,是由layout 限定符决定的。设置顶点的工作是和顶点着色器十分类似的,即写入内建变量gl_Position,它是一个裁剪空间的顶点位置坐标。在调用EmitVertex时,几何着色器会将当前所有输出变量中的值进行存储,并使用它们生成一个新的顶点。在一个几何着色器中,我们可以对EmitVertex进行任意次数的调用,直到达到在max_vertices布局限定符所设定的限制数量为止。每次调用我们都会向输出变量中放入新值,来生成新的顶点。

注意:EmitVertex每次调用后,它都会清空所有内建变量,如gl_Position等,所以请为每一次调用EmitVertex之前设定好所有要提交的数据,不然将会得到错误的结果。

EndPrimitive表示我们已经完成了将顶点附加到图元末端的工作,几何着色器只支持line_strip和triangle_strip图元类型。

若输出图元类型为triangle_strip,并调用EmitVertex的次数大于3次,那么几何着色器将在一个条带中生成多个三角形。同理,line_strip时,则会得到多条线段。

EndPrimitive指的是条带结束,如果想绘制独立的线或三角形,则必须在每2个或3个顶点之后调用EndPrimitive。

注意:若想生成一个triangles_strip 三角形条带,如果只有2个顶点 EmitVertex,之后调用EndPrimitive的话,将不会产生任何图元,之前生成的2个顶点也将会被简单地丢弃。线带同理。

在几何着色器中丢弃几何图形

https://blog.csdn.net/qq_39574690/article/details/116212339

在几何着色器中修改几何图形

https://blog.csdn.net/qq_39574690/article/details/116212472

在几何着色器中生成几何图形

https://blog.csdn.net/qq_39574690/article/details/116212788

在几何着色器中改变图元类型

https://blog.csdn.net/qq_39574690/article/details/116212995

由几何着色器引入的新图元类型

GL_LINES_ADJACENCY、GL_LINE_STRIP_ADJACENCY、GL_TRIANGLES_ADJACENCY和GL_TRIANGLE_STRIP_ADJACENCY。这些均为邻接图元类型(adjacency primitive type),这种类型能访问到邻近图元的顶点数据。例如:GL_LINES_ADJACENCY时,会输入4个顶点,中间2个顶点是自身线段的顶点,第一和最后一个是邻接顶点;GL_LINE_STRIP_ADJACENCY时,并且自定义输入8个顶点,它会形成5条线,即A..B->C->D->E->F->G..H ,其中..代表虚线 即A和H都是邻接顶点,只有BCDEF互相连在一起形成线带,若是GL_LINES_ADJACENCY类型,则是A..B->C..D  E..F->G..H ,只有2条线。

GL_TRIANGLES_ADJACENCY是输入6个顶点,形成1个三角形,只有1、3、5顶点形成,其他均为邻接顶点,若输入12个顶点,则只有2个三角形。若是GL_TRIANGLE_STRIP_ADJACENCY则会形成三角形带即 12个顶点 形成6个三角形,它会每隔一个顶点作为三角形带的真正顶点,其余均为邻接顶点。关于三角形带是比较复杂的,它的排序规则可自行查阅。

高级片段着色器

片段着色器中的后期处理——颜色校正

无案例

片段着色器中的后期处理——卷积

https://blog.csdn.net/qq_39574690/article/details/116246268

在片段着色器中生成图像数据

https://blog.csdn.net/qq_39574690/article/details/116278915

在片段着色器中丢弃工作

https://blog.csdn.net/qq_39574690/article/details/116279098

说明一下discard是会彻底舍弃片段的,之后的流程都不会跑了,有一些习惯做法就是对alpha值很小的片段会进行discard,避免不必要的开销。坏处也是有的,目前我只知道就是可能有些硬件支持了提前深度测试,如果着色器使用了discard就可能无法进行这种提前测试操作了,关于提前深度测试的好处懂的都懂。

逐片段控制深度

即可以在片段着色器控制片段的深度值,不控制的话就是OpenGL生成的片段插值深度。gl_FragDepth内建变量设置深度值 或 gl_FragCoord.z值设置深度值。若没有进行设置深度值,OpenGL是在执行片段着色器之前就已经确定下深度值了,所以就会有提前深度测试一说法,即在片段着色器之前先进行深度测试来舍弃一部分无法通过深度测试的片段,这样就能节省一部分开销。(这可能要保证片段着色器没有写discard舍弃片段,如果写了就可能无法进行提前深度测试了,因为如果给那些被舍弃的片段提前进行深度测试的话,如果通过了那么被舍弃的片段也会进行写入深度,会影响深度缓冲区了。为了保证不影响就必须得放到片段着色器之后进行深度测试。)

更高级的着色器函数

插值和存储限定符

之前了解到的有smooth和flat,分别是插值限定符和关闭插值限定符,还有2个是centroid 质心采样和noperspective限定符。

质心采样:顶点着色器输出 centroid out vec2 tex_coord;  片段着色器输入 centroid in vec2 tex_coord;

质心采样是指定片段数据的插值样本是保证在图元覆盖内的,否则可能会是采用图元之外的样本进行插值。若需精确地过渡,最好不使用质心采样。

使用质心采样来执行边缘检测

原理就是利用了质心采样会保证变量在图元区域内的作用,使用1个变量不使用质心采样的和一个使用了质心采样限定符的vec2变量,它保存了经过变换的顶点数据(x,y)分量,在片段着色器对它们进行比较,若不相等则是处于边缘。

无透视校正的插值

顶点着色器 noperspective out vec2 texcoord;   片段着色器 noperspective in vec2 texcoord;
它是屏幕空间中进行的线性插值,默认是透视插值,透视插值能保持着纹理的正确透视效果(不会产生倾斜),而线性插值是随着观察角度的倾斜导致纹理倾斜。

高级内建函数

clamp限制数值x在范围[min,max]内、mix线性插值、step(a,b)b>a时返回1,否则返回0、smoothstep将范围[min,max]内的数值映射回[0,1],之外的数值<min则为0,>max则为1。

intBitsToFloat和floatBitsToInt强转

dot点积、cross叉积、outerProduct求两个向量的外积、distance求两点之间距离、length求一个向量长度、normalize求向量标准化、reflect反射和refract折射函数

transpose矩阵转置、determinant求行列式、inverse求逆矩阵操作、matrixCompMult在2个矩阵之间执行一个逐分量相乘的操作,注意默认*是矩阵之间的乘法。

>、!=和<=等比较操作符是针对单个标量布尔值比较的,向量比较是通过内建函数来进行的,如 lessThan、notEqual和lessThanEqual等,可使用any检查任意分量为true则返回true、all检查所有分量为true才返回true。布尔向量不能直接用在if中,if必须是判断一个标量布尔才能使用,例如用all或any来转化布尔向量。

统一缓冲区对象

将统一值封装成结构体形式存在,并保证了统一值在结构体的变量位置相同。

uniform TransformBlock
{float scale;vec3 translation;float rotation[3];mat projection_matrix;
} transform; //transform[];

建立统一块

统一块即上面的结构体一样的东西,它会放入一个缓冲区对象,以glBufferData或glMapBuffer填充数据,缓冲区的数据会以两种布局存储。

① 共享布局 shared layout。

共享布局是OpenGL决定如何布置对于运行时性能和着色器访问来说最佳的情况,这样能让着色器获得更好的性能,但应用程序需要做更多的工作。若应用程序有多个着色器,每个着色器都会共享同一个统一块的数据,每个着色器编译时都会为这个统一块开辟同样的空间。使用共享布局必须在缓冲区对象中确定统一块成员的位置。

一个统一块的每一个成员都有一个索引,作为它的引用,来查询它的大小和在块中的位置。获取索引的方法:

void glGetUniformIndices(GLuint program, GLsizei uniformCount, const GLchar ** uniformNames, GLuint * uniformIndices);

这方法是查询出program着色器中的所有uniformNames名称的统一值(包括在统一块内的)索引,将查询索引结果存储到uniformIndices中,其中uniformCount是统一值个数。

const GLchar * uniformNames[4] =
{"TransformBlock.scale","TransformBlock.translation","TransformBlock.rotation","TransformBlock.projection_matrix"
};
GLuint uniformIndices[4];glGetUniformIndices(program, 4 uniformNames, uniformIndices);

使用uniformIndices[4]索引来查询缓冲区中统一块成员的位置和大小。

void glGetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint * uniformIndices, GLenum pname, GLint * params);
GLint uniformOffsets[4];
GLint arrayStrides[4];
GLint matrixStrides[4];
glGetActiveUniformsiv(program, 4 uniformIndices, GL_UNIFORM_OFFSET, uniformOffsets);
glGetActiveUniformsiv(program, 4 uniformIndices, GL_UNIFORM_ARRAY_STRIDE, arrayStrides);
glGetActiveUniformsiv(program, 4 uniformIndices, GL_UNIFORM_MATRIX_STRIDE, matrixStrides);

其中,uniformOffsets是获取了4个统一值的偏移量,arrayStrides是它的数组成员的步长,matrixStrides是它的矩阵成员的步长。

其他相关的统一值参数:

Pname的值 返回的信息
GL_UNIFORM_TYPE 统一值的数据类型
GL_UNIFORM_SIZE 数组的大小,若不是数组则为1
GL_UNIFORM_NAME_LENGTH 统一值名称长度,以字符为单位
GL_UNIFORM_BLOCK_INDEX 统一值作为其中一个成员的块的索引
GL_UNIFORM_OFFSET 返回块的缓冲区中统一值的偏移
GL_UNIFORM_ARRAY_STRIDE 一个数组中连续元素之间间隔的字节数,若不是数组,则为0
GL_UNIFORM_MATRIX_STRIDE 列优先矩阵中的每一列或行优先矩阵中的每一行的第一个元素之间间隔的字节数。若统一值不是数组,则为0
GL_UNIFORM_IS_ROW_MAJOR 输出数组中每个元素的值,若统一值是一个行优先矩阵则为1,若是列优先矩阵则为0

一般我们只用到int、float、bool 或vec4这种,我们只需要它的偏移量即可。一旦知道了这些信息我们就可以将偏移量传递到glBufferSubData在正确的位置加载数据,或直接使用偏移量在内存中装配缓冲区了。

举例填充统一块的scale成员(float类型数据)

unsigned char * buffer = (unsigned char *)malloc(4096); //缓冲区分配空间//对缓冲区指针偏移第一成员scale的偏移量拿到scale成员指针,并直接赋值3.0f
*((float*)(buffer + uniformOffsets[0])) = 3.0f;

填充translation(vec3类型)

*((float *)(buffer + uniformOffsets[1])) = 1.0f;
*((float *)(buffer + uniformOffsets[1] + sizeof(GLfloat))) = 2.0f;
*((float *)(buffer + uniformOffsets[1] + 2 * sizeof(GLfloat))) = 3.0f;

填充rotation(vec3[3]数组)

const GLfloat rotations[] = { 30.0f, 40.0f, 60.0f };
unsigned int offset = uniformOffsets[2];for (int n = 0; n < 3; n++)
{*((float *)(buffer + offset)) = rotations[n];offset += arrayStrides[2];
}

填充projection_matrix(mat4)

//列优先存储
const GLfloat matrix[] =
{1.0f, 2.0f, 3.0f, 4.0f,9.0f, 8.0f, 7.0f, 6.0f,2.0f, 4.0f, 6.0f, 8.0f,1.0f, 3.0f, 5.0f, 7.0f
};for(int i = 0; i < 4; i++)
{//matrixStride[3]是矩阵列步长GLuint offset = uniformOffsets[3] + matrixStride[3] * i;for(int j = 0; j < 4; j++){*((float *)(buffer + offset)) = matrix[i * 4 + j];offset += sizeof(GLfloat);}
}

②标准布局

根据OpenGL提供的一组为各种数据类型指定大小和排列的规则决定将数据存放在缓冲区什么位置。声明统一块使用标准布局是用layout(std140)前缀声明,如下:

layout(std140) uniform TransformBlock
{float scale;vec3 translation;float rotation[3];mat projection_matrix;
} transform; //transform[];

一旦声明使用std140标准布局,这个块的每个成员就会在缓冲区以遵循一组规则确定的偏置占用预先定义好的空间大小,规则概述如下。

int 、float、bool总是在N字节分界处开始存储,例如:4字节的整数倍位置开始

vec2 总是在2N字节分界处开始

vec3、vec4总是在4N字节的分界处开始

一个标量数组或向量类型数组总是以同样规则定义的分界处开始 (ints 或 vec3s) 会进行上舍入与vec4看齐。意味着这些会产生一个缺口,除了vec4或Nx4矩阵数组是紧密的。

矩阵本质上是当成多个vec4,而矩阵数组则是更多个vec4看待。

结构体和结构体的数组都需要额外的包装:整个结构体从它最大的成员所要求的分界点开始,并上舍入到vec4的大小。

注意:当使用标准布局时,定义一个floats数组 可能无法从C语言的floats数组将数据复制到统一块的floats数组中,因C语言的会进行包装,而统一块的floats不会进行包装。

举例说明:

layout(std140) uniform TransformBlock
{//成员                           基本排列    偏移   排列偏移float scale;                    //4          0        0vec3 translation;               //16         4        16float rotation[3];              //16         28       32(rotation[0])//                    48(rotation[1])//                    64(rotation[2])mat4 projection_matrix;         //16                  80(column 0)//                    96(column 1)//                    112(column 2)//                    128(column 3)
} transform;

可使用GL_MAX_UNIFORM_BUFFERS参数的glGetIntegerv查找着色器阶段使用统一块缓冲区对象的最大值。其他相关还有GL_MAX_VERTEX_UNIFORM_BUFFERS、GL_MAX_GEOMETRY_UNIFORM_BUFFERS或GL_MAX_FRAGMENT_UNIFORM_BUFFERS分别查找顶点着色器、几何着色器、片段着色器中的统一块缓冲区限制最大值。

获取程序中一个统一块的索引:

GLuint glGetUniformBlockIndex(GLuint program, const GLchar * uniformBlockName);

返回program的uniformBlockName名称统一块的索引,上面例子叫"TransformBlock"。

绑定点分配统一块缓冲区

void glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);

uniformBlockIndex是统一块索引,即上面方法返回的索引。uniformBlockBinding是绑定点索引。

可通过GL_MAX_UNIFORM_BUFFER_BINDINGS为参数的glGetIntegerv查询最大绑定点数量。

将缓冲区绑定到统一缓冲区绑定点

glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer);

其中index是会对应上uniformBlockBinding的统一块,即buffer会填充到对应的统一块,举例说明:

GLuint harry_index = glGetUniformBlockIndex(program, "Harry");
GLuint bob_index = glGetUniformBlockIndex(program, "Bob");
GLuint susan_index = glGetUniformBlockIndex(program, "Susan");glUniformBlockBinding(program, harry_index, 1);
glUniformBlockBinding(program, bob_index, 3);
glUniformBlockBinding(program, susan_index, 0);glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer_b);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, buffer_c);
glBindBufferBase(GL_UNIFORM_BUFFER, 3, buffer_a);//至此,Harry <---> 1 <---> buffer_c //即buffer_c数据会填充到Harry统一块
//Bob <---> 3 <---> buffer_a //即buffer_a数据会填充到Bob统一块
//Susan <---> 0 <---> buffer_b //即buffer_b数据会填充到Susan统一块

统一块的普遍用法是从瞬时状态分离出稳定状态。通过以上标准绑定后,可以在改变程序时让缓冲区保持绑定。例如,有一些相对固定的状态——比如说投影矩阵、视口大小和一些其他东西,它们每一帧改变一次或者更少——那么我们可以将这个信息保留在一个绑定到绑定点0的缓冲区中。以后想使用这些信息作为统一值使用时,就可将一个程序的统一块绑定到绑定点0上即可直接获取到最新的投影矩阵或视口大小等统一值。更具体点的是,当一个片段着色器模拟金属时,提前将着色器程序的统一块绑定到绑定点1上,在每次需要对对象进行渲染时再将对象所用的金属材质参数缓冲区绑定到绑定点1上进行改变统一块的数据,来进行渲染(其实就是一口气填充了所有统一值,但具体案例没有。)

【OpenGL】蓝宝书第十一章——高级着色器应用相关推荐

  1. OpenGL蓝宝书第七章:立体天空和纹理折射、双纹理(下)

    对照了蓝宝书,才知道红宝书的长处. reflect函数的原理在红宝书中有说明,仅仅有对照了红宝书,才知道红宝书的定位:高级工具书. 蓝宝书作为入门级书籍,以较快的速度让读者敲到代码去思考,总遗留了须要 ...

  2. OpenGL蓝宝书第九章学习笔记:片段着色器和帧缓存

    前言 本篇在讲什么 OpenGL蓝宝书第九章学习笔记之片段着色器和帧缓存 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C++语法有简单认知 对OpenGL有简单认知 最好是有OpenGL ...

  3. OpenGL超级宝典(第7版)之第十一章高级数据管理

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 OpenGL超级宝典(第7版)之第十一章高级数据管理 前言 一.取消绑定 二.稀疏纹理 三.压缩纹理 四.压缩数据格式 五.高质量纹理 ...

  4. OpenGL蓝宝书学习日记(1)—— 配置OpenGL环境与创造第一个三角形

    OpenGL蓝宝书学习日记(1)-- 配置OpenGL环境与创造第一个三角形 一.安装VS VS有众多版本,本人使用的是VS2017,在官网即可下载,有为学生专门提供的免费版,注册账号登陆后即可无限试 ...

  5. OpenGL程序管道,可分离程序和着色器子例程的基本用法

    OpenGL程序管道,可分离程序和着色器子例程的基本用法 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <stdio.h> #inc ...

  6. OpenGL中的曲面细分和几何着色器

    [摘要]本文我们先介绍OpenGL中的曲面细分的一些基本概念,然后给两个例子说明不得不用这项技术的理由. 曲面细分是OpenGL 4.0之后才定义的功能,使用之前请确认你的显卡驱动支持OpenGL4. ...

  7. 【OpenGL】蓝宝书第四章——基础变换:初识向量/矩阵

    目录 3D数学 向量 点乘 叉乘 矩阵 理解变换 视觉坐标 视图变换 模型变换 模型视图的二元性 投影变换 视口变换 模型视图矩阵 矩阵构造 单位矩阵 平移 旋转 缩放 综合变换 运用模型视图矩阵 更 ...

  8. OpenGL蓝宝书源码学习(五)第三章——Blending.cpp

    颜色混合源码实例,此程序使用透明度来实现可以在白色背景上来回移动的半透明红色的幻觉,并且移动过程中实现了有其他色块颜色混合的效果. 此实例是在Move.cpp示例程序基础上编写的,所以这里只贴出新增的 ...

  9. OpenGL蓝宝书源码学习(十)第五章——纹理的应用、Mip贴图、各项异性过滤和纹理压缩基础

    一.纹理应用 1.纹理坐标 我们是通过为每个顶点指定一个纹理坐标而直接在几何图形上进行纹理贴图的.纹理坐标要么是指定为着色器的一个属性,要么通过算法计算出来.纹理贴图中的纹理单元是作为一个更加抽象的纹 ...

最新文章

  1. Jmeter学习(三)
  2. 请问大数据有没有速成的方法?嗯 真的没有
  3. sql语句转linq的一个小工具
  4. 当互联网公司聚餐时,他们是如何谈(ge)笑(huai)风(gui)生(tai)的
  5. 远程服务器概念,远程服务
  6. E百科 | 基于MEC的边缘AI服务
  7. 给Tomcat打开远程debug端口
  8. 数据库-查看表-创建表-查询表
  9. 论文《learning to link with wikipedia》
  10. [剑指offer]面试题第[49]题[Leetcode][第264题][JAVA][丑数][动态规划][堆]
  11. 計算機二級-java05
  12. 《深入理解OSGi:Equinox原理、应用与最佳实践》一3.4 事件监听
  13. vlookup两个条件匹配_用VLOOKUP进行同时满足两个条件的查找,几种方法都在这啦!||Excel技巧...
  14. 亚马逊欧洲站的VAT需要多久申报一次
  15. vray渲染出图尺寸_3DMax渲染出图尺寸怎么设置?
  16. 坚持不懈2 android游戏,坚持不懈的赛跑者
  17. android 专业密码键盘,Android仿支付宝、京东的密码键盘和输入框
  18. 键盘党的福音 史上最全win8快捷键大集合
  19. 浙大 | PTA 习题9-5 通讯录排序 (20分)
  20. 高德地图 动态渲染marker

热门文章

  1. ICASSP 2022丨多通道多方会议转录(M2Met)国际挑战赛
  2. 试试TextLogoLayout生成自己的logo
  3. 慕课java工程师2020版_中国大学MOOC慕课2020Java程序设计题目答案
  4. 极目智能发布首款「全国产化」L2级智能驾驶方案,已获得10余家车企定点
  5. 微信瑞文智力测试1分_瑞文智力测试
  6. Text file busy 的解决办法
  7. EFI引导-硬盘安装win7 64位
  8. 6款经典图表软件推荐
  9. 数据结构(二)栈和队列(模板实现)
  10. 超50%的新冠肺炎患者出现认知障碍,部分患者现抑郁症状