帧缓冲
到目前为止,我们已经掌握了很多缓冲,比如用来储存顶点的GL_ARRAY_BUFFER
,用来储存顶点索引的GL_ELEMENT_ARRAY_BUFFER
以及储存颜色的颜色缓冲,储存深度信息的深度缓冲等等,这些诸多缓冲其实就是构成每一帧画面的诸多要素,所以故而便存在一个储存这些所有缓冲的缓冲,便是帧缓冲。
和其他缓冲一样,帧缓冲的创建也是先创建,然后再绑定。glGenFramebuffers
用来创建一个帧缓冲,再将帧缓冲用glBindFramebuffer
绑定到GL_FRAMEBUFFER
之后,便可以开始对其进行修改。刚创建好的帧缓冲其实是不完整的,一个完整的帧缓冲需要满足以下的条件:
- 附加至少一个缓冲(颜色、深度或模板缓冲)
- 至少有一个颜色缓冲附件(color-attachment)
- 所有的附件都必须是完整的(保留了内存)
- 每个缓冲都应该有相同的样本数。
附件
附件其实可以理解为一段内存,也即是在计算机内存中真实的内存空间,一个帧缓冲只有拥有附件的时候才是有效的。附件也分为纹理附件和缓冲对象附件。
- 纹理附件
纹理附件顾名思义,就是利用纹理的格式作为帧缓冲的内存,它可以将帧缓冲里面储存的东西以纹理的形式保存下来,这样使用起来也可以按照纹理的格式去使用。首先需要创建一个空的纹理,我们之前创建的纹理都是先用stbi_image
导入图像后再将图像导入进纹理,但是这里由于我们事先没有纹理数据,所以只能创建一个空的纹理glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,widht,height,GL_RGB,GL_UNSIGNED_BYTE,NULL)
。创建完成后再设置纹理必要参数,例如环绕模式,纹理过滤器,多级渐远纹理等等。之后我们就可以将纹理绑定在我们创建好的framebuffer
上了,glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0)
- target:帧缓冲的目标(一般都是GL_FRAMEBUFFER)
- attachment:我们想要附加的附件类型,颜色附件就是GL_COLOR_ATTACHMENTN,注意这里的N表示附加很多个颜色纹理附件,就和纹理中的GL_TEXUTURE0(1,2,3…)一样,可以有很多个。
- textarget:希望附加的纹理类型,一般都是2D纹理,所以就是GL_TEXTURE_2D。
- texuture:要附加的纹理本身。
- level:多级渐远纹理级别,这里设置为最近一级也即是0即可。
当然除了颜色可以用纹理的格式存储外,深度和模板都是可以的,如果要存储深度,那么纹理格式和内部格式就要改成GL_DEPTH_COMPONENT
,并且绑定时的附件类型就要变成GL_DEPTH_ATTACHMENT
;如果要存储模板缓冲,那么纹理格式就要改成GL_STENCIL_INDEX
绑定时附件类型改为GL_STENCIL_ATTACHMENT
。同时深度与模板可以合并一个纹理附加到帧缓冲中,纹理格式为GL_DEPTH24_STENCIL8
,格式的含义是用24位来存储深度信息,用8位来存储模板信息。同时附件类型变为GL_DEPTH_STENCIL_ATTACHMENT
。
- 渲染缓冲对象附件(Renderbuffer Object)
在引入渲染缓冲对象之前,纹理是唯一可用的附件,所以所有的信息都需要转换位纹理再使用。但是纹理并不是针对帧缓冲设计的,所以转换起来需要耗费一定时间。所以渲染缓冲便是针对帧缓冲而设计的。渲染缓冲直接将所有的渲染数据存储到它里面,所以它可以很快速的被写入或者复制到其他缓冲中去,所以像交换缓冲(即每次最后进行的glfwSwapBuffers
)便可以用渲染缓冲对象实现。但是渲染缓冲对象是只写的,所以一般用于只需要写操作的缓冲,例如深度缓存和模板缓存。
渲染缓冲对象用glGenRenderbuffers
创建,然后glBindRenderbuffer
之后,便可以创建它真正的缓冲,即内存中的位置glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,width,height)
,最后也是用glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DPETH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo)
来将渲染缓冲对象绑定到帧缓冲上面,这里注意到第三个参数变为了GL_RENDERBUFFER
,这表明附件是使用渲染缓冲作为存储的(其实从函数名已经可以看出来了)。
开始渲染
有了缓冲附件,那么我们的帧缓冲就已经完整了,当然在使用前还可以用glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE
来检查帧缓冲是否完整。首先我们要先创建一个帧缓冲:
1 | unsigned int FBO; |
之后就可以开始补充附件了,我们先创建颜色缓冲所需单独附件,这里我们选择纹理附件,因为写入纹理之后我们还需要将纹理渲染到其他图形上去。
1 | unsigned int texture_pic; |
这里我们选择1200X1200的纹理尺寸,然后只设置纹理过滤,纹理环绕没有设置。之后便要设置深度缓冲和模板缓冲了,由于深度缓冲和模板缓冲我们不需要再使用,只要openGL自动进行模板测试和深度测试就可以了,所以我们选择缓冲对象附件。
1 | unsigned int RBO; |
之后的阶段其实和之前渲染是完全一样的,只不过由于绑定了新的帧缓冲,所以渲染的最终结果不会显示到屏幕上而是被储存到了帧缓冲里面,所以这也叫离屏缓冲。这里还需要注意的是,在开始渲染之前还需要使用glViewport(0,0,1200,1200)
来调整视口变换,因为我们这里的帧缓冲大小设置的是1200X1200,也就是相当于把屏幕画面放缩到了1200X1200,但是我们之前屏幕画面是800X600,所以如果不进行新的视口变化调整,渲染出来的画面会出问题。同样的,调整了视口变化,那么投影矩阵也要相对应进行调整Projection=perspective(radians(45.f),1200*1.f/1200*1.f,0.1f,1000.f)
这样画面才不会被拉伸。
最后我们将离屏渲染出来的纹理绑定到一个矩形上,然后在默认的屏幕帧缓冲上渲染出这个矩形,那么将会看到这样的画面,就好像是投影在了一块屏幕上的感觉。