帧缓冲可以理解为着色器渲染之后将要显示在窗口上的所有颜色信息,深度信息和模版信息的数据集合,这些数据都保存在内存中,最后经由显示器显示在窗口中。窗口都有一个默认的帧缓冲,来存放最终要显示的所有信息。

OpenGL允许用户自定义帧缓冲,用户自定义帧缓冲有什么用呢?试想,如果你想实现这样一个功能,就是希望能够在最终显示在窗口中的画面的基础上再对每一帧图片进行处理,也就相当于动态的后处理,对于这种需求,自定义帧缓冲就能实现。

具体的实现可以是这样:

  1. 自定义一个帧缓冲;
  2. 绑定自定义帧缓冲(默认情况下绑定的是默认帧缓冲);
  3. 场景渲染(渲染结束之后我们自定义帧缓冲中就有了帧缓冲数据);
  4. 切换到默认帧缓冲(绑定默认帧缓冲,最终窗口要通过交换默认帧缓冲数据来显示的);
  5. 将自定义帧缓冲中的数据以一个纹理的形式传给着色器,在片段着色器中就可以进行相应处理;

就像是GDI中的双缓冲绘图一样,先在内存DC里面处理所有的绘图操作,然后再通过Bitblt函数将要显示的东西copy到窗口DC。其实自定义帧缓冲相当于内存DC,默认帧缓冲相当于窗口DC。

在OpenGL中,帧缓存里面包含了颜色缓存,深度缓存和模板缓存,它是这些数据的集合。颜色缓冲我们可以理解为:它里面包含了像素颜色信息;深度缓冲里面是深度测试相关的信息;模板缓冲里面是模板测试所需的信息。所以在代码里面这些都会有涉及到,下面我们看一下如何定义这些缓冲:

1.创建帧缓冲
GLuint fbo;
glGenFramebuffers(1, &fbo);     //生成帧缓冲对象

这里创建了帧缓冲对象,此时这个帧缓冲是不完整的,它需要满足一下条件才可以算定义完成:

  • 必须往里面加入至少一个附件(颜色、深度、模板缓冲);
  • 其中至少有一个是颜色附件;
  • 所有的附件都应该是已经完全做好的;
  • 每个缓冲都应该有同样数目的样本;

所以我们还需要至少定义一个颜色缓冲才可以使用我们的自定义帧缓冲。

2.创建颜色缓冲
GLuint texture;
glGenTextures(1, & texture);    //和创建普通纹理一样
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 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);

我们看到在glTextImage2D函数中我们的最后一个参数给了NULL,在我们以往设置普通纹理的时候这个参数都传的是一个指针(图片数据的地址),这里只是分配了内存,并没有实际数据填入,实际数据会在渲染过程中填入,最后才显示在窗口中。

3.将纹理(颜色缓冲)附加到帧缓冲上
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHENT0, GL_TEXTURE_2D, texture, 0);

注意上面函数中的参数,GL_FRAMEBUFFER,即帧缓冲类型,GL_COLOR_ATTACHENT0指颜色缓冲类型,texture就是我们实际绑定的一个纹理,该函数实现了将颜色缓冲附加到帧缓冲上。实际上,我们这个缓冲现在就可以用了,因为已经满足了他的使用条件,但是如果我们的渲染需要深度测试和模版测试,我们还需要将深度测试和模板测试附加到帧缓冲上。

4.将深度测试缓冲和模板测试缓冲附加到帧缓冲上

渲染缓冲对象:渲染缓冲对象将所有渲染数据直接储存到它们的缓冲里,而不会进行针对特定纹理格式的任何转换,这样它们就成了一种快速可写的存储介质了。因为它们的数据已经是原生格式了,在写入或把它们的数据简单地到其他缓冲的时候非常快。当使用渲染缓冲对象时,像切换缓冲这种操作变得异常高速。

创建渲染缓冲对象:

GLuint rbo;
glGenRenderbuffers(1, &rbo);

相似地,我们打算把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象:

glBindRenderbuffer(GL_RENDERBUFFER, rbo);

由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。我们就需要有深度和模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合的。当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优化过的。

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

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, WIDTH, WEIGHT);

附加到帧缓冲对象上:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
5.检查帧缓冲是否可用
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
 cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;

通过glCheckFramebufferStatus函数我们可以判断我们所定义的帧缓冲是否可用。

实际代码调用如下:

// 绑定我们自定义的帧缓冲,开始渲染,渲染的结果会保存到我们自定义的帧缓冲中。
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);
// 我们自定义的帧缓冲中的颜色缓冲中已经有了我们最终的渲染结果,并已经以纹理的方式保存在了Colorbuffer中,
// 这里用另外一个着色器程序,再次渲染,就是把我们得到的纹理贴到一个四边形上,所以在片段着色器中我们就可以再次处理这个结果,
// 最终得到一些特殊的效果。
screenShader.Use();  
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
6.后期处理

前面我们说过通过帧缓冲我们可以实现一些有趣的效果,现在我们应该明白所谓的后期处理就是当场景第一次在我们自定义的帧缓冲中渲染结束之后,再次将这个渲染结果以纹理的形式传给片段着色器,片段着色器再次对图片进行加工。我们常见的后期处理如反相、灰度、模糊、边检测,这些效果都可以通过后期的处理得到。下面我们就了解一下他们都是如何实现的:

反相:我们已经取得了渲染输出的每个颜色,所以在片段着色器里返回这些颜色的反色(Inversion)并不难。我们得到屏幕纹理的颜色,然后用1.0减去它。
void main()
    color = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);

在片段着色器里面我们做如上代码操作就可以实现反相的效果,下图中右上角表示反向后的效果。

灰度:移除所有除了黑白灰以外的颜色作用,是整个图像成为黑白的。实现它的简单的方式是获得所有颜色元素,然后将它们平均化。
void main()
    color = texture(screenTexture, TexCoords);
    float average = (color.r + color.g + color.b) / 3.0;
    color = vec4(average, average, average, 1.0);

这已经创造出很赞的效果了,但是人眼趋向于对绿色更敏感,对蓝色感知比较弱,所以为了获得更精确的符合人体物理的结果,我们需要使用加权通道:

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

最终的效果如下所示:
这里写图片描述

我们在后期处理的时候都是对已有的一个纹理进行一些操作,所以我们就可以采样纹理上的颜色来实现一些效果,如何采样这些颜色并对这些颜色怎样操作,最终能产生怎样的效果。首先我们先了解一下 Kernel,它其实就像是一个矩阵,如下是一个Kernel的例子:

2222152222

这个Kernel是一个3×3的矩阵,中间那个-15代表我们要处理的像素,在它周围有8个采样点,这8个采样点通过2这个因子来影响我们中间那个像素的颜色。假设我们在相应位置采样得到的这些点上的颜色如下:

adgbehcfi
我们把a、b、c这些都看成是一个个颜色向量,那么我们最终得到中间点的颜色就是这样的:
我们可以看到Kernel里面的这些因子(矩阵中每个元素)都会影响到最终的颜色,所以不同的Kernel就能对最终颜色产生不同的效果,网上也有一些其他Kernel的例子,当然我们也可以自己设置满足自己需求的Kernel。如果对Kernel有什么疑问,可以从 这里了解相关原理。

kernel对于后处理来说非常管用,因为用起来简单。网上能找到有很多实例,为了能用上kernel我们还得改改片段着色器。这里假设每个kernel都是3×3(实际上大多数都是3×3):

const float offset = 1.0 / 300;  
void main()
    vec2 offsets[9] = vec2[](
        vec2(-offset,  offset),  // top-left
        vec2( 0.0f,    offset),  // top-center
        vec2( offset,  offset),  // top-right
        vec2(-offset,  0.0f),    // center-left
        vec2( 0.0f,    0.0f),    // center-center
        vec2( offset,  0.0f),    // center-right
        vec2(-offset, -offset),  // bottom-left
        vec2( 0.0f,   -offset),  // bottom-center
        vec2( offset, -offset)   // bottom-right
    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);
    //注意这里定义的临时col一定要初始化,否则会出问题,当时调试也是在这里出的问题,还是一位前辈给我指出了问题,非常感谢。
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];
    color = vec4(col, 1.0);

片段着色器中的代码如上所示,这是一个锐化效果的Kernel,效果如下图所示:
这里写图片描述

模糊:通过Kernel我还可以实现模糊的效果,模糊Kernel如下所示:

在片段着色器中我们只需要把之前锐化的那个Kernel替换成模糊Kernel就可以实现模糊效果了,模糊Kernel代码如下:

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  

下面是一个边检测Kernel与锐化Kernel类似:

111181111

同样我们改变片段着色器中的Kernel代码:

float kernel[9] = float[](
   1,  1, 1,
   1, -8, 1,
   1,  1, 1

通过以上内容我们可以看到,帧缓冲给我们提供了后期处理的机会;同样,Kernel提供了一种更加方便的后期处理方式。因此,我们可以使用以上技术来制作我们想要的后期效果。

参考LearnOpenGL CN 帧缓冲

概念:帧缓冲可以理解为着色器渲染之后将要显示在窗口上的所有颜色信息,深度信息和模版信息的数据集合,这些数据都保存在内存中,最后经由显示器显示在窗口中。窗口都有一个默认的帧缓冲,来存放最终要显示的所有信息。OpenGL允许用户自定义帧缓冲,用户自定义帧缓冲有什么用呢?试想,如果你想实现这样一个功能,就是希望能够在最终显示在窗口中的画面的基础上再对每一帧图片进行处理,也就相当于动态的后处理,对于这种需求
qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl实现缓冲qt+opengl
一、OGL显卡中的缓存区(非输入的原始顶点和像素数据) 1) 颜色缓存,  左,右,左后,右后和任意数量的辅助颜色缓存; OGL 3.1辅助颜色缓存没有,不支持立体观察(VR), 是使用左颜色缓存(启用双缓存是左,左后颜色缓存,启用单缓存是左颜色缓存) 2) 深度缓存 3) 模板缓存 4) 累积缓存(累计存储,拷贝回源颜色缓存的,OGL 3.1后已废弃) 二、Vertex Sh
在之的章节,所有的物体都是中规中矩的显示的,只考虑了光照对物体的影响,那假设想要显示特殊的效果该怎么操作呢?例如马赛克风、将所有的物体都显示为黑白色,就像上世纪80年代的灰白电视一样,又或者说将整个场景渲染到一张泛黄的纸上以体现出年代感…… 当然是修改着色器,事实上,很多地方都是这么做的,不过有些情况下,场景中的物体和对应的着色器都不少,若是想要整个场景(视口)体现出某个效果,就需要借助别的方法了 接上文:OpenGL基础34:缓冲之附件 接下来就可以开始实战了 五、黑白画 根据上文的目...
【项目资源】:包含端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
中南民族大学概率论A班期末复习重点资源是一套专为该校概率论课程的学生设计的复习资料。这些资源旨在帮助学生巩固和深化他们对概率论基本概念、原理和应用的理解,以及为期末考试做好充分准备。 1. 课程内容概览 资源通常包括概率论的基础知识,如随机事件、概率的定义和计算、条件概率、独立性、贝叶斯定理等。此外,还涵盖了更高级的主题,如随机变量、概率分布、期望值、方差、协方差、大数定律和中心极限定理等。 2. 公式和定理总结 复习资料中会详细列出所有重要的数学公式和定理,以及它们的适用条件和应用场景。这有助于学生快速回顾和记忆,确保在考试中能够准确应用。 3. 典型例题解析 通过提供一系列典型的例题及其详细解答,资源帮助学生理解如何将理论知识应用于解决实际问题。这些例题通常包括选择题、填空题和解答题,覆盖了课程的所有主要部分。 4. 解题技巧和策略 除了具体的知识点和例题,复习资源还会提供解题技巧和策略,比如如何识别和应用条件概率,如何计算期望值和方差,以及如何使用概率分布来解决问题。
1. 使用OpenGL API创建一个FBO,并将其与您的OpenGL上下文关联起来。 2. 使用CUDA的图形API将FBO与CUDA上下文关联起来,以便您可以在CUDA内存中读取和写入FBO数据。 3. 在CUDA内存中分配和初始化您的数据。 4. 使用CUDA的图形API将您的数据从CUDA上下文传递到FBO中。 5. 在您的CUDA程序中执行计算任务,将计算结果存储在CUDA内存中。 6. 使用CUDA的图形API将计算结果从CUDA上下文传递回FBO中。 7. 使用OpenGL API将FBO中的数据渲染到屏幕上。 请注意,由于CUDA和OpenGL都是高级工具,并且它们之间的集成需要一定的OpenGL和CUDA编程经验,因此您可能需要一些时间和实践才能成功地将它们集成到您的应用程序中。