帧缓冲
原文
一个完整的帧缓冲需要满足以下的条件:
1、附加至少一个缓冲(颜色、深度或模板缓冲)。
2、至少有一个颜色附件(Attachment)。
3、所有的附件都必须是完整的(保留了内存)。
4、每个缓冲都应该有相同的样本数。
检查一个FrameBuffer是否创建好:
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
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,也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。渲染缓冲对象对这种操作非常完美。
由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。我们需要深度和模板值用于测试,但不需要对它们进行采样,所以渲染缓冲对象非常适合它们。当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。
创建FrameBuffer并绑定纹理和渲染缓冲
unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, 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_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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);
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1280, 720);
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);
使用方法
先绑定我们自己创建的帧缓冲来进行离屏渲染,渲染完毕后将framebuffer再次绑定回系统默认的缓冲,调用我们刚刚绑定在帧缓冲上的纹理或者渲染缓冲来使用我们的framebuffer。
// 第一处理阶段(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);
几种特效
反色(Inversion)
vec4 Inversion()
{
vec4 result=vec4(vec3(1.0-texture(Texture,Texcoord)),1.0);
return result;
}
灰度化(Grayscale)
vec4 Grayscale()
{
vec4 color=texture(Texture,Texcoord);
float average=(color.x+color.y+color.z)/3.0;
return vec4(average,average,average,1.0);
}
加强绿色减弱蓝色的灰度化(GreenGrayscale)
人眼会对绿色更加敏感一些,而对蓝色不那么敏感,所以为了获取物理上更精确的效果,我们需要使用加权的(Weighted)通道:
vec4 GreenGrayscale()
{
vec4 color=texture(Texture,Texcoord);
float average=color.x*0.2126+color.y*0.7152+color.z*0.0722;
return vec4(average,average,average,1.0);
}
锐化(Sharpen)
vec4 Sharpen()
{
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,-offset),
vec2(offset,-offset)
);
float kernel[9]=float[](
-1,-1,-1,
-1,9,-1,
-1,-1,-1
);
vec4 color=vec4(0.0);
for(int i=0;i<9;i++)
color+=texture(Texture,Texcoord.st+offsets[i])*kernel[i];
return vec4(vec3(color),1.0);
}
模糊(Blur)
vec4 Blur()
{
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,-offset),
vec2(offset,-offset)
);
float kernel[9]=float[](
1.0,2.0,1.0,
2.0,4.0,2.0,
1.0,2.0,1.0
);
vec4 color=vec4(0.0f);
for(int i=0;i<9;i++)
color+=texture(Texture,Texcoord.st+offsets[i])*kernel[i];
color/=16;
return color;
}
边缘检测(Edge_Detection)
vec4 Edge_Detection()
{
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,-offset),
vec2(offset,-offset)
);
float kernel[9]=float[](
1,1,1,
1,-8,1,
1,1,1
);
vec4 color=vec4(0.0f);
for(int i=0;i<9;i++)
color+=texture(Texture,Texcoord.st+offsets[i])*kernel[i];
return color;
}