絮叨几句

最近看到一段挺心酸的对话,发生在我所在的行业。图形学。2012年我入门图形学,2014年开始全面专攻游戏引擎,学习的是一位前辈的书。之后我从3D Driver行业,转行进入了完美世界3D引擎组。时隔3年,这位前辈又众筹出了一本书,借鉴了无数本国外原著,老实说,我觉得应该是属于国内图形学顶级书刊了。但是前几天在他的群里聊到,目前只众筹了不到400本,还不足以支付印刷厂最低2000本起印的成本,更不用说他为了这本书全职投入了一年的时间成本。哎。不要怪我为什么不在这里写下他的书名,因为我这人玻璃心,从不做推荐,精心推荐给别人的产品,一旦遭到怀疑,我就不开心。对吧,凭啥怀疑我...我又没收钱...

最近清华教授的事情也闹得沸沸扬扬,我不去评价这件事情,只是觉得做学术,又累又难,做好了还有别样的风险,哎。

老实说,之前我觉得做技术挺好的,在SHADERVARIANTS这篇文章的第一段我也提到了。但是现在我更觉得,做技术的需要和项目去对接,或者说是,需要去项目组,解决项目中实际会用到的问题,用技术把项目的水平提到到另外一个层次,慢慢的去进入社会,这样,才适合我。单一的做技术,我已经没什么兴趣了。所以申请了进入下一个项目,在进入项目之前,把之前好久没有更新的OpenGL ES2.0串讲更新完,时间能来及的话,把3.0也更新上。毕竟现在已经进入3.0时代,需要整理一下3.0的新技术,去用在项目上。

一心做技术真的赚不到什么钱,我的二维码挂在首页一个月了也没收到钱...挂了百度广告,点击一下就有收入,一个星期了也没人点...网站一年的收入连最便宜的网站服务器200块成本都收不回来,哎。


对上面十节进行回顾

通过上面十节的内容,已经把EGL、GLSL、OpenGL ES2.0的基本知识说完了。通过这些基础知识,已经可以绘制出看上去不错的东西了。通过EGL准备了环境(最主要是含有framebuffer的surface和context),通过OpenGL ES2.0创建了Shader,并用GLSL编写了Shader,然后通过OpenGL ES2.0把顶点信息和纹理传入Shader,在Shader中更新顶点位置,完成上色,然后在OpenGL ES2.0一声令下,绘制到framebuffer上,再有EGL把这块framebuffer显示到屏幕上,一张图片就被绘制出来了,齐活。

但是这些都是最基本的,还可以完成更复杂的操作,比如这节要说的FBO(framebuffer object)。(我习惯于把EGL创建的framebuffer称为framebuffer,也叫做window-system-provided framebuffer,把OpenGL ES创建的FBO称为FBO,也叫做application-created framebuffer,其实它们构造是一样的,只是默认情况下,OpenGL ES使用的是window-system-provided framebuffer,而且通过EGL swap到屏幕上的时候,只能swap framebuffer)

通过之前的内容,知道了OpenGL ES2.0就是把内容绘制到EGL创建的framebuffer上,然后swap到屏幕上。但是有了FBO后,也可以先把内容绘制到FBO上,然后再搞到framebuffer上,最后再swap到屏幕上。比如在场景中有一面镜子,那么就可以先以镜子的视角把世界绘制到FBO上,然后再把FBO上的内容作为一张texture绘制到真实世界的镜子这个平面上。那么这节课,我们将介绍如何使用FBO。


OpenGL ES API 详解

void glGenFramebuffers(GLsizei n, GLuint * framebuffers);

window-system-provided framebuffer是由EGL创建的,无法被OpenGL ES所控制,而application-created framebuffer是由OpenGL ES创建、更改、销毁的。而 glGenFramebuffers 这 个 API,就是用于先创建 application-created framebuffer 的 name,然后再通过 API glBindFramebuffer 创建一个 FBO。先说 glGenFramebuffers,glBindFramebuffer 这个 API 一会再进行说明。

window-system-provided framebuffer被创建的时候会关联一些color buffer、depth buffer(如果有)、stencil buffer(如果有)。而application-created framebuffer是不会自动创建及关联这些buffer的,所以又多了一个新的概念,叫做renderbuffer,renderbuffer对应了framebuffer中的color\depth\stencil等buffer,由于FBO是由OpenGL ES创建的,那么理所当然renderbuffer也是由OpenGL ES创建。而glGenRenderbuffersglBindRenderbufferglRenderbufferStorage就是用于创建FBO对应的renderbuffer的,关于renderbuffer的API我们一会再说。

这个函数的第一个输入参数的意思是该 API 会生成 n 个 framebuffer object name, 当 n 小于 0 的时候,出现 INVALID_VALUE 的错误。第二个输入参数用于保存被创建的 framebuffer object name。这些 framebuffer object name 其实也就是一些数字,而且假如一次性生成多个 framebuffer object name,那么它们没有必要必须是连续的数字。 framebuffer object name 是 uint 类型,而且 0 对应的就是window-system-provided framebuffer,已经被预留了,所以肯定是一个大于 0 的整数。

这个函数没有输出参数。当创建成功的时候,会在第二个参数 framebuffer 中生成 n 个之前没有使用过的 framebuffer objects 的 name。然后这些 name 会被标记为已使用,而这个标记只对 glGenFramebuffers 这个 API 有效,也就是再通过这个 API 生成更多的 framebuffer object name 的时候,不会使用之前创建的这些 framebuffer objects name,除非这些framebuffer objects name由被glDeleteFramebuffers Delete掉。 所以回忆一下,这一步其实只是创建了一些 framebuffer object name,而没有真正的创建 framebuffer object。而只有在这些 framebuffer object name 被 glBindFramebuffer 进行 bind 之后, 才会真正的创建对应的 framebuffer object。

void glBindFramebuffer(GLenum target, GLuint framebuffer);

上一个 API glGenFramebuffers 只创建了一些 framebuffer object 的 name,glBindFramebuffer 这个 API 再创建一个 framebuffer object。 这个函数的第一个输入参数的意思是指定 framebuffer object 的类型,framebuffer object目前只有一种类似,那就是GL_FRAMEBUFFER。那么在这里,第一个输入参数必须是 GL_FRAMEBUFFER 。如果传入其他的参数,就会报 INVALID_ENUM 的错误。第二个输入参数为刚才 glGenFramebuffers 得到的 framebuffer object name。

这个函数没有输出参数,假如传入的 framebuffer 是刚被创建的 framebuffer object name,而且它还没有被创建和关联一个 framebuffer object,那么通过这个 API,就会生成一个 framebuffer object,且与这个 framebuffer object name 关联在一起,之后指定某个 framebuffer object name 的时候,也就相当于指定这个 framebuffer object。新创建的 framebuffer object 是一个空间为 0,且初始状态为默认值的 framebuffer object,一个FBO有一个color attachment挂载点、一个depth attachment挂载点、一个stencil attachment挂载点。然后创建和关联完毕之后,也就会把这个 framebuffer object 当作是当前 GPU 所使用的 framebuffer 了,也就是说OpenGL ES再次绘制的时候,将不再绘制到 window-system-provided framebuffer,而是绘制到这个FBO上了。而如果通过API glReadPixels、glCopyTexImage2DglCopyTexSubImage2D进行读取的话,也将从这个FBO种进行读取。如果传入的 framebuffer 已经有关联的 framebuffer object 了,那么只是把该 framebuffer object 指定为当前 GPU 所使用的 framebuffer。然后 GPU 之前使用的 framebuffer 或者 FBO 就不再是处于被使用状态了。

所以回忆一下,通过 glGenFramebuffers 创建一些 framebuffer object name,然后通过 glBindFramebuffer,给 framebuffer object name 创建和关联一个 framebuffer object,同时,通过这个 API,还将参数 framebuffer 对应的 framebuffer object 设置为目前 GPU 所使用的 framebuffer。虽然 GPU 中可以存放大量的 framebuffer object,但是同一时间一个 thread 的一 个 context 中只能有一个 framebuffer 是被使用着的。之后关于 framebuffer 的操作, 比如查询 framebuffer 的状态等,就会直接操作 GL_FRAMEBUFFER ,而不会在使用 framebuffer object name 了。如果当前GL_FRAMEBUFFER 为0,那么查询或者修改GL_FRAMEBUFFER,则会出现GL_INVALID_OPERATION 的错误。所以,如果想使用某个 framebuffer object,必须先通过 glBindFramebuffer 这个 API,把这个 framebuffer 推出来,设置为 GPU 当前的 framebuffer。对FBO的操作,也就直接影响到FBO上挂载的attachment。

初始的时候,相当于执行了glBindFramebuffer(GL_FRAMEBUFFER,0);上面也说了FBO 0对应着window-system-provided framebuffer,所以所有的操作的操作都是在window-system-provided framebuffer上进行操作。

window-system-provided framebuffer 和application-created framebuffer的最主要的区别是:1. FBO的attachment可以修改。在OpenGL ES2.0中FBO只能有一个color attachment挂载点GL_COLOR_ATTACHMENT0、一个depth attachment挂载点GL_DEPTH_ATTACHMENT、一个stencil attachment挂载点GL_STENCIL_ATTACHMENT,而这三个挂载点初始都为GL_NONE,需要通过API glFramebufferRenderbufferglFramebufferTexture2D手动挂载。挂载到上面的RBO和texture也是由OpenGL ES来创建和控制。2. 无论读取还是写入,像素所有权测试总是通过(Patrick:什么是像素所有权测试?)。3. FBO的buffer无法像framebuffer那样可以被swap到屏幕上,所以OpenGL ES针对这种绘制叫做off-screen rendering。4.没有multisample buffer,所以针对FBO的 GL_SAMPLES 和GL_SAMPLE_BUFFERS 均为0。

通过glBindFramebuffer active的FBO可以通过glBindFramebuffer bind另外一个FBO或者bind0的方式,或者是glDeleteFramebuffers的方式deactivate。

framebuffer object name 和对应的 framebuffer object 和 shader 以及 program 一样,都属于一个 namespace,也就是可以被多个 share context 进行共享。

void glGenRenderbuffers(GLsizei n, GLuint * renderbuffers);

renderbuffer就是FBO上关联的一块buffer(texture也可以)。renderbuffer是由OpenGL ES创建和管理的,所以可以随意的将其与FBO进行attach和detach,甚至可以将其与多个FBO绑定,这样可以避免了数据copy以及减少了内存浪费。这一点window-system-provided framebuffer就做不到。

renderbuffer被attach到FBO上后,当该FBO active后,这块renderbuffer就被作为OpenGL ES实际绘制和读取的目标。

这个函数的第一个输入参数的意思是该 API 会生成 n 个 renderbuffer object name, 当 n 小于 0 的时候,出现 INVALID_VALUE 的错误。第二个输入参数用于保存被创建的 renderbuffer object name。这些 renderbuffer object name 其实也就是一些数字,而且假如一次性生成多个 renderbuffer object name,那么它们没有必要必须是连续的数字。 renderbuffer object name 是 uint 类型,而且 0 已经被预留了,虽然0并没有对应任何东西,所以肯定是一个大于 0 的整数。

这个函数没有输出参数。当创建成功的时候,会在第二个参数 renderbuffer 中生成 n 个之前没有使用过的 renderbuffer objects 的 name。然后这些 name 会被标记为已使用,而这个标记只对 glGenRenderbuffers 这个 API 有效,也就是再通过这个 API 生成更多的 renderbuffer object name 的时候,不会使用之前创建的这些 renderbuffer objects name,除非这些renderbuffer objects name由被glDeleteRenderbuffers Delete掉。 所以回忆一下,这一步其实只是创建了一些 renderbuffer object name,而没有真正的创建 renderbuffer object。而只有在这些 renderbuffer object name 被 glBindRenderbuffer 进行 bind 之后, 才会真正的创建对应的 renderbuffer object。

void glBindRenderbuffer(GLenum target, GLuint renderbuffer);

上一个 API glGenRenderbuffers 只创建了一些 renderbuffer object 的 name,glBindRenderbuffer 这个 API 再创建一个 renderbuffer object。 这个函数的第一个输入参数的意思是指定 renderbuffer object 的类型,renderbuffer object目前只有一种类似,那就是GL_RENDERBUFFER。那么在这里,第一个输入参数必须是 GL_RENDERBUFFER 。如果传入其他的参数,就会报 INVALID_ENUM 的错误。第二个输入参数为刚才 glGenRenderbuffers 得到的 renderbuffer object name。

这个函数没有输出参数,假如传入的 renderbuffer 是刚被创建的 renderbuffer object name,而且它还没有被创建和关联一个 renderbuffer object,那么通过这个 API,就会生成一个 renderbuffer object,且与这个 renderbuffer object name 关联在一起,之后指定某个 renderbuffer object name 的时候,也就相当于指定这个 renderbuffer object。新创建的 renderbuffer object 是一个空间为 0,格式为GL_RGBA4,red, green, blue, alpha, depth, and stencil的像素位均为0,且初始状态为默认值的 renderbuffer object。然后创建和关联完毕之后,也就会把这个 renderbuffer object 当作是当前 GPU 所使用的 RBO 了。如果传入的 renderbuffer 已经有关联的 renderbuffer object 了,那么只是把该 renderbuffer object 指定为当前 GPU 所使用的 renderbuffer。然后 GPU 之前使用的 RBO 就不再是处于被使用状态了。

所以回忆一下,通过 glGenRenderbuffers 创建一些 renderbuffer object name,然后通过 glBindRenderbuffer,给 renderbuffer object name 创建和关联一个 renderbuffer object,同时,通过这个 API,还将参数 renderbuffer 对应的 renderbuffer object 设置为目前 GPU 所使用的 renderbuffer。虽然 GPU 中可以存放大量的 renderbuffer object,但是同一时间一个 thread 的一 个 context 中只能有一个 renderbuffer 是被使用着的。之后关于 renderbuffer 的操作, 比如查询 renderbuffer 的状态等,就会直接操作 GL_RENDERBUFFER ,而不会在使用 renderbuffer object name 了。如果当前GL_RENDERBUFFER 为0,那么查询或者修改GL_RENDERBUFFER,则会出现 GL_INVALID_OPERATION 的错误。所以,如果想使用某个 renderbuffer object,必须先通过 glBindRenderbuffer 这个 API,把这个 renderbuffer 推出来,设置为 GPU 当前的 RBO。

初始的时候,相当于执行了glBindRenderbuffer(GL_RENDERBUFFER,0);执行了这个,GL_RENDERBUFFER的binging就会恢复到初始状态。

通过glBindRenderbuffer active的RBO可以通过glBindRenderbuffer bind另外一个RBO或者bind0的方式,或者是glDeleteRenderbuffers的方式deactivate。

renderbuffer object name 和对应的 renderbuffer object 和 framebuffer object、shader 以及 program 一样,都属于一个 namespace,也就是可以被多个 share context 进行共享。

void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);

通过上面2个API创建了一个size为0的RBO,虽然这个RBO可以attach到FBO上,但是依然无法被使用,因为绘制buffer尺寸怎么可以只为0呢,所以就通过glRenderbufferStorage这个API给RBO创建、初始化存储空间。

这个函数的第一个输入参数的意思是指定 renderbuffer object 的类型,renderbuffer object目前只有一种类似,那就是GL_RENDERBUFFER。那么在这里,第一个输入参数必须是 GL_RENDERBUFFER 。如果传入其他的参数,就会报 INVALID_ENUM 的错误。由于RBO是将作为FBO的color/depth/stencil attachment,所以第二个输入参数internalformat必须为color/depth/stencil相关的格式。其中,如果该RBO将作为color attachment,那么internalformat必须为GL_RGBA4/GL_RGB5_A1/GL_RGB565,如果该RBO将作为depth attachment,那么internalformat必须为GL_DEPTH_COMPONENT16,如果该RBO将作为stencil attachment,那么internalformat必须为GL_STENCIL_INDEX8。否则,则会出现GL_INVALID_ENUM的错误。第三个和第四个参数width、height为RBO的尺寸,如果width或者height超过GL_MAX_RENDERBUFFER_SIZE,或者小于0,则会出现GL_INVALID_VALUE的错误。如果在执行这个API的时候,当前没有RBO active,也就是说当前处于glBindRenderbuffer(GL_RENDERBUFFER,0);的状态,则会出现GL_INVALID_OPERATION的错误。

这个函数没有输出参数,如果OpenGL ES无法创建所需要尺寸的一块空间,则出现GL_OUT_OF_MEMORY的错误。如果该RBO之前就关联了一块空间,那么之前关联的空间将会被销毁。执行完这个命令后,新生成的这块buffer为undefined。

虽然通过这个API创建的空间可以是各种各样的,但是一旦创建好之后,RBO的尺寸和格式将无法更改。

void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);

通过上面5个api,创建了FBO和RBO,下面就要把RBO和FBO关联起来,所使用的函数,就是这个glFramebufferRenderbuffer。通过这个API可以将指定的RBO关联到GPU当前的FBO上。

这个函数的第一个输入参数的意思是指定 framebuffer object 的类型,framebuffer object目前只有一种类似,那就是GL_FRAMEBUFFER。那么在这里,第一个输入参数必须是 GL_FRAMEBUFFER 。如果传入其他的参数,就会报 GL_INVALID_ENUM 的错误。如果当前GPU没有指定的FBO,也就是说当前GPU使用的是window-system-provided framebuffer的话,则会出现 GL_INVALID_OPERATION 的错误。第二个参数必须是FBO的3个挂载点之一,GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT or GL_STENCIL_ATTACHMENT,否则,则会出现GL_INVALID_ENUM 的错误。第三个参数renderbuffertarget和第四个参数renderbuffer相关,如果第四个参数为0,那么第三个参数就无所谓了,这样的话会把当前FBO的attachment point上attach的东西进行detach。但是如果第四个参数为一个非0的,那么第三个参数必须为GL_RENDERBUFFER ,否则则会出现 GL_INVALID_ENUM 的错误。而如果第四个参数不是0,也不是一个已有的RBO name,则会出现 GL_INVALID_OPERATION 的错误。

这个函数没有输出参数,如果参数都无误,其中renderbuffer不为0,则该RBO就被attach到当前FBO的attachment point上了。GL的状态GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE就被设为GL_RENDERBUFFER,而GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME则会被设置为第四个参数renderbuffer了。(初始状态下,GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE :GL_NONE;GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME :0;GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL :0 ;GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE :GL_TEXTURE_CUBE_MAP_POSITIVE_X)其他attachment point的状态不变。当然,如果这个API执行出错,则所有状态都不变。

需要注意的是,如果某个RBO被delete掉了,那么首先会把这个RBO从当前FBO的attachment point上detach掉(如果之前attach了的话)。但是如果该RBO还被attach到了其他FBO上,那么那些FBO上的该RBO不会自动detach,需要应用程序自己去完成这个工作。

void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);

在本节的最开始就说了,FBO的buffer可以是RBO,还可以是texture,反正左右就是一块buffer。texture的创建在OPENGL ES 2.0 知识串讲 (9) ——OPENGL ES 详解III(纹理)一节已经说的很清楚了,那么在这里就说,如何通过glFramebufferTexture2D这个API将texture attach到FBO上。

OpenGL ES支持把framebuffer/FBO的内容通过APIglCopyTexImage2D copy到texture。那么在这里,其实OpenGL ES也支持直接往texture里面绘制内容,只要将该texture attach到FBO上即可。这个函数的第一个输入参数的意思是指定 framebuffer object 的类型,framebuffer object目前只有一种类似,那就是GL_FRAMEBUFFER。那么在这里,第一个输入参数必须是 GL_FRAMEBUFFER 。如果传入其他的参数,就会报 GL_INVALID_ENUM 的错误。如果当前GPU没有指定的FBO,也就是说当前GPU使用的是window-system-provided framebuffer的话,则会出现 GL_INVALID_OPERATION 的错误。第二个参数必须是FBO的3个挂载点之一,GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT or GL_STENCIL_ATTACHMENT,否则,则会出现GL_INVALID_ENUM 的错误。第三个参数textarget、第四个参数texture和第五个参数level相关,如果第四个参数为0,那么第三个参数和第五个参数就无所谓了,这样的话会把当前FBO的attachment point上attach的东西进行detach。但是如果第四个参数为一个非0的,而且指向一个2D的texture,那么第三个参数必须为GL_TEXTURE_2D ,否则则会出现 GL_INVALID_OPERATION 的错误。而如果第四个参数不是0,而且指向一个Cubemap的texture,那么第三个参数必须为GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,否则则会出现GL_INVALID_OPERATION的错误。如果第四个参数不是0,也不是一个已有的texture name,则会出现 GL_INVALID_OPERATION 的错误。如果第四个参数不是0,且第三个参数不是GL_TEXTURE_2D,GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,则会出现 GL_INVALID_ENUM 的错误。第五个参数level指的是该texture的mipmap,在这里必须为0,否则则会出现GL_INVALID_VALUE的错误。

这个函数没有输出参数,如果参数都无误,其中texture不为0,则该texture就被attach到当前FBO的attachment point上了。GL的状态GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE就被设为GL_TEXTURE,而GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME则会被设置为第四个参数texture了,GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL也就会被设置为第五个参数level,也就还是0。如果传入的texture为cubemap的,则GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE也就被设置为第三个参数textarget了。其他attachment point的状态不变。当然,如果这个API执行出错,则所有状态都不变。

需要注意的是,如果某个texture被delete掉了,那么首先会把这个texture从当前FBO的attachment point上detach掉(如果之前attach了的话)。但是如果该texture还被attach到了其他FBO上,那么那些FBO上的该texture不会自动detach,需要应用程序自己去完成这个工作。

还有一点需要注意,被attach到当前FBO上的texture,不能作为shader的输入texture,否则就会出现从texture中读取信息,然后再写入该texture的问题。这种情况虽然看起来FBO是正常的,但是其实写入FBO的时候会出现undefined,读取texture也会出现undefined。除非第五个参数为0,而GL_TEXTURE_MIN_FILTER 不为 GL_NEAREST 和 GL_LINEAR,而是GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, or GL_LINEAR_MIPMAP_LINEAR,而且真的没有采到纹理的第0层mipmap。(Patrick:这一点需要测试一下)

类似的还有另外一种情况可能发生,就是一个texture作为当前FBO的color attachment,然后从这个FBO通过APIglCopyTexImage2D把color attachment内容读取到这个texture中,并且glCopyTexImage2D的第2个参数level等于attach到FBO的那个level,也就是0(否则应该不会有问题,(Patrick:这一点也需要测试一下)),那么又出现了从一个texture又读又写的情况,那么写入texture就会出现undefined的结果。

GLenum glCheckFramebufferStatus(GLenum target);

当FBO准备好了之后,就可以被使用了,可以用glCheckFramebufferStatus这个API确认FBO是否准备好,然而FBO准备好,需要满足如下两个条件之一。

  • GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 为 GL_NONE,也就是没有东西attach到FBO上。(Patrick:这一点需要测试一下)
  • GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 不为 GL_NONE,并且attach的buffer宽高非0,且RBO/texture格式匹配(GL_COLOR_ATTACHMENT0 format为GL_RGBA4/GL_RGB5_A1/GL_RGB565,GL_DEPTH_ATTACHMENT format为GL_DEPTH_COMPONENT16,GL_STENCIL_ATTACHMENT format为GL_STENCIL_INDEX8)。也就是说,至少有一个RBO/texture attach到FBO上,否则就会出现GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT的错误。这个attach还必须是没问题的,否则就会出现GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT的错误(比如FBO上attach的某个RBO/texture被delete掉了,或者width\height为0,或者格式不匹配)。所有attach的RBO/texture的尺寸必须完全一致,否则就会出现GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS的错误。几个attachment的组合不违背平台的设计,否则就会出现GL_FRAMEBUFFER_UNSUPPORTED(Patrick:这一点从没关注过,也无所谓吧)

当然,如果使用windows-system-provided framebuffer,肯定是framebuffer complete。

如下操作会改变framebuffer complete的状态:

由于framebuffer是否complete很重要,所以建议在绘制之前调用一下这个API。这个函数的第一个输入参数必须是 GL_FRAMEBUFFER 。如果传入其他的参数,就会报 GL_INVALID_ENUM 的错误

如果这个函数的执行出现错误,则返回0。如果framebuffer complete,则返回GL_FRAMEBUFFER_COMPLETE。如果framebuffer非complete,则返回相应的enum。

如果framebuffer不complete,那么使用当前FBO去读操作(glReadPixels, glCopyTexImage2D, glCopyTexSubImage2D)和写操作(glClear, glDrawArrays, glDrawElements),就会出现GL_INVALID_FRAMEBUFFER_OPERATION 的错误。

void glDeleteFramebuffers(GLsizei n, const GLuint * framebuffers);

当某个 FBO 不再被需要的时候,则可以通过 glDeleteFramebuffers 这个 API 把 framebuffer object name 删除。

这个函数输入参数的意思是该 API 会删除 n 个 framebuffer object,当 n 小于 0 的 时候,出现 GL_INVALID_VALUE 的错误。 framebuffer 保存的就是这些 framebuffer object 的变量名。如果传入的变量名为 0,或者对应的不是一个合法的 framebuffer object,那么 API 就会被忽略掉。

这个函数没有输出参数。当 framebuffer object 被删除之后,其中attach的物件会被自动detach,名字也会被释放,可以被 glGenFramebuffers 重新使用。如果被删除的 framebuffer 正在处于 bind 状态,那么就相当于先执行了一次 glBindFramebuffer 把GPU当前的 framebuffer 变回使用window-system-provided framebuffer,然后再进行删除。

void glDeleteRenderbuffers(GLsizei n, const GLuint * renderbuffers);

当某个 RBO 不再被需要的时候,则可以通过 glDeleteRenderbuffers 这个 API 把 renderbuffer object name 删除。

这个函数输入参数的意思是该 API 会删除 n 个 renderbuffer object,当 n 小于 0 的 时候,出现 GL_INVALID_VALUE 的错误。 renderbuffer 保存的就是这些 renderbuffer object 的变量名。如果传入的变量名为 0,或者对应的不是一个合法的 renderbuffer object,那么 API 就会被忽略掉。

这个函数没有输出参数。当 renderbuffer object 被删除之后,其内容会被全部清空,所占用的空间也会被全部释放,名字也会被释放,可以被 glGenRenderbuffers 重新使用。如果被删除的 renderbuffer 正在处于 bind 状态,那么就相当于先执行了一次 glBindRenderbuffer 把GPU当前的 RBO 变成 binging 0,也就相当于什么都没有 bind 了,然后再进行删除。另外就是,当这个RBO被attach到当前FBO的时候,删除这个RBO会自动从当前FBO detach,导致当前FBO framebuffer incomplete,所以需要格外注意。

本节教程就到此结束,希望大家继续阅读我之后的教程。

谢谢大家,再见!


原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2017 年 12 月 11 日发表,原文链接(http://geekfaner.com/shineengine/blog12_OpenGLESv2_11.html)