LearnOpenGL->高级OpenGL->帧缓冲

帧缓冲

到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。有了我们自己的帧缓冲,我们就能够有更多方式来渲染了。

你可能不能很快理解帧缓冲的应用,但渲染你的场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果。首先我们会讨论它是如何工作的,之后我们将来实现这些炫酷的后期处理效果。

创建一个帧缓冲

和OpenGL中的其它对象一样,我们会使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(Framebuffer Object, FBO):

unsignedint fbo;
glGenFramebuffers(1, &fbo);//创建一个帧缓冲对象;

这种创建和使用对象的方式我们已经见过很多次了,所以它的使用函数也和其它的对象类似。首先我们创建一个帧缓冲对象,将它绑定为激活的(Active)帧缓冲,做一些操作,之后解绑帧缓冲。我们使用glBindFramebuffer来绑定帧缓冲。

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
//在后面的渲染循环中,我们只要进行了一次对帧缓冲的绑定,则我们会启用当前的帧缓冲而不使用原来的默认缓冲;

在绑定到GL_FRAMEBUFFER目标之后,所有的读取写入帧缓冲的操作将会影响当前绑定的帧缓冲。我们也可以使用GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标。绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中,而绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上。

但是我们现在还不能使用我们的帧缓冲,因为它还不完整(Complete),一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。

  • 至少有一个颜色附件(Attachment)。

  • 所有的附件都必须是完整的(保留了内存)。

  • 每个缓冲都应该有相同的样本数。

我们需要为帧缓冲创建一些附件,并将附件附加到帧缓冲上。在完成所有的条件之后,我们可以以GL_FRAMEBUFFER为参数调用glCheckFramebufferStatus,检查帧缓冲是否完整。它将会检测当前绑定的帧缓冲,并返回规范中这些值的其中之一。如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout<<......<<std::endl;//打印错误信息;

之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。由于我们的帧缓冲不是默认帧缓冲,渲染指令将不会对窗口的视觉输出有任何影响。出于这个原因,渲染到一个不同的帧缓冲被叫做离屏渲染(Off-screen Rendering)要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到0

glBindFramebuffer(GL_FRAMEBUFFER, 0);//解绑,相当于将GL_FRAMEBUFFER对象绑定到0;

在完成所有的帧缓冲操作之后,不要忘记删除这个帧缓冲对象:

glDeleteFramebuffers(1, &fbo);

在完整性检查执行之前,我们需要给帧缓冲附加一个附件附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象(Renderbuffer Object)

纹理附件

当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。

为帧缓冲创建一个纹理和创建一个普通的纹理差不多:

unsignedint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

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);

主要的区别就是,我们将维度设置为了屏幕大小(尽管这不是必须的),并且我们给纹理的data参数传递了NULL。对于这个纹理,我们仅仅分配了内存而没有填充它。填充这个纹理将会在我们渲染到帧缓冲之后来进行。同样注意我们并不关心环绕方式或多级渐远纹理,我们在大多数情况下都不会需要它们。

如果你想将你的屏幕渲染到一个更小或更大的纹理上,你需要(在渲染到你的帧缓冲之前)再次调用glViewport,使用纹理的新维度作为参数,否则只有一小部分的纹理或屏幕会被渲染到这个纹理上。

现在我们已经创建好一个纹理了,要做的最后一件事就是将它附加到帧缓冲上了:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

glFrameBufferTexture2D有以下的参数:

  • target:帧缓冲的目标(绘制、读取或者两者皆有)

  • attachment:我们想要附加的附件类型。当前我们正在附加一个颜色附件。注意最后的0意味着我们可以附加多个颜色附件。我们将在之后的教程中提到。

  • textarget:你希望附加的纹理类型

  • texture:要附加的纹理本身

  • level:多级渐远纹理的级别。我们将它保留为0。

除了颜色附件之外,我们还可以附加一个深度和模板缓冲纹理到帧缓冲对象中。要附加深度缓冲的话,我们将附件类型设置为GL_DEPTH_ATTACHMENT。注意纹理的格式(Format)和内部格式(Internalformat)类型将变为GL_DEPTH_COMPONENT,来反映深度缓冲的储存格式。要附加模板缓冲的话,你要将第二个参数设置为GL_STENCIL_ATTACHMENT并将纹理的格式设定为GL_STENCIL_INDEX

也可以将深度缓冲和模板缓冲附加为一个单独的纹【我们使用这个】。纹理的每32位数值将包含24位的深度信息和8位的模板信息。要将深度和模板缓冲附加为一个纹理的话,我们使用GL_DEPTH_STENCIL_ATTACHMENT类型,并配置纹理的格式,让它包含合并的深度和模板值。将一个深度和模板缓冲附加为一个纹理到帧缓冲的例子可以在下面找到:

glTexImage2D(
  GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, 
  GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

渲染缓冲对象附件

渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中,作为一个可用的帧缓冲附件类型的,所以在过去纹理是唯一可用的附件。和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。渲染缓冲对象对这种操作非常完美。

创建一个渲染缓冲对象的代码和帧缓冲的代码很类似:

unsignedint rbo;
glGenRenderbuffers(1, &rbo);

类似,我们需要绑定这个渲染缓冲对象,让之后所有的渲染缓冲操作影响当前的rbo

glBindRenderbuffer(GL_RENDERBUFFER, rbo);
//目标缓存为:GL_RENDERBUFFER;

由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。我们需要深度和模板值用于测试,但不需要对它们进行采样,所以渲染缓冲对象非常适合它们。当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。

创建一个深度和模板渲染缓冲对象可以通过调用glRenderbufferStorage函数来完成:

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

创建一个渲染缓冲对象和纹理对象类似,不同的是这个对象是专门被设计作为帧缓冲附件使用的,而不是纹理那样的通用数据缓冲(General Purpose Data Buffer)。这里我们选择GL_DEPTH24_STENCIL8作为内部格式,它封装了24位的深度和8位的模板缓冲。

最后一件事就是附加这个渲染缓冲对象:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
//将渲染缓冲对象附加到帧缓冲上;

渲染缓冲对象能为你的帧缓冲对象提供一些优化,但知道什么时候使用渲染缓冲对象,什么时候使用纹理是很重要的。通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。

渲染到纹理

我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。这为什么很有用呢?我们会在下一部分中知道原因。

首先要创建一个帧缓冲对象,并绑定它,这些都很直观:

unsignedint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

接下来我们需要创建一个纹理图像,我们将它作为一个颜色附件附加到帧缓冲上。我们将纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据:

// 生成纹理unsignedint 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);  

我们还希望OpenGL能够进行深度测试(如果你需要的话还有模板测试),所以我们还需要添加一个深度(和模板)附件到帧缓冲中。由于我们只希望采样颜色缓冲,而不是其它的缓冲,我们可以为它们创建一个渲染缓冲对象。还记得当我们不需要采样缓冲的时候,渲染缓冲对象是更好的选择吗?

创建一个渲染缓冲对象不是非常复杂。我们需要记住的唯一事情是,我们将它创建为一个深度模板附件渲染缓冲对象。我们将它的内部格式设置为GL_DEPTH24_STENCIL8,对我们来说这个精度已经足够了。

unsignedint 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);
//判断完成之后就进行解绑;

记得要解绑帧缓冲,保证我们不会不小心渲染到错误的帧缓冲上。

现在这个帧缓冲就完整了,我们只需要绑定这个帧缓冲对象,让渲染到帧缓冲的缓冲中而不是默认的帧缓冲中。之后的渲染指令将会影响当前绑定的帧缓冲所有的深度和模板操作都会从当前绑定的帧缓冲的深度和模板附件中(如果有的话)读取

所以,要想绘制场景到一个纹理上,我们需要采取以下的步骤:(重点重点!!!;)

-->>渲染场景前:

  1. 将新的帧缓冲绑定为激活的帧缓冲,和往常一样渲染场景

当我们绑定这一个缓冲之后,我们也就绑定了它的各种包括颜色等的附件,这个时候我们渲染场景,就会把每一帧的场景绘制到绑定帧缓冲的纹理图像上面,而这个纹理图像会以颜色附件的形式附加到这一个新的帧缓冲里面;【这里我们要以颜色附件的形式是因为一个完整的帧缓冲会包括至少一个的颜色附件】;

-->>渲染场景后:

2.绑定默认的帧缓冲

3.绘制一个横跨整个屏幕的四边形,将帧缓冲的颜色缓冲作为它的纹理。

为了绘制这个四边形,我们将会新创建一套简单的着色器。我们将不会包含任何花哨的矩阵变换,因为我们提供的是标准化设备坐标的顶点坐标,所以我们可以直接将它们设定为顶点着色器的输出。下面我会利用导入模型以及光照的一个场景来进行解析-->>

顶点着色器是这样的:

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

voidmain(){
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 
    TexCoords = aTexCoords;
}

片段着色器中,我们做的唯一一件事就是从纹理中采样:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

voidmain(){ 
    FragColor = texture(screenTexture, TexCoords);
}

接着就靠你来为屏幕四边形创建并配置一个VAO了。帧缓冲的一个渲染迭代将会有以下的结构:

// 第一处理阶段(Pass)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 我们现在不使用模板缓冲
glEnable(GL_DEPTH_TEST);
DrawScene();    

// 第二处理阶段
glBindFramebuffer(GL_FRAMEBUFFER, 0); // 返回默认
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
glClear(GL_COLOR_BUFFER_BIT);

screenShader.use();  
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);  

要注意一些事情。1,由于我们使用的每个帧缓冲都有它自己一套缓冲,我们希望设置合适的位,调用glClear,清除这些缓冲。2,当绘制四边形时,我们将禁用深度测试,因为我们是在绘制一个简单的四边形,并不需要关系深度测试。3.在绘制普通场景的时候我们将会重新启用深度测试。

输出结果:

这里我不用前面的纳米模型,加上这一句代码后,是它的线框模式:

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//放在渲染操作前;

和文档的输出结果一样!!;

下面是一些后处理操作,我们会创造一些其他效果:

反相

我们现在能够访问渲染输出的每个颜色,所以在片段着色器中返回这些颜色的反相(Inversion)并不是很难。我们将会从屏幕纹理中取颜色值,然后用1.0减去它,对它进行反相

voidmain(){
    FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

结果如下:

有点好玩;

灰度

灰度是移除场景中除了黑白灰以外所有的颜色,让整个图像灰度化(Grayscale)。很简单的实现方式是,取所有的颜色分量,将它们平均化

void main(){
    FragColor = texture(screenTexture, TexCoords);
    float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;//求分量的平均;
    FragColor = vec4(average, average, average, 1.0);//输出;
}

这已经能创造很好的结果了,但人眼会对绿色更加敏感一些,而对蓝色不那么敏感,所以为了获取物理上更精确的效果,我们需要使用加权的(Weighted)通道

void main(){
    FragColor = texture(screenTexture, TexCoords);
    float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
    FragColor = vec4(average, average, average, 1.0);
}

结果如下:

灰度很明显!!;

核效果

在一个纹理图像上做后期处理的另外一个好处是,我们可以从纹理的其它地方采样颜色值。比如说我们可以在当前纹理坐标的周围取一小块区域,对当前纹理值周围的多个纹理值进行采样。我们可以结合它们创建出很有意思的效果。

核(Kernel)(或卷积矩阵(Convolution Matrix))是一个类矩阵的数值数组,它的中心为当前的像素,它会用它的核值乘以周围的像素值,并将结果相加变成一个值。所以,基本上我们是在对当前像素周围的纹理坐标添加一个小的偏移量,并根据核将结果合并。下面是核的一个例子:

这个核取了8个周围像素值,将它们乘以2,而把当前的像素乘以-15。这个核的例子将周围的像素乘上了一个权重,并将当前像素乘以一个比较大的负权重来平衡结果。

你在网上找到的大部分核将所有的权重加起来之后都应该会等于1,如果它们加起来不等于1,这就意味着最终的纹理颜色将会比原纹理值更亮或者更暗了。

核是后期处理一个非常有用的工具,它们使用和实验起来都很简单,网上也能找到很多例子。我们需要稍微修改一下片段着色器,让它能够支持核。我们假设使用的核都是3x3核(实际上大部分核都是):

锐化

constfloat offset = 1.0 / 300.0;  

void main(){
    vec2 offsets[9] = vec2[](
        vec2(-offset,  offset), // 左上
        vec2( 0.0f,    offset), // 正上
        vec2( offset,  offset), // 右上
        vec2(-offset,  0.0f),   // 左
        vec2( 0.0f,    0.0f),   // 中
        vec2( offset,  0.0f),   // 右
        vec2(-offset, -offset), // 左下
        vec2( 0.0f,   -offset), // 正下
        vec2( offset, -offset)  // 右下
    );

    float kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );

    vec3 sampleTex[9];
    for(int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    FragColor = vec4(col, 1.0);
}

有几个要注意的点:

1.首先是vec2 offsets[9] = vec2[](
        vec2(-offset,  offset), // 左上
        vec2( 0.0f,    offset), // 正上
        vec2( offset,  offset), // 右上
        vec2(-offset,  0.0f),   // 左
        vec2( 0.0f,    0.0f),   // 中
        vec2( offset,  0.0f),   // 右
        vec2(-offset, -offset), // 左下
        vec2( 0.0f,   -offset), // 正下
        vec2( offset, -offset)  // 右下
    );的书写方式;

在片段着色器里面构建一个vec2数组:

vec2 offset[index] = vec2[]();//注意后面是用括号而不是{}来进行数据的存储;

!!!!核操作的主要方式:设定向对于中间位置向四周一共九个的偏移量,并对处于目标纹理像素的周边偏移量个单位的像素进行纹理采样,共九个的颜色值分别乘以他们各种对应的核并相加,最终会得到一个锐化的效果:

模糊

创建模糊(Blur)效果的核是这样的:

由于所有值的和是16,所以直接返回合并的采样颜色将产生非常亮的颜色,所以我们需要将核的每个值都除以16。最终的核数组将会是:

float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);

注意点同上!!!;

代码其他部分不变;

明显模糊了不少!!;

边缘检测

边缘检测同样仅需要改变一下核,他的核如下:

注意周边均为8,这个核会将重点的高亮集中在边缘,其他代码同上,结果如下:

很炫酷是吧!!!;

这一个是三种效果合并的效果;

代码

main.cpp

#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include <iostream>
#include<F:\OpenGl\练习1.1\3.3.shader_class\shader s.h>//引入shader类头文件;
//以下三行为glm的头文件代码;
#include <F:\OpenGl\glm\glm-master\glm\glm.hpp>
#include <F:\OpenGl\glm\glm-master\glm\gtc\matrix_transform.hpp>
#include <F:\OpenGl\glm\glm-master\glm\gtc\type_ptr.hpp>

//#define STB_IMAGE_IMPLEMENTATION
#include <F:/OpenGl/stb_image.h/stb-master/stb_image.h>//这两行代码加入了stb_image库;

#include"Model s.h"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_back(GLFWwindow* window, double xoffset, double yoffset);
unsigned int loadCubemap(std::vector<std::string> faces);//传入六张图片,返回一个纹理ID;

//三个调整摄像机位置的全局变量;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

float deltatime = 0.0f;//上一帧与这一帧的时间差;
float lastime = 0.0f;//上一帧的时间;

//用来存储上一帧鼠标的位置!,设置为屏幕中心;
float lastX = 400.0;
float lastY = 300.0;

//仰俯角和偏航角;
float pitch = 0.0f;
float yaw = -90.0f;//从下往上;

float fov = 45.0f;//视域角;

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);//声明一个光源,表示光源在空间中的位置;


int main()
{

    //先进行初始化glfw;
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本设置为3;
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本设置为3;
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_SAMPLES, 1024);

    GLFWwindow* window = glfwCreateWindow(800, 600, "MY OPENGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Fail to create a window" << std::endl;
        glfwTerminate();//释放资源;
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    //创建完告诉将上下文设置为进程上下文;

    //以下两步用于摄像机操作中的设置,由于是窗口的操作,因此放在此处!!;
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//告诉GLFW隐藏光标并捕捉他;
    glfwSetCursorPosCallback(window, mouse_callback);//此句代码当发生鼠标移动时,就会调用mouse_callback函数改变两个欧拉角,
    //进而改变cameraFront方向向量,进而可以实现3D旋转;
    //还要对视域角fov做出变化,可以进行放大缩小;
    glfwSetScrollCallback(window, scroll_back);//当滚动鼠标滚轮的时候,我们就可以通过调用该函数来改变fov,进而改变透视投影矩阵,
    //以此来进一步形成放大和缩小!!;


    //对glad进行初始化;
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Fail to initnite glad" << std::endl;
        return -1;
    }
    //引入着色器类,着色器被封装到了class Shader里面;
    //这里要创建两个着色器程序,分别用在物体和光源上面;

    //
    // light positions
    float vertices[] = {
        -0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f, -0.5f,  0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,

        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,

         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,

        -0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f,  0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
    };

    //光源
    unsigned int lightVAO;
    unsigned int lightVBO;
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);

    glGenBuffers(1, &lightVBO);
    glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

//------------------------------------------------------------------------------
    float quadVertices[] = { 
        -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
    };//平面四边形的顶点;

    //帧缓冲;
    //1.先创建一个帧缓冲;
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    //2.初始化一个纹理图像,把它作为颜色附件附加到帧缓冲上面;我们将纹理的维度设置为屏幕高宽,并不进行初始化;
    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);
    //3.将纹理附加到当前的帧缓冲对象上;用于存储颜色信息;
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorbuffer, 0);//注意弄清五个参数的含义;
    //4.创建一个渲染缓冲对象;
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    //为深度和模板缓冲创建一个对象;
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
    //附加这个渲染缓冲到帧缓冲上;
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,rbo);//将渲染缓冲对象附加到帧缓冲上面去;
    //要进行检查,如果帧缓冲frame不完整,则打印错误信息!!!;
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "ERROR:Frame is not complete!" << std::endl;
    //最后记得要解绑;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);//解绑;
//--------------------------------------------------------------
    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)));
    //用于绘制平面四边形;

//------------------------------------------------------------------------------------------------------------------------
    //天空盒;
    //天空盒立方体顶点数据;
    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
    };

    //创建天空盒的VAO,VBO;
    //skybox;VAO,VBO;
    //此处创建一个新的VAO,VBO用于存储顶点数据;
    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);

    //ready to be change;
    std::vector<std::string> faces
    {
        "F:/OpenGl/textures/skybox.texture/skybox/right.jpg",
    "F:/OpenGl/textures/skybox.texture/skybox/left.jpg",
    "F:/OpenGl/textures/skybox.texture/skybox/top.jpg",
    "F:/OpenGl/textures/skybox.texture/skybox/bottom.jpg",
    "F:/OpenGl/textures/skybox.texture/skybox/front.jpg",
    "F:/OpenGl/textures/skybox.texture/skybox/back.jpg"
    };
    unsigned int cubemapTexture = loadCubemap(faces);//获取到天空盒的ID;
    //下面要传入两个天空盒的着色器进行着色绘制;

    Shader lightshader("3.2.shader2.vs", "3.2.shader2.fs");
    Shader lightCubeshader("3.2.shader.light.vs", "3.2.shader.light.fs");//用于光源的着色器程序;//后面会绘制两个光源;
    Shader skyboxshader("4.4.sky.shader.vs", "4.4.sky.shader.fs");//用于绘制天空盒的着色器程序;
    //----创建一个新的着色器程序;
    Shader screenShader("4.6.shader.vs.screen", "4.6.shader.fs.screen");//用于绘制屏幕三角形的一个着色器程序;
    //--

    skyboxshader.useProgram();
    skyboxshader.setInt("skybox", 0);
    //---------------------------
    lightshader.useProgram();
    lightshader.setInt("material.skybox", 3);
    //---------------------------

    //模型导入:
    Model OurModel("F:/OpenGl/Model/Model6/FBX/Earth Dragon Egg.obj");//F:/OpenGl/Model/Model1.2nami2/nanosuit.obj


    //光源位置;
    glm::vec3 pointLightPositions[] = {
        glm::vec3(-2.0f,6.0f,1.0f),
        glm::vec3(2.0f,-3.0f,0.0f)
    };
    
    //glEnable(GL_DEPTH_TEST);//启用深度测试;

    //准备引擎:
    while (!glfwWindowShouldClose(window))
    {
        float currentFrame = static_cast<float>(glfwGetTime());
        deltatime = currentFrame - lastime;
        lastime = currentFrame;

        processInput(window);
//-----------------------------此处要进行修改--------------------------------------
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);//最先要进行绑定,此时会把新的帧缓冲设定为当前的帧缓冲;
        glEnable(GL_DEPTH_TEST);//启用深度测试;
        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //---结束之后会进行场景的绘制,会将场景对应屏幕的每一个像素的颜色存储在帧缓冲的纹理附件中;

        // don't forget to enable shader before setting uniforms
        lightshader.useProgram();//启用着色器;
        lightshader.setFloat("material.shininess", 32.0f);//设置material中的选项;

        //人眼设置;//这里设置为摄像机的位置;
        lightshader.setVec3("viewPos", cameraPos);
        //定向光参数设置;
        lightshader.setVec3("dirlight.position", glm::vec3(-2.0f, -0.3f, -1.0f));
        lightshader.setVec3("dirlight.ambient", glm::vec3(0.2f, 0.2f, 0.2f));
        lightshader.setVec3("dirlight.specular", glm::vec3(1.0f, 1.0f, 1.0f));
        lightshader.setVec3("dirlight.diffuse", glm::vec3(0.5f));

        //点光源参数设置;下面有两个电光源需要进行设置;
        //point1;
        lightshader.setVec3("pointlights[0].position", pointLightPositions[0]);
        lightshader.setVec3("pointlights[0].ambient", glm::vec3(0.2f, 0.2f, 0.2f));
        lightshader.setVec3("pointlights[0].specular", glm::vec3(50.0f, 50.0f, 50.0f));
        lightshader.setVec3("pointlights[0].diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
        lightshader.setFloat("pointlights[0].constant", 1.0f);
        lightshader.setFloat("pointlights[0].linear", 0.09f);
        lightshader.setFloat("pointlights[0].quadratic", 0.032f);
        //point2;
        lightshader.setVec3("pointlights[1].position", pointLightPositions[1]);
        lightshader.setVec3("pointlights[1].ambient", glm::vec3(0.2f, 0.2f, 0.2f));
        lightshader.setVec3("pointlights[1].specular", glm::vec3(10.0f, 10.0f, 10.0f));
        lightshader.setVec3("pointlights[1].diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
        lightshader.setFloat("pointlights[1].constant", 1.0f);
        lightshader.setFloat("pointlights[1].linear", 0.09f);
        lightshader.setFloat("pointlights[1].quadratic", 0.032f);

        //聚光参数设置;
        lightshader.setVec3("spotlight.position", cameraPos);
        lightshader.setVec3("spotlight.direction", cameraFront);
        lightshader.setVec3("spotlight.ambient", glm::vec3(0.2f, 0.2f, 0.2f));
        lightshader.setVec3("spotlight.specular", glm::vec3(1.0f, 1.0f, 1.0f));
        lightshader.setVec3("spotlight.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
        lightshader.setFloat("spotlight.constant", 1.0f);
        lightshader.setFloat("spotlight.linear", 0.09f);
        lightshader.setFloat("spotlight.quadratic", 0.032f);
        lightshader.setFloat("spotlight.outOff", glm::radians(12.5f));
        lightshader.setFloat("spotlight.outerCutOff", glm::radians(17.5f));

        // view/projection transformations
        glm::mat4 projection = glm::perspective(glm::radians(fov), (float)800 / (float)600, 0.1f, 100.0f);
        glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
        lightshader.setMat4("projection", projection);
        lightshader.setMat4("view", view);


        // render the loaded model
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
        float tmptime = (glfwGetTime());
        model = glm::rotate(model, glm::radians(20.0f)*tmptime, glm::vec3(0.0f, 1.0f, 0.0f));
        model = glm::scale(model, glm::vec3(6.0f, 6.0f, 6.0f));    // it's a bit too big for our scene, so scale it down
        lightshader.setMat4("model", model);
        //------------------
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        //------------------
        OurModel.Draw(lightshader);//调用Draw进行绘制相关操作;


        //下面是对灯源的操作,会在场景中画出两个灯光位置,灯光均为白色;
        lightCubeshader.useProgram();//启用另外一个着色器;
        glBindVertexArray(lightVAO);
        lightCubeshader.setMat4("view", view);
        lightCubeshader.setMat4("projection", projection);
        for (unsigned int i = 0; i < 2; i++)
        {
            model = glm::translate(glm::mat4(1.0f), pointLightPositions[i]);
            model = glm::rotate(model, glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));
            lightCubeshader.setMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }


        // 最后画天空盒
        //更改深度函数,以便深度测试在值等于深度缓冲区的内容时通过
        //注意此处深度测试是重点!!!!;
        glDepthFunc(GL_LEQUAL);
        skyboxshader.useProgram();
        view = glm::mat4(glm::mat3(view)); //从视图矩阵中删除平移
        skyboxshader.setMat4("view", view);
        skyboxshader.setMat4("projection", projection);
        // 天空盒
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS); // 将深度功能设置回默认值
    //此处场景的基本信息绘制结束,下面会进行修改,将缓冲重新设置为默认缓冲;
        glBindFramebuffer(GL_FRAMEBUFFER, 0);//解绑,也就相当于回到默认的帧缓冲;
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//重新进行一次屏幕的清除;
        glClear(GL_COLOR_BUFFER_BIT);//这一步仅清除了上一帧的颜色缓冲;
    //下面是对平面三角形的绘制,绑定framebuffer中的纹理texColorbuffer,这一步会使得这一帧的纹理绘制到占满屏幕的这一个三角形上!!;
    //需要启用一个新的着色器程序;这里我们命名为screenShader();
        screenShader.useProgram();//不要忘了要先启动着色器程序;
        glDisable(GL_DEPTH_TEST);//禁用深度测试;
        glBindVertexArray(quadVAO);//绑定纹理的三角形的VAO;
        glBindTexture(GL_TEXTURE_2D, texColorbuffer);//绑定位于新的帧缓冲的颜色附件中的纹理到三角形上;
        screenShader.setInt("screenTexture", 0);//绑定纹理到已经激活的纹理单元0上,才可以传入到着色器程序中;(这一步也可以放在循环外面;)
        glDrawArrays(GL_TRIANGLES, 0, 6);//进行屏幕三角形的绘制,

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//如果按下的键为回车键;
        glfwSetWindowShouldClose(window, true);
    float cameraSpeed = 10.0f * deltatime;//移动速度;
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraUp * cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraUp * cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
}

bool firstMouse = true;

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    //计算鼠标距上一帧的偏移量。
        //把偏移量添加到摄像机的俯仰角和偏航角中。
        //对偏航角和俯仰角进行最大和最小值的限制。
        //计算方向向量。
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;//否则每一次都会进行循环;
    }
    //1.计算鼠标距上一帧的偏移量。
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;//更新存储的上一帧的值;
    float sensitivity = 0.1f;//设置灵敏度;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    //2.把偏移量添加到摄像机的俯仰角和偏航角中。
    pitch = pitch + yoffset;
    yaw = yaw + xoffset;

    //3.对偏航角和俯仰角进行最大和最小值的限制

    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;
    //计算方向向量。
    glm::vec3 direction;
    direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
    direction.y = sin(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
    cameraFront = glm::normalize(direction);
}
void scroll_back(GLFWwindow* window, double xoffset, double yoffset)
{
    //我们要把fov限制在1.0到45.0之间!!;
    if (fov >= 1.0f && fov <= 45.0f)
    {
        fov -= yoffset;
    }
    if (fov >= 45.0f)
    {
        fov = 45.0f;
    }
    if (fov <= 1.0f)
    {
        fov = 1.0f;
    }
}

//skybox天空盒的生成函数;//注意返回的是skybox的ID;
unsigned int loadCubemap(std::vector<std::string> faces)//这里传入的是一个包含了六个面的数组;
{
    unsigned int textureID;//一个纹理ID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);//此处要绑定的对象是GL_TEXTURE_CUBE_MAP;注意已经不是GL_TEXTURE_2D;

    //设置纹理选项;
    //此处用上GL_CLAMP_TO_EDGE的原因是因为正好处于两个面之间的纹理坐标可能不能击中一个面(由于一些硬件限制),所以通过使用GL_CLAMP_TO_EDGE,OpenGL将在我们对两个面之间采样的时候,永远返回它们的边界值。
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, 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_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    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);//生成纹理;
            //glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
            stbi_image_free(data);//释放数据;
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path:" << std::endl;
            stbi_image_free(data);
        }
    }
    //结束一系列操作之后,会
    return textureID;
}

shader.vs.screen

#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexCoords;


out vec2 TexCoords;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x,aPos.y,0.0,1.0);//记住传入的顶点是四维的向量;
}

shader.fs.screen

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;//这一个采样器会采样每一帧上面的场景纹理的对应坐标的颜色值;

const float offset = 1.0/300.0;//用于核处理的偏移量的计算;

void main()
{
    //FragColor = texture(screenTexture,TexCoords);//进行采样,并返回FragColor的颜色值;
 //---//反相处理;
    //FragColor = vec4(vec3(1.0-texture(screenTexture,TexCoords)),1.0);
 //---//灰度处理1,取三个颜色分量的平均值,将其运用在最终输出的片段颜色的每一个分量上;
    //vec4 tempColor = texture(screenTexture, TexCoords);//时刻要记住通过纹理坐标取得的颜色值均为四分量;
    //float averge = (tempColor.r+tempColor.g+tempColor.b)/3.0;//取rgb三个分量,相加并相乘;
    //FragColor = vec4(averge,averge,averge,1.0);
//---//灰度处理2,由于人眼会对绿色更加敏感一些,而对蓝色不那么敏感,所以为了获取物理上更精确的效果,我们需要使用加权的(Weighted)通道:
    //vec4 tempColor = texture(screenTexture,TexCoords);
    //float averge = tempColor.r * 0.2126 + tempColor.g * 0.7152+tempColor.b * 0.0722;
    //FragColor = vec4(averge,averge,averge,1.0);
//---//核处理;
    vec2 offsets[9] = vec2[](
        vec2(-offset,  offset), // 左上
        vec2( 0.0f,    offset), // 正上
        vec2( offset,  offset), // 右上
        vec2(-offset,  0.0f),   // 左
        vec2( 0.0f,    0.0f),   // 中
        vec2( offset,  0.0f),   // 右
        vec2(-offset, -offset), // 左下
        vec2( 0.0f,   -offset), // 正下
        vec2( offset, -offset)  // 右下
    );

    float kenel[9]= float[](//注意到加权值的所有系数相加应该为1!!;
        -1,-1,-1,
        -1, 9,-1,
        -1,-1,-1
    );

    vec3 Sampler1[9];
    for(int i = 0;i<9;i++)
    {
        Sampler1[i] = vec3(texture(screenTexture,TexCoords.st+offsets[i]));//注意这句代码的写法;
    }
    vec3 col = vec3(0.0,0.0,0.0);
    for(int i = 0;i<9;i++)
    {//对邻近包括自己在内的九个值进行权重的相乘;
        col+=Sampler1[i]*kenel[i];
    }
    FragColor = vec4(col,1.0);//最后输出锐化的效果;


//---//模糊:
    float blurred[9] = float[](
        1.0/16,2.0/16,1.0/16,
        2.0/16,4.0/16,2.0/16,
        1.0/16,2.0/16,1.0/16
    );
    vec3 Sampler2[9];
    for(int i = 0;i<9;i++)
    {
        Sampler2[i] = vec3(texture(screenTexture,TexCoords.st+offsets[i]));//注意这句代码的写法;
    }
    vec3 dol = vec3(0.0,0.0,0.0);
    for(int i = 0;i<9;i++)
    {//对邻近包括自己在内的九个值进行权重的相乘;
        dol+=Sampler2[i]*blurred[i];
    }
    FragColor += vec4(dol,1.0);

//---//边缘检测
    float kenel3[9]= float[](//注意到加权值的所有系数相加应该为1!!;
        1,1,1,
        1, -8,-1,
        1,1,1
    );

    vec3 Sampler3[9];
    for(int i = 0;i<9;i++)
    {
        Sampler3[i] = vec3(texture(screenTexture,TexCoords.st+offsets[i]));//注意这句代码的写法;
    }
    vec3 eol = vec3(0.0,0.0,0.0);
    for(int i = 0;i<9;i++)
    {//对邻近包括自己在内的九个值进行权重的相乘;
        eol+=Sampler3[i]*kenel3[i];
    }
    FragColor += vec4(eol,1.0);
}

结束.

猜你喜欢

转载自blog.csdn.net/2201_75303014/article/details/129018391