LearnOpenGL学习笔记—高级OpenGL 07-08:高级数据/高级GLSL
1 高级数据
本节对应官网学习内容:高级数据
1.1 回顾
我们之前在OpenGL的使用中,已经用过大量的缓冲来存储数据了。
操作缓冲其实还有更有意思的方式,而且使用纹理将大量数据传入着色器也有更有趣的方法。
这一节中,我们将讨论一些更有意思的缓冲函数,以及我们该如何使用纹理对象来储存大量的数据(纹理的部分还没有完成)。
OpenGL中的缓冲只是一个管理特定内存块的对象,没有其它更多的功能了。
在我们将它绑定到一个缓冲目标(Buffer Target)时,我们才赋予了其意义。
当我们绑定一个缓冲到GL_ARRAY_BUFFER时,它就是一个顶点数组缓冲,我们也可以将其绑定到GL_ELEMENT_ARRAY_BUFFER,就变成了元素数组缓冲。
也就是所有的XXX_BUFFER都属于BUFFER的概念,当被赋予不同种类时才会真正实例化。
OpenGL内部会为每个目标储存一个缓冲,并且会根据目标的不同,以不同的方式处理缓冲。
到目前为止,我们一直是调用glBufferData函数来填充缓冲对象所管理的内存,这个函数会分配一块内存,并将数据添加到这块内存中。
如果我们将它的data参数设置为NULL,那么这个函数将只会分配内存,但不进行填充。
我们通常会在预留(Reserve)特定大小的内存,之后再回到这个缓冲一点一点填充时这么用。
除了只调用一次函数来调填充整个缓冲之外,我们也可以使用glBufferSubData,填充缓冲的特定区域。
这个函数需要一个缓冲目标、一个偏移量、数据的大小和数据本身作为它的参数。
这个函数不同的地方在于,我们可以提供一个偏移量,指定从何处开始填充这个缓冲。
这能够让我们插入或者更新缓冲内存的某一部分。
要注意的是,缓冲需要有足够的已分配内存,所以对一个缓冲调用glBufferSubData之前必须要先调用glBufferData。
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // 范围: [24, 24 + sizeof(data)]
将数据导入缓冲的另外一种方法是,请求缓冲内存的指针,直接将数据复制到缓冲当中。
通过调用glMapBuffer函数,OpenGL会返回当前绑定缓冲的内存指针,供我们操作:
float data[] = {
0.5f, 1.0f, -0.35f
...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 获取指针
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 复制数据到内存
memcpy(ptr, data, sizeof(data));
// 记得告诉OpenGL我们不再需要这个指针了
glUnmapBuffer(GL_ARRAY_BUFFER);
- 当我们使用glUnmapBuffer函数,告诉OpenGL我们已经完成指针操作之后,OpenGL就会知道你已经完成了。
在解除映射(Unmapping)之后,指针将会不再可用,并且如果OpenGL能够成功将数据映射到缓冲中,这个函数将会返回GL_TRUE。
如果要直接映射数据到缓冲,而不事先将其存储到临时内存中,glMapBuffer这个函数会很有用。
比如说,我们可以从文件中读取数据,并直接将它们复制到缓冲内存中。
1.2 分批顶点属性
通过使用glVertexAttribPointer,我们能够指定顶点数组缓冲内容的属性布局。
在顶点数组缓冲中,我们是对属性进行了交错(Interleave)处理,也就是说,我们将每一个顶点的位置、法线和纹理坐标紧密放置在一起。
既然我们现在已经对缓冲有了更多的了解,我们可以采取另一种方式。
我们可以做的是,将每一种属性类型的向量数据打包(Batch)为一个大的区块,而不是对它们进行交错储存。
与交错布局123123123123不同,我们将采用分批(Batched)的方式111122223333。
在日常的处理中,当从文件中加载顶点数据的时候,我们会获取到的是一个位置数组、一个法线数组和/或一个纹理坐标数组。
我们需要花点力气才能将这些数组转化为一个大的交错数据数组。
使用分批的方式会是更简单的解决方案,我们可以很容易使用glBufferSubData函数实现:
void glBufferSubData(
GLenum target,
GLintptr offset,
GLsizeiptr size,
const GLvoid * data);
- target: 可以参考glBufferData中的描述,用来指定需要更新的缓冲区对象的类型
offset: 指定了更新数据相对于缓冲区对象中原始数据开始位置的偏移量,也就是说要从什么地方开始更新原来的数据(以字节为单位)
size:需要更新的数据量的大小
data:一个指向新数据源的指针,将新的数据源拷贝到缓冲区对象中完成更新
sizeof(数组名) 计算的是数组占用的存储大小
float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// 填充缓冲
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
这样子我们就能直接将属性数组作为一个整体传递给缓冲,而不需要事先处理它们了。
我们虽然也可以将它们合并为一个大的数组,再使用glBufferData来填充缓冲,但对于这上述类型的数据,使用glBufferSubData会更合适一点。
我们还需要更新顶点属性指针来反映这些改变,我们已经把数据以位置,法线,纹理的方式存进了GL_ARRAY_BUFFER,所以接下来和以前设置顶点属性的方式差不多:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
注意第五个参数步长(Stride)等于顶点属性的大小,因为下一个顶点属性向量能在3个(或2个)分量之后找到。
这给了我们设置顶点属性的另一种方法。
使用哪种方法都不会对OpenGL有什么立刻的好处,它只是设置顶点属性的一种更整洁的方式。
具体使用的方法将完全取决于喜好与程序类型。
1.3 复制缓冲
当缓冲已经填充好数据之后,我们可能会想与其它的缓冲共享其中的数据,或者想要将缓冲的内容复制到另一个缓冲当中。glCopyBufferSubData能够让我们相对容易地从一个缓冲中复制数据到另一个缓冲中。
这个函数的原型如下:
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,
GLintptr writeoffset, GLsizeiptr size);
- readtarget和writetarget参数需要填入复制源和复制目标的缓冲目标。
-比如说,我们可以将VERTEX_ARRAY_BUFFER缓冲复制到VERTEX_ELEMENT_ARRAY_BUFFER缓冲,分别将这些缓冲目标设置为读和写的目标。
但如果我们想读写数据的两个不同缓冲,但是它们都是都为顶点数组缓冲该怎么办呢?
我们不能同时将两个缓冲绑定到同一个缓冲目标上。
正是出于这个原因,OpenGL提供给我们另外两个缓冲目标,叫做GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER。
我们接下来就可以将需要的缓冲绑定到这两个缓冲目标上,并将这两个目标作为readtarget和writetarget参数。
glCopyBufferSubData会从readtarget中读取size大小的数据,并将其写入writetarget缓冲的writeoffset偏移量处。
下面这个例子展示了如何复制两个顶点数组缓冲:
float vertexData[] = { ... };
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
我们也可以只将writetarget缓冲绑定为新的缓冲目标类型之一:
float vertexData[] = { ... };
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
有了这些关于如何操作缓冲的额外知识,我们已经能够以更有意思的方式使用它们了。
当越深入OpenGL时,这些新的缓冲方法将会变得更加有用。
在下一节中,在我们讨论Uniform缓冲对象(Uniform Buffer Object)时,我们将会充分利用glBufferSubData。
2 高级GLSL
本节对应官网学习内容:高级GLSL
这一小节并不会向展示非常先进非常酷的新特性,也不会对场景的视觉质量有显著的提高。
但是,这一节会或多或少涉及GLSL的一些有趣的地方以及一些很棒的技巧,它们可能在今后会帮助到我们。
简单来说,它们就是在组合使用OpenGL和GLSL创建程序时的一些最好要知道的东西,和一些会让生活更加轻松的特性。
我们将会讨论一些有趣的内建变量(Built-in Variable),管理着色器输入和输出的新方式,以及一个叫做Uniform缓冲对象(Uniform Buffer Object)的有用工具。
2.1 GLSL的内建变量
着色器的设置通常都是最简化的,如果需要当前着色器以外地方的数据的话,我们必须要将数据传进来。
在之前的教学中,我们已经学会使用顶点属性、uniform和采样器来完成这一任务了。
然而,除此之外,GLSL还定义了另外几个以gl_为前缀的变量,它们能提供给我们更多的方式来读取/写入数据。
我们已经在前面教程中接触过其中的两个了:顶点着色器的输出向量gl_Position,和片段着色器的gl_FragCoord。
我们将会讨论几个有趣的GLSL内建输入和输出变量,并会解释它们能够怎样帮助到我们。
如果想知道所有的内建变量的话,请查看OpenGL的wiki。
2.1.1 顶点着色器变量
我们已经见过gl_Position了,它是顶点着色器的裁剪空间输出的位置向量。
如果你想在屏幕上显示任何东西,在顶点着色器中设置gl_Position是必须的步骤。
我们有很多可选的图元类型,我们一直都是用的独立三角形绘制。
OpenGL可以支持很多不同的图元类型,最基础的为点,线,或三角形。
线和三角形可以组合成条带,循环体或者扇面三角形。
点,线,或三角形也是大部分图象硬件设备支持的基础图元类型。
2.1.1.1 gl_PointSize
我们能够选用的其中一个图元是GL_POINTS,如果使用它的话,每一个顶点都是一个图元,都会被渲染为一个点。
点可以通过单一的顶点来表示,点实际上不存在面积,在OpenGL中它通过屏幕上的一个矩形区域来模拟,在渲染点源的时候,OpenGL会通过光栅化规则类判断点的位置。
以点为中心绘制一个四边形区域,四边形区域的边长等于点的大小,它是一个固定的状态,可以调用函数glPointSIze()设置。
gl_PointSize这个输出变量,它是一个float变量,我们可以使用它来设置点的宽高(像素)。
在顶点着色器中修改点的大小的话,就能对每个顶点设置不同的值了。
在顶点着色器中修改点大小的功能默认是禁用的,如果你需要启用它的话,你需要启用OpenGL的GL_PROGRAM_POINT_SIZE:
glEnable(GL_PROGRAM_POINT_SIZE);
一个简单的例子就是将点的大小设置为裁剪空间位置的z值,也就是顶点距观察者的距离。点的大小会随着观察者距顶点距离变远而增大。
在顶点着色器中
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
gl_PointSize = gl_Position.z;
}
结果就是,当我们远离这些点的时候,它们会变得更大
你可以想到,对每个顶点使用不同的点大小,会在粒子生成之类的技术中很有意思。
2.1.1.2 gl_VertexID
gl_Position和gl_PointSize都是输出变量,因为它们的值是作为顶点着色器的输出被读取的。
我们可以对它们进行写入,来改变结果。
顶点着色器还为我们提供了一个有趣的输入变量,我们只能对它进行读取,它叫做gl_VertexID。
整型变量gl_VertexID储存了正在绘制顶点的当前ID。
当(使用glDrawElements)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。
当(使用glDrawArrays)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。
虽然现在它没有什么具体的用途,但知道我们能够访问这个信息总是好的。
2.1.2 片段着色器变量
在片段着色器中,我们也能访问到一些有趣的变量。GLSL提供给我们两个有趣的输入变量:gl_FragCoord和gl_FrontFacing。
2.1.2.1 gl_FragCoord
在讨论深度测试的时候,我们已经见过gl_FragCoord了,因为gl_FragCoord的z分量等于对应片段的深度值。
然而,我们也能使用它的x和y分量来实现一些有趣的效果。
gl_FragCoord的x和y分量是片段的窗口空间(Window-space)坐标,其原点为窗口的左下角。
我们已经使用glViewport设定了一个800x600的窗口了,所以片段窗口空间坐标的x分量将在0到800之间,y分量在0到600之间。
通过利用片段着色器,我们可以根据片段的窗口坐标,计算出不同的颜色。
gl_FragCoord的一个常见用处是用于对比不同片段计算的视觉输出效果,这在技术演示中可以经常看到。
比如说,我们能够将屏幕分成两部分,在窗口的左侧渲染一种输出,在窗口的右侧渲染另一种输出。下面这个例子片段着色器会根据窗口坐标输出不同的颜色:
void main()
{
if(gl_FragCoord.x < 400)
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
else
FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
因为窗口的宽度是800。当一个像素的x坐标小于400时,它一定在窗口的左侧,所以我们给它一个不同的颜色。
我们现在会计算出两个完全不同的片段着色器结果,并将它们显示在窗口的两侧。
举例来说,你可以将它用于测试不同的光照技巧。
2.1.2.2 gl_FrontFacing
片段着色器另外一个很有意思的输入变量是gl_FrontFacing。
在面剔除教程中,我们提到OpenGL能够根据顶点的环绕顺序来决定一个面是正向还是背向面。
如果我们不(启用GL_FACE_CULL来)使用面剔除,那么gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分。
举例来说,我们能够对正向面计算出不同的颜色。
gl_FrontFacing变量是一个bool,如果当前片段是正向面的一部分那么就是true,否则就是false。比如说,我们可以这样子创建一个立方体,在内部和外部使用不同的纹理:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D frontTexture;
uniform sampler2D backTexture;
void main()
{
if(gl_FrontFacing)
FragColor = texture(frontTexture, TexCoords);
else
FragColor = texture(backTexture, TexCoords);
}
如果我们往箱子里面看,就能看到使用的是不同的纹理。
如果开启了面剔除,就看不到箱子内部的面了,所以现在再使用gl_FrontFacing就没有意义了。
2.1.2.3 gl_FragDepth
输入变量gl_FragCoord能让我们读取当前片段的窗口空间坐标,并获取它的深度值,但是它是一个只读(Read-only)变量。
我们不能修改片段的窗口空间坐标,但实际上修改片段的深度值还是可能的。
GLSL提供给我们一个叫做gl_FragDepth的输出变量,我们可以使用它来在着色器内设置片段的深度值。
要想设置深度值,我们直接写入一个0.0到1.0之间的float值到输出变量就可以了:
gl_FragDepth = 0.0; // 这个片段现在的深度值为 0.0
如果着色器没有写入值到gl_FragDepth,它会自动取用gl_FragCoord.z的值。
然而,由我们自己设置深度值有一个很大的缺点,只要我们在片段着色器中对gl_FragDepth进行写入,OpenGL就会(像深度测试小节中讨论的那样)禁用所有的提前深度测试(Early Depth Testing)。
它被禁用的原因是,OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。
在写入gl_FragDepth时,你就需要考虑到它所带来的性能影响。
然而,从OpenGL 4.2起,我们仍可以对两者进行一定的调和,在片段着色器的顶部使用深度条件(Depth Condition)重新声明gl_FragDepth变量:
layout (depth_<condition>) out float gl_FragDepth;
通过将深度条件设置为greater或者less,OpenGL就能假设只会写入比当前片段深度值更大或者更小的值了。
这样子的话,当深度值比片段的深度值要小的时候,OpenGL仍是能够进行提前深度测试的。
下面这个例子中,我们对片段的深度值进行了递增,但仍然也保留了一些提前深度测试:
注意这个特性只在OpenGL 4.2版本或以上才提供。
#version 420 core // 注意GLSL的版本!
out vec4 FragColor;
layout (depth_greater) out float gl_FragDepth;
void main()
{
FragColor = vec4(1.0);
gl_FragDepth = gl_FragCoord.z + 0.1;
}
2.2 接口块
到目前为止,每当我们希望从顶点着色器向片段着色器发送数据时,我们都声明了几个对应的输入/输出变量。
将它们一个一个声明是着色器间发送数据最简单的方式了,但当程序变得更大时,你希望发送的可能就不只是几个变量了,它还可能包括数组和结构体。
为了帮助我们管理这些变量,GLSL为我们提供了一个叫做接口块(Interface Block)的东西,来方便我们组合这些变量。
接口块的声明和struct的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in或out关键字来定义的。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out VS_OUT
{
vec2 TexCoords;
} vs_out;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vs_out.TexCoords = aTexCoords;
}
这次我们声明了一个叫做vs_out的接口块,它打包了我们希望发送到下一个着色器中的所有输出变量。
这只是一个很简单的例子,但可以想象一下,它能够帮助管理着色器的输入和输出。
当我们希望将着色器的输入或输出打包为数组时,它也会非常有用,我们将在下一节讨论几何着色器(Geometry Shader)时见到。
之后,我们还需要在下一个着色器,即片段着色器中定义一个输入接口块。
块名(Block Name)应该是和着色器中一样的(VS_OUT),但实例名(Instance Name)(顶点着色器中用的是vs_out)可以是随意的,但要避免使用误导性的名称。
#version 330 core
out vec4 FragColor;
in VS_OUT
{
vec2 TexCoords;
} fs_in;
uniform sampler2D texture;
void main()
{
FragColor = texture(texture, fs_in.TexCoords);
}
只要两个接口块的名字一样,它们对应的输入和输出将会匹配起来。
这是帮助管理代码的又一个有用特性,它在几何着色器这样穿插特定着色器阶段的场景下会很有用。
2.3 Uniform缓冲对象
我们已经使用OpenGL很长时间了,学会了一些很酷的技巧,但也遇到了一些很麻烦的地方。
比如说,当使用多于一个的着色器时,尽管大部分的uniform变量都是相同的,我们还是需要不断地设置它们,所以为什么要这么麻烦地重复设置它们呢?
OpenGL为我们提供了一个叫做Uniform缓冲对象(Uniform Buffer Object)的工具,它允许我们定义一系列在多个着色器中相同的全局Uniform变量。
当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次。
当然,我们仍需要手动设置每个着色器中不同的uniform。
并且创建和配置Uniform缓冲对象会有一点繁琐。
因为Uniform缓冲对象仍是一个缓冲,我们可以使用glGenBuffers来创建它,将它绑定到GL_UNIFORM_BUFFER缓冲目标,并将所有相关的uniform数据存入缓冲。
在Uniform缓冲对象中储存数据是有一些规则的,我们将会在之后讨论它。
首先,我们将使用一个简单的顶点着色器,将projection和view矩阵存储到所谓的Uniform块(Uniform Block)中:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
在我们大多数的例子中,我们都会在每个渲染迭代中,对每个着色器设置projection和view Uniform矩阵。
这是利用Uniform缓冲对象的一个非常完美的例子,因为现在我们只需要存储这些矩阵一次就可以了。
这里,我们声明了一个叫做Matrices的Uniform块,它储存了两个4x4矩阵。
Uniform块中的变量可以直接访问,不需要加块名作为前缀。
接下来,我们在OpenGL代码中将这些矩阵值存入缓冲中,每个声明了这个Uniform块的着色器都能够访问这些矩阵。
你现在可能会在想layout (std140)这个语句是什么意思。
它的意思是说,当前定义的Uniform块对它的内容使用一个特定的内存布局。这个语句设置了Uniform块布局(Uniform Block Layout)。
2.3.1 Uniform块布局
Uniform块的内容是储存在一个缓冲对象中的,它实际上只是一块预留内存。
因为这块内存并不会保存它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。
假设着色器中有以下的这个Uniform块:
layout (std140) uniform ExampleBlock
{
float value;
vec3 vector;
mat4 matrix;
float values[3];
bool boolean;
int integer;
};
我们需要知道的是每个变量的大小(字节)和(从块起始位置的)偏移量,来让我们能够按顺序将它们放进缓冲中。
每个元素的大小都是在OpenGL中有清楚地声明的,而且直接对应C++数据类型,其中向量和矩阵都是大的float数组。
OpenGL没有声明的是这些变量间的间距(Spacing)。
这允许硬件能够在它认为合适的位置放置变量。
比如说,一些硬件可能会将一个vec3放置在float边上。
不是所有的硬件都能这样处理,可能会在附加这个float之前,先将vec3填充(Pad)为一个4个float的数组。
这个特性本身很棒,但是会对我们造成麻烦。
默认情况下,GLSL会使用一个叫做共享(Shared)布局的Uniform内存布局,共享是因为一旦硬件定义了偏移量,它们在多个程序中是共享并一致的。
使用共享布局时,GLSL是可以为了优化而对uniform变量的位置进行变动的,只要变量的顺序保持不变。因为我们无法知道每个uniform变量的偏移量,我们也就不知道如何准确地填充我们的Uniform缓冲了。
我们能够使用像是glGetUniformIndices这样的函数来查询这个信息,但这超出本节的范围了。
虽然共享布局给了我们很多节省空间的优化,但是我们需要查询每个uniform变量的偏移量,这会产生非常多的工作量。
通常的做法是,不使用共享布局,而是使用std140布局。std140布局声明了每个变量的偏移量都是由一系列规则所决定的,这显式地声明了每个变量类型的内存布局。
由于这是显式提及的,我们可以手动计算出每个变量的偏移量。
每个变量都有一个基准对齐量(Base Alignment),它等于一个变量在Uniform块中所占据的空间(包括填充量(Padding)),这个基准对齐量是使用std140布局的规则计算出来的。
接下来,对每个变量,我们再计算它的对齐偏移量(Aligned Offset),它是一个变量从块起始位置的字节偏移量。一个变量的对齐字节偏移量必须等于基准对齐量的倍数。
布局规则的原文可以在OpenGL的Uniform缓冲规范这里找到,但我们将会在下面列出最常见的规则。
GLSL中的每个变量,比如说int、float和bool,都被定义为4字节量。每4个字节将会用一个N来表示。
和OpenGL大多数的规范一样,使用例子就能更容易地理解。我们会使用上面引入的那个叫做ExampleBlock的Uniform块,并使用std140布局计算出每个成员的对齐偏移量:
layout (std140) uniform ExampleBlock
{
// 基准对齐量 // 对齐偏移量
float value; // 4 // 0
vec3 vector; // 16 // 16 (必须是16的倍数,所以 4->16)
mat4 matrix; // 16 // 32 (列 0)
// 16 // 48 (列 1)
// 16 // 64 (列 2)
// 16 // 80 (列 3)
float values[3]; // 16 // 96 (values[0])
// 16 // 112 (values[1])
// 16 // 128 (values[2])
bool boolean; // 4 // 144
int integer; // 4 // 148
};
作为练习,尝试去自己计算一下偏移量,并和表格进行对比。
使用计算后的偏移量值,根据std140布局的规则,我们就能使用像是glBufferSubData的函数将变量数据按照偏移量填充进缓冲中了。
虽然std140布局不是最高效的布局,但它保证了内存布局在每个声明了这个Uniform块的程序中是一致的。
通过在Uniform块定义之前添加layout (std140)语句,我们告诉OpenGL这个Uniform块使用的是std140布局。
除此之外还可以选择两个布局,但它们都需要我们在填充缓冲之前先查询每个偏移量。
我们已经见过shared布局了,剩下的一个布局是packed。
当使用紧凑(Packed)布局时,是不能保证这个布局在每个程序中保持不变的(即非共享),因为它允许编译器去将uniform变量从Uniform块中优化掉,这在每个着色器中都可能是不同的。
2.3.2 使用Uniform缓冲
我们已经讨论了如何在着色器中定义Uniform块,并设定它们的内存布局了,但我们还没有讨论该如何使用它们。
首先,我们需要调用glGenBuffers,创建一个Uniform缓冲对象。
一旦我们有了一个缓冲对象,我们需要将它绑定到GL_UNIFORM_BUFFER目标,并调用glBufferData,分配足够的内存。
unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
glBindBuffer(GL_UNIFORM_BUFFER, 0);
现在,每当我们需要对缓冲更新或者插入数据,我们都会绑定到uboExampleBlock,并使用glBufferSubData来更新它的内存。
我们只需要更新这个Uniform缓冲一次,所有使用这个缓冲的着色器就都使用的是更新后的数据了。
但是,如何才能让OpenGL知道哪个Uniform缓冲对应的是哪个Uniform块呢?
在OpenGL上下文中,定义了一些绑定点(Binding Point),我们可以将一个Uniform缓冲链接至它。
在创建Uniform缓冲之后,我们将它绑定到其中一个绑定点上,并将着色器中的Uniform块绑定到相同的绑定点,把它们连接到一起。下面的这个图示展示了这个:
可以看到,我们可以绑定多个Uniform缓冲到不同的绑定点上。
因为着色器A和着色器B都有一个链接到绑定点0的Uniform块,它们的Uniform块将会共享相同的uniform数据,uboMatrices,前提条件是两个着色器都定义了相同的Matrices Uniform块。
为了将Uniform块绑定到一个特定的绑定点中,我们需要调用glUniformBlockBinding函数,它的第一个参数是一个程序对象,之后是一个Uniform块索引和链接到的绑定点。
Uniform块索引(Uniform Block Index)是着色器中已定义Uniform块的位置值索引。
这可以通过调用glGetUniformBlockIndex来获取,它接受一个程序对象和Uniform块的名称。我们可以用以下方式将图示中的Lights Uniform块链接到绑定点2:
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);
我们需要对每个着色器重复这一步骤。
- 从OpenGL 4.2版本起,你也可以添加一个布局标识符,显式地将Uniform块的绑定点储存在着色器中,这样就不用再调用 glGetUniformBlockIndex和glUniformBlockBinding了。下面的代码显式地设置了Lights Uniform块的绑定点。
layout(std140, binding = 2) uniform Lights { ... };
接下来,我们还需要绑定Uniform缓冲对象到相同的绑定点上,这可以使用glBindBufferBase或glBindBufferRange来完成。
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
glBindbufferBase需要一个目标,一个绑定点索引和一个Uniform缓冲对象作为它的参数。
这个函数将uboExampleBlock链接到绑定点2上,自此,绑定点的两端都链接上了。
也可以使用glBindBufferRange函数,它需要一个附加的偏移量和大小参数,这样子可以绑定Uniform缓冲的特定一部分到绑定点中。
通过使用glBindBufferRange函数,可以让多个不同的Uniform块绑定到同一个Uniform缓冲对象上。
现在,所有的东西都配置完毕了,我们可以开始向Uniform缓冲中添加数据了。
只要我们需要,就可以使用glBufferSubData函数,用一个字节数组添加所有的数据,或者更新缓冲的一部分。要想更新uniform变量boolean,我们可以用以下方式更新Uniform缓冲对象:
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL中的bool是4字节的,所以我们将它存为一个integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
同样的步骤也能应用到Uniform块中其它的uniform变量上,但需要使用不同的范围参数。
2.3.3 实现:一个简单的例子
所以,我们来展示一个真正使用Uniform缓冲对象的例子。
如果我们回头看看之前所有的代码例子,我们不断地在使用3个矩阵:投影、观察和模型矩阵。
在所有的这些矩阵中,只有模型矩阵会频繁变动。如果我们有多个着色器使用了这同一组矩阵,那么使用Uniform缓冲对象可能会更好。
我们会将投影和模型矩阵存储到一个叫做Matrices的Uniform块中。
我们不会将模型矩阵存在这里,因为模型矩阵在不同的着色器中会不断改变,所以使用Uniform缓冲对象并不会带来什么好处。
类似下面这个例子,我们会把观察和投影矩阵拼在一起,我们会用这种方式修改自己以前的所有顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
这里没什么特别的,除了我们现在使用的是一个std140布局的Uniform块。
首先,我们将顶点着色器的Uniform块设置为绑定点0。
注意我们需要对每个着色器都设置一遍,按照我们的例子来说,观察和投影矩阵没有修改过的是,vertexSource.vert,skyBoxReflection.vert(天空盒需要去除位移的部分,所以不包括skyBox.vert)
然后回到main中,我们看看用过的Shader
Shader* myShader = new Shader("vertexSource.vert", "fragmentSource.frag");
Shader* border = new Shader("vertexSource.vert", "Border.frag");
Shader* grass = new Shader("vertexSource.vert", "grass.frag");
Shader* glass = new Shader("vertexSource.vert", "glass.frag");
Shader* screenShader = new Shader("screen.vert", "screen.frag");
Shader* skyBox = new Shader("skyBox.vert", "skyBox.frag");
Shader* skyBoxReflection = new Shader("skyBoxReflection.vert", "skyBoxReflection.frag");
我们需要修改myShader,border(虽然功能已经被注释了),grass,glass,skyBoxReflection
首先,我们将这些着色器的Uniform块设置为绑定点0。
//设置观察矩阵和投影矩阵的uniform缓冲
unsigned int uniformBlockIndexmyShader = glGetUniformBlockIndex(myShader->ID, "Matrices");
unsigned int uniformBlockIndexborder = glGetUniformBlockIndex(border->ID, "Matrices");
unsigned int uniformBlockIndexgrass = glGetUniformBlockIndex(grass->ID, "Matrices");
unsigned int uniformBlockIndexglass = glGetUniformBlockIndex(glass->ID, "Matrices");
unsigned int uniformBlockIndexskyBoxReflection = glGetUniformBlockIndex(skyBoxReflection->ID, "Matrices");
glUniformBlockBinding(myShader->ID, uniformBlockIndexmyShader, 0);
glUniformBlockBinding(border->ID, uniformBlockIndexborder, 0);
glUniformBlockBinding(grass->ID, uniformBlockIndexgrass, 0);
glUniformBlockBinding(glass->ID, uniformBlockIndexglass, 0);
glUniformBlockBinding(skyBoxReflection->ID, uniformBlockIndexskyBoxReflection, 0);
接下来,我们创建Uniform缓冲对象本身,并将其绑定到绑定点0:
//创建Uniform缓冲对象本身,并将其绑定到绑定点0
unsigned int uboMatrices;
glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));
首先我们为缓冲分配了足够的内存,它等于glm::mat4大小的两倍。
GLM矩阵类型的大小直接对应于GLSL中的mat4。
接下来,我们将缓冲中的特定范围(在这里是整个缓冲)链接到绑定点0。
剩余的就是填充这个缓冲了。
如果我们将投影矩阵的视野(Field of View)值保持不变(所以摄像机就没有缩放了),我们只需要将其在程序中定义一次——这也意味着我们只需要将它插入到缓冲中一次。
因为我们已经为缓冲对象分配了足够的内存,我们可以使用glBufferSubData在进入渲染循环之前存储投影矩阵:
//提前设置好透视矩阵(也就是滚轮功能不能用了)
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projMat));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
上面我们将投影矩阵储存在Uniform缓冲的后半部分
在每次渲染迭代中绘制物体之前,我们会将观察矩阵更新到缓冲的前半部分:
while (!glfwWindowShouldClose(window))
{
......
//设置好uniform缓冲中的观察矩阵
viewMat = camera.GetViewMatrix();
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(viewMat));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
......
}
Uniform缓冲对象的部分就结束了。
每个包含了Matrices这个Uniform块的顶点着色器将会包含储存在uboMatrices中的数据。
我们可以把之前在渲染循环中设置观察和模型矩阵的部分都注释掉了
以上设置都没有出错的话,应该就能得到和以前一样的效果
这只是一个很简单的情景,我们可能会需要使用Uniform缓冲对象,但任何大型的渲染程序都可能同时激活有上百个着色器程序,这时候Uniform缓冲对象的优势就会很大地体现出来了。
Uniform缓冲对象比起独立的uniform有很多好处。
第一,一次设置很多uniform会比一个一个设置多个uniform要快很多。
第二,比起在多个着色器中修改同样的uniform,在Uniform缓冲中修改一次会更容易一些。
最后一个好处可能不会立即显现,如果使用Uniform缓冲对象的话,你可以在着色器中使用更多的uniform。
OpenGL限制了它能够处理的uniform数量,这可以通过GL_MAX_VERTEX_UNIFORM_COMPONENTS来查询。
当使用Uniform缓冲对象时,最大的数量会更高。
所以,当达到了uniform的最大数量时(比如再做骨骼动画(Skeletal Animation)的时候),总是可以选择使用Uniform缓冲对象。
2.3.3.1 代码文件
我们对于如何修改顶点着色器在上面的部分讲得很清楚了,设置一下layout (std140) uniform Matrices就可以
我们修改了一下main.cpp,虽然上面也讲了如何设置,不过有时候还是直观的代码更加清晰,如下
#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Shader.h"
#include "Camera.h"
#include "Material.h"
#include "LightDirectional.h"
#include "LightPoint.h"
#include "LightSpot.h"
#include "Mesh.h"
#include "Model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <map>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
//发光正方体的数据
#pragma region Model Data
float vertices[] = {
//position //normal //TexCoord
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
};
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
float grassVertices[] = {
//position //normal //TexCoord
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
};
glm::vec3 grassPosition[] = {
glm::vec3(-1.5f, 0.0f, 1.0f),
glm::vec3(1.5f, 0.0f, 1.0f),
};
glm::vec3 glassPosition[] = {
glm::vec3(-1.5f, 1.0f, 2.0f),
glm::vec3(-1.2f, 0.9f, 1.5f),
glm::vec3(0.5f, 1.5f, 1.0f),
};
float quadVertices[] = {
// positions // texCoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
float skyboxVertices[] = {
// positions
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f
};
//skyBoxReflection
float skyBoxReflectionVertices[] = {
//position //normal
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
};
#pragma endregion
#pragma region Camera Declare
//建立camera
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
Camera camera(cameraPos, cameraTarget, cameraUp);
#pragma endregion
#pragma region Light Declare
//position angle color
LightDirectional lightD (
glm::vec3(135.0f, 0,0),
glm::vec3(0.8f, 0.8f, 0.8f));
LightPoint lightP0 (glm::vec3(1.0f, 0,0),
glm::vec3(1.0f, 0, 0));
LightPoint lightP1 (glm::vec3(0, 1.0f, 0),
glm::vec3(0, 1.0f, 0));
LightPoint lightP2 (glm::vec3(0, 0, 1.0f),
glm::vec3(0, 0, 1.0f));
LightPoint lightP3 (glm::vec3(0.0f, 0.0f, -7.0f),
glm::vec3(1.0f, 1.0f, 1.0f));
LightSpot lightS (glm::vec3(0, 8.0f,0.0f),
glm::vec3(135.0f, 0, 0),
glm::vec3(0, 0,1.5f));
#pragma endregion
#pragma region Input Declare
//移动用
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
void processInput(GLFWwindow* window) {
//看是不是按下esc键 然后退出
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
//更流畅点的摄像机系统
if (deltaTime != 0) {
camera.cameraPosSpeed = 5 * deltaTime;
}
//camera前后左右根据镜头方向移动
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.PosUpdateForward();
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.PosUpdateBackward();
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.PosUpdateLeft();
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.PosUpdateRight();
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
camera.PosUpdateUp();
if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
camera.PosUpdateDown();
}
float lastX;
float lastY;
bool firstMouse = true;
//鼠标控制镜头方向
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
if (firstMouse == true)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float deltaX, deltaY;
float sensitivity = 0.05f;
deltaX = (xpos - lastX)*sensitivity;
deltaY = (ypos - lastY)*sensitivity;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(deltaX, deltaY);
};
//缩放
float fov = 45.0f;
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if (fov >= 1.0f && fov <= 45.0f)
fov -= yoffset;
if (fov <= 1.0f)
fov = 1.0f;
if (fov >= 45.0f)
fov = 45.0f;
}
#pragma endregion
//加载一般的图片
unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format) {
unsigned int TexBuffer;
glGenTextures(1, &TexBuffer);
glBindTexture(GL_TEXTURE_2D, TexBuffer);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// 加载并生成纹理
int width, height, nrChannel;
unsigned char *data = stbi_load(filename, &width, &height, &nrChannel, 0);
if (data) {
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
printf("Failed to load texture");
}
stbi_image_free(data);
return TexBuffer;
}
//加载天空盒图片
unsigned int loadCubemap(std::vector<std::string> faces)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++)
{
unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
);
stbi_image_free(data);
}
else
{
std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
stbi_image_free(data);
}
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
return textureID;
}
int main(int argc, char* argv[]) {
std::string exePath = argv[0];
#pragma region Open a Window
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//Open GLFW Window
GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL);
if(window == NULL)
{
printf("Open window failed.");
glfwTerminate();
return - 1;
}
glfwMakeContextCurrent(window);
//关闭鼠标显示
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//回调函数监听鼠标
glfwSetCursorPosCallback(window, mouse_callback);
//回调函数监听滚轮
glfwSetScrollCallback(window, scroll_callback);
//Init GLEW
glewExperimental = true;
if (glewInit() != GLEW_OK)
{
printf("Init GLEW failed.");
glfwTerminate();
return -1;
}
glViewport(0, 0, 800, 600);
//glEnable(GL_DEPTH_TEST);
//glDepthFunc(GL_LESS);
//glEnable(GL_STENCIL_TEST);
//glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
//glEnable(GL_BLEND);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//
//glEnable(GL_CULL_FACE);
#pragma endregion
#pragma region Init Shader Program
Shader* myShader = new Shader("vertexSource.vert", "fragmentSource.frag");
Shader* border = new Shader("vertexSource.vert", "Border.frag");
Shader* grass = new Shader("vertexSource.vert", "grass.frag");
Shader* glass = new Shader("vertexSource.vert", "glass.frag");
Shader* screenShader = new Shader("screen.vert", "screen.frag");
Shader* skyBox = new Shader("skyBox.vert", "skyBox.frag");
Shader* skyBoxReflection = new Shader("skyBoxReflection.vert", "skyBoxReflection.frag");
#pragma endregion
#pragma region FBO / RBO
unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// 生成fbo的纹理附件
unsigned int texColorBuffer;
glGenTextures(1, &texColorBuffer);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
// 将它附加到当前绑定的帧缓冲对象
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
// 生成fbo的rbo附件
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// 将它附加当前绑定的帧缓冲对象
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#pragma endregion
#pragma region Init Material
Material * myMaterial = new Material(myShader,
LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("matrix.jpg", GL_RGB, GL_RGB),
glm::vec3 (1.0f, 1.0f, 1.0f),
150.0f);
Material * Grass = new Material(grass,
LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA),
glm::vec3(1.0f, 1.0f, 1.0f),
150.0f);
Material * Glass = new Material(glass,
LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA),
LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA),
glm::vec3(1.0f, 1.0f, 1.0f),
150.0f);
std::vector<std::string> faces
{
"right.jpg",
"left.jpg",
"top.jpg",
"bottom.jpg",
"front.jpg",
"back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);
#pragma endregion
#pragma region Init and Load Models to VAO,VBO
Mesh cube(vertices);
Mesh grassMesh(grassVertices);
Mesh glassMesh(grassVertices);
Model model(exePath.substr(0, exePath.find_last_of('\\')) + "\\model\\nanosuit.obj");
// screen quad VAO
unsigned int quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
// skyBox VAO
unsigned int skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//skyBoxReflection VAO
unsigned int skyBoxReflectionVAO, skyBoxReflectionVBO;
glGenVertexArrays(1, &skyBoxReflectionVAO);
glGenBuffers(1, &skyBoxReflectionVBO);
glBindVertexArray(skyBoxReflectionVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyBoxReflectionVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyBoxReflectionVertices), &skyBoxReflectionVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
#pragma endregion
#pragma region Init and Load Textures
//坐标翻转
stbi_set_flip_vertically_on_load(true);
#pragma endregion
#pragma region Prepare MVP matrices
//model
glm::mat4 modelMat;
//view
glm::mat4 viewMat;
//projection
glm::mat4 projMat;
//设置观察矩阵和投影矩阵的uniform缓冲
unsigned int uniformBlockIndexmyShader = glGetUniformBlockIndex(myShader->ID, "Matrices");
unsigned int uniformBlockIndexborder = glGetUniformBlockIndex(border->ID, "Matrices");
unsigned int uniformBlockIndexgrass = glGetUniformBlockIndex(grass->ID, "Matrices");
unsigned int uniformBlockIndexglass = glGetUniformBlockIndex(glass->ID, "Matrices");
unsigned int uniformBlockIndexskyBoxReflection = glGetUniformBlockIndex(skyBoxReflection->ID, "Matrices");
glUniformBlockBinding(myShader->ID, uniformBlockIndexmyShader, 0);
glUniformBlockBinding(border->ID, uniformBlockIndexborder, 0);
glUniformBlockBinding(grass->ID, uniformBlockIndexgrass, 0);
glUniformBlockBinding(glass->ID, uniformBlockIndexglass, 0);
glUniformBlockBinding(skyBoxReflection->ID, uniformBlockIndexskyBoxReflection, 0);
//创建Uniform缓冲对象本身,并将其绑定到绑定点0
unsigned int uboMatrices;
glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));
//提前设置好透视矩阵(也就是滚轮功能不能用了)
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projMat));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
#pragma endregion
while (!glfwWindowShouldClose(window))
{
//Process Input
processInput(window);
//第一阶段处理,渲染到自己建立的fbo
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_CULL_FACE);
//Clear Screen
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//设置好uniform缓冲中的观察矩阵
viewMat = camera.GetViewMatrix();
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(viewMat));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
//方块 0代表模型
for (unsigned int i = 0; i < 11; i++)
{
if (i != 0) {
int k = i - 1;
//Set Model matrix
modelMat = glm::translate(glm::mat4(1.0f), cubePositions[k]);
float angle = 20.0f * (k);
//float angle = 20.0f * i + 50*glfwGetTime();
modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
}
else {
modelMat = glm::translate(glm::mat4(1.0f), { 0,-10,-5 });
}
//Set view matrix
//viewMat = camera.GetViewMatrix();
//Set projection matrix
//projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
//Set Material -> Shader Program
myShader->use();
//Set Material -> Uniforms
#pragma region Uniform
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glUniform3f(glGetUniformLocation(myShader->ID, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);
glUniform1f(glGetUniformLocation(myShader->ID, "time"), glfwGetTime());
glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 0.1f, 0.1f, 0.1f);
glUniform3f(glGetUniformLocation(myShader->ID, "lightD.color"), lightD.color.x, lightD.color.y, lightD.color.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightD.dirToLight"), lightD.direction.x, lightD.direction.y, lightD.direction.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP0.pos"), lightP0.position.x, lightP0.position.y, lightP0.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP0.color"), lightP0.color.x, lightP0.color.y, lightP0.color.z);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.constant"), lightP0.constant);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.linear"), lightP0.linear);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.quadratic"), lightP0.quadratic);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP1.pos"), lightP1.position.x, lightP1.position.y, lightP1.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP1.color"), lightP1.color.x, lightP1.color.y, lightP1.color.z);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.constant"), lightP1.constant);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.linear"), lightP1.linear);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.quadratic"), lightP1.quadratic);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP2.pos"), lightP2.position.x, lightP2.position.y, lightP2.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP2.color"), lightP2.color.x, lightP2.color.y, lightP2.color.z);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.constant"), lightP2.constant);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.linear"), lightP2.linear);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.quadratic"), lightP2.quadratic);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP3.pos"), lightP3.position.x, lightP3.position.y, lightP3.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightP3.color"), lightP3.color.x, lightP3.color.y, lightP3.color.z);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.constant"), lightP3.constant);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.linear"), lightP3.linear);
glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.quadratic"), lightP3.quadratic);
glUniform3f(glGetUniformLocation(myShader->ID, "lightS.pos"), lightS.position.x, lightS.position.y, lightS.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightS.color"), lightS.color.x, lightS.color.y, lightS.color.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightS.dirToLight"), lightS.direction.x, lightS.direction.y, lightS.direction.z);
glUniform1f(glGetUniformLocation(myShader->ID, "lightS.constant"), lightS.constant);
glUniform1f(glGetUniformLocation(myShader->ID, "lightS.linear"), lightS.linear);
glUniform1f(glGetUniformLocation(myShader->ID, "lightS.quadratic"), lightS.quadratic);
glUniform1f(glGetUniformLocation(myShader->ID, "lightS.cosPhyInner"), lightS.cosPhyInner);
glUniform1f(glGetUniformLocation(myShader->ID, "lightS.cosPhyOuter"), lightS.cosPhyOuter);
#pragma endregion
myMaterial->shader->SetUniform3f("material.ambient", myMaterial->ambient);
myMaterial->shader->SetUniform1f("material.shininess", myMaterial->shininess);
if (i == 0) {
glStencilMask(0x00); // 记得保证我们在绘制机器人的时候不会更新模板缓冲
glUniform1f(glGetUniformLocation(myShader->ID, "What"), 1);
//skyBoxReflection->use();
//model.Draw(skyBoxReflection);
model.Draw(myShader);
//折射机器人
skyBoxReflection->use();
modelMat = glm::translate(glm::mat4(1.0f), { 0,-10,-10 });
//Set view matrix
//viewMat = camera.GetViewMatrix();
//Set projection matrix
//projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
model.Draw(skyBoxReflection);
glDisable(GL_CULL_FACE);
//画草
for (unsigned int i = 0; i < 2; i++)
{
//Set Model matrix
modelMat = glm::translate(glm::mat4(1.0f), grassPosition[i]);
modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));
//Set view matrix
//viewMat = camera.GetViewMatrix();
//Set projection matrix
//projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
//Set Material -> Shader Program
grass->use();
glUniformMatrix4fv(glGetUniformLocation(grass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(grass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(grass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
grassMesh.DrawSliceArray(Grass->shader, Grass->diffuse, Grass->specular, Grass->emission);
}
glEnable(GL_CULL_FACE);
}
else {
//模板测试绘制边框
////方块与方块边框
//glStencilFunc(GL_ALWAYS, 1, 0xFF); // 更新模板缓冲函数,所有的片段都要写入模板
//glStencilMask(0xFF); // 启用模板缓冲写入
////正常绘制十个正方体,而后记录模板值
glUniform1f(glGetUniformLocation(myShader->ID, "What"), 0);
cube.DrawArray(myMaterial->shader, myMaterial->diffuse, myMaterial->specular, myMaterial->emission);
////现在模板缓冲在箱子被绘制的地方都更新为1了,我们将要绘制放大的箱子,也就是绘制边框
//glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
//glStencilMask(0x00); // 禁止模板缓冲的写入
//
//
//
////glDisable(GL_DEPTH_TEST);
//border->use();
//
////Set Model matrix
//modelMat = glm::translate(glm::mat4(1.0f), cubePositions[i-1]);
//float angle = 20.0f * (i-1);
//modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
//modelMat = glm::scale(modelMat, glm::vec3(1.2, 1.2, 1.2));
//glUniformMatrix4fv(glGetUniformLocation(border->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(border->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(border->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
//
////因为之前设置了GL_NOTEQUAL,它会保证我们只绘制箱子上模板值不为1的部分
//cube.DrawArray(border,1,1,1);
//glStencilMask(0xFF);
// //glEnable(GL_DEPTH_TEST);
}
}
glDisable(GL_CULL_FACE);
//绘制反射盒子
skyBoxReflection->use();
modelMat = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 5.0f));
//Set view matrix
//viewMat = camera.GetViewMatrix();
//Set projection matrix
//projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(skyBoxReflection->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glUniform3f(glGetUniformLocation(skyBoxReflection->ID, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);
glBindVertexArray(skyBoxReflectionVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
//最后(透明物体之前)画天空盒
glDepthMask(GL_FALSE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
skyBox->use();
//Set view matrix
viewMat = glm::mat4(glm::mat3(camera.GetViewMatrix()));
//Set projection matrix
projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyBox->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(skyBox->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
//画玻璃
//利用map排序
std::map<float, glm::vec3 > sorted;
int length = sizeof(glassPosition) / sizeof(glassPosition[0]);
for (unsigned int i = 0; i < length; i++)
{
float distance = glm::length(camera.Position - glassPosition[i]);
sorted[distance] = glassPosition[i];
}
//在渲染的时候,我们将以逆序(从远到近)从map中获取值
//使用了map的一个反向迭代器(Reverse Iterator),反向遍历其中的条目,second会得到value,讲每个窗户四边形位移到对应的窗户位置上。
for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
{
//Set Model matrix
modelMat = glm::translate(glm::mat4(1.0f), it->second);
modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));
//Set view matrix
//viewMat = camera.GetViewMatrix();
//Set projection matrix
//projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
//Set Material -> Shader Program
glass->use();
glUniformMatrix4fv(glGetUniformLocation(glass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
//glUniformMatrix4fv(glGetUniformLocation(glass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
//glUniformMatrix4fv(glGetUniformLocation(glass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
grassMesh.DrawSliceArray(Glass->shader, Glass->diffuse, Glass->specular, Glass->emission);
}
glEnable(GL_CULL_FACE);
//第二阶段 渲染到默认的帧缓冲 绘制四边形
glBindFramebuffer(GL_FRAMEBUFFER, 0); // 返回默认
//线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glClearColor(1.0f, 1.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
screenShader->use();
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, texColorBuffer); // use the color attachment texture as the texture of the quad plane
glDisable(GL_DEPTH_TEST);
glUniform1i(glGetUniformLocation(screenShader->ID, "screenTexture"), 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
//Clean up prepare for next render loop
glfwSwapBuffers(window);
glfwPollEvents();
//Recording the time
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
}
//Exit program
glfwTerminate();
return 0;
}