本节书摘来异步社区《OpenGL ES 3.x游戏开发(下卷)》一书中的第1章,第1.1节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。
上一节介绍了复制缓冲区对象以及从颜色缓冲区复制纹理数据这两种比较特殊的缓冲区操作,本节将介绍在开发中常用的帧缓冲和渲染缓冲,主要包括帧缓冲和渲染缓冲对象的基本知识以及利用帧缓冲与渲染缓冲实现二次绘制的案例。
本小节主要介绍帧缓冲和渲染缓冲对象的基本知识,主要包括帧缓冲和渲染缓冲对象的基本结构以及两者与纹理之间的关系、创建和使用帧缓冲与渲染缓冲、相关方法介绍3部分。
1.基本结构
帧缓冲对象(Framebuffer Object,FBO)是由颜色附件、深度附件、模板附件组成的,作为着色器各方面(一般包括颜色、深度、模板值)绘制结果存储的逻辑对象。渲染缓冲对象(Renderbuffer Object,RBO)是一个由应用程序分配的2D图像缓冲区,可以用于分配和存储颜色、深度或者模板值,可以用作帧缓冲区对象的颜色、深度或者模板附件。二者之间的基本关系如图1-5所示。
从图1-5中可以看出,帧缓冲包括3种可选的附件:颜色附件、深度附件、模板附件,具体情况如下所列。
颜色附件一般用纹理图来实现,也可以使用渲染缓冲对象实现。
深度附件有两个选择,可以采用纹理图,也可以采用渲染缓冲对象,本节案例采用的是渲染缓冲对象。
模板附件仅能采用渲染缓冲对象来进行模板测试。
2.创建与使用帧缓冲与渲染缓冲对象
使用帧缓冲对象之前首先需要执行创建工作,对应的方法为glGenFramebuffers,其具体的方法签名如下。
1 public static void glGenFramebuffers (int n, IntBuffer framebuffers)
2 public static void glGenFramebuffers (int n, int[] framebuffers, int offset)
如果帧缓冲中的颜色或深度附件需要使用渲染缓冲对象,那么使用前也需要执行创建操作,对应的方法为glGenRenderbuffers,其具体的方法签名如下。
1 public static void glGenRenderbuffers (int n, IntBuffer renderbuffers)
2 public static void glGenRenderbuffers (int n, int[] renderbuffers, int offset)
创建完帧缓冲对象后,使用前需要进行绑定,对应的方法为glBindFramebuffer,其具体的方法签名如下。
1 public static void glBindFramebuffer (int target, int framebuffer)
说明
参数target表示指定帧缓冲绑定的类型,可选的值包括GL_FRAMEBUFFER、GL_READ_FRAMEBUFFER和GL_DRAW_FRAMEBUFFER。参数framebuffer为用glGenFramebuffers方法创建帧缓冲时得到的帧缓冲编号。
同样地,创建完渲染缓冲对象后,使用前也需要进行绑定,对应的方法为glBindRenderbuffer,其具体的方法签名如下。
1 public static void glBindRenderbuffer (int target, int renderbuffer)
说明
参数target表示指定渲染缓冲绑定的类型,可选的值仅包括GL_RENDERBUFFER。参数renderbuffer为用glGenRenderbuffers方法创建渲染缓冲时得到的渲染缓冲编号。
帧缓冲和渲染缓冲都创建完毕后,还需要把渲染缓冲作为帧缓冲的某种(颜色、深度、模板)附件,此时需要调用glFramebufferRenderbuffer方法,其具体的方法签名如下。
1 public static void glFramebufferRenderbuffer(
2 int target, //帧缓冲区类型
3 int attachment, //缓冲附件类型
4 int renderbuffertarget, //渲染缓冲区类型
5 int renderbuffer) //渲染缓冲编号
接下来介绍可以将2D纹理设置为自定义帧缓冲的某种(颜色、深度、模板)附件的glFramebufferTexture2D方法,其具体的方法签名如下。
1 public static void glFramebufferTexture2D (
2 int target, //帧缓冲区类型
3 int attachment, //缓冲附件类型
4 int textarget, //纹理类型
5 int texture, //纹理id
6 int level) //纹理层次
说明
该方法通过设置帧缓冲区类型、缓冲附件类型、纹理类型和纹理id等参数,实现了将2D纹理设置为自定义帧缓冲的某种(颜色、深度、模板)附件,其中最常用的是将2D纹理设置为自定义帧缓冲的颜色附件。
提示
实际开发中出于性能考虑有一点需要读者注意,创建缓冲后尽量重复使用,不要在程序运行过程中频繁地创建与删除缓冲。另外,删除帧缓冲以及渲染缓冲使用前面介绍过的glDeleteBuffers方法即可。
3.相关方法介绍
前文主要介绍了创建和使用帧缓冲和渲染缓冲对象的方法,下面将介绍关于清除缓冲区的相关方法和用掩码控制不同缓冲写入的相关方法,具体内容如表1-10和表1-11所列。
public static void glClearColor (float red, float green, float blue, float alpha)
该方法的功能为指定清除颜色缓冲时采用的颜色值,red、green、blue、alpha参数分别用于给出清除颜色四个色彩通道的值,取值范围为0~1。
public static void glClearDepthf (float depth)
该方法的功能为指定清除深度缓冲时采用的值,参数depth就是清除时采用的值
public static void glClearStencil (int s)
该方法的功能为指定清除模板缓冲时采用的值,参数s就是清除时采用的值
说明
表1-10中主要列出的是用于清除不同缓冲的相关方法,实际开发中主要是通过glClear方法清除指定的缓冲区,利用其他三个方法指定清除不同缓冲区时采用的值。
上一小节介绍了帧缓冲和渲染缓冲对象的相关知识,接下来本小节将利用自定义帧缓冲和渲染缓冲对象进行二次绘制案例的开发,在正式介绍案例的具体开发步骤之前,请读者先看看本小节案例的运行效果,具体情况如图1-6所示。
说明
图1-6中是一个通过帧缓冲和渲染缓冲经过二次绘制的茶壶,其中左侧的图是茶壶原始状态的情况,右侧是旋转了一定角度时的情况。
此案例首先将场景中的茶壶绘制到了自定义帧缓冲中颜色附件对应的纹理中,然后又将纹理应用到了与屏幕尺寸大小相同的一个纹理矩形上,实际在屏幕上看到的是纹理矩形上所贴的纹理图。因此仅从画面效果看,是看不出端倪的。下面来介绍本案例的开发,具体步骤如下。
提示
读者可能会有疑问:“直接绘制茶壶就行,何必如此费事?”对于仅仅绘制普通的茶壶场景而言,确实如此。但二次绘制技术是后面章节将要介绍的很多高级特效(如阴影贴图、运动模糊等)必不可少的实现技术支撑,因此这里有必要单独进行介绍。
(1)首先介绍场景渲染类MySurfaceView,由于该类实现的功能比较多,代码较长,所以先介绍该类的代码框架,具体内容如下。
1 package com.bn.Sample1_5; //声明包名
2 ……//此处省略了一些导入相关类的代码,读者可自行查阅随书附带中的源代码
3 class MySurfaceView extends GLSurfaceView{
4 ……//此处省略了一些定义成员变量的代码,读者可自行查阅随书附带中的源代码
5 public MySurfaceView(Context context) {/*此处省略了该类的构造器,读者可自行查阅随书源代码*/}
6 public boolean onTouchEvent(MotionEvent e) {/*此处省略了触控事件回调方法,读者可自行查阅源代码*/}
7 private class SceneRenderer implements GLSurfaceView.Renderer {
8 float yAngle; //绕Y轴旋转的角度
9 float xAngle; //绕X轴旋转的角度
10 LoadedObjectVertexNormalTexture lovo; //加载的茶壶绘制对象
11 int frameBufferId; //帧缓冲编号
12 int renderDepthBufferId; //深度渲染缓冲编号
13 int textureId; //生成的矩形纹理id
14 int textureIdGHXP; //国画小品的纹理id
15 TextureRect tr; //纹理矩形
16 public void initFRBuffers(){/*此处省略了初始化帧缓冲和渲染缓冲的方法,将在下面介绍*/}
17 public void generateTextImage(){/*此处省略了通过绘制产生矩形纹理的方法,将在下面介绍*/}
18 public void drawShadowTexture(){/*此处省略了绘制纹理矩形的方法,将在下面介绍*/}
19 public void onDrawFrame(GL10 gl){
20 generateTextImage(); //调用通过绘制产生矩形纹理方法
21 drawShadowTexture(); //调用绘制纹理矩形方法
22 }
23 public void onSurfaceChanged(GL10 gl, int width, int height){
24 ……//此处省略了一些初始化的代码,读者可自行查阅随书
25 }
26 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
27 ……//此处省略了一些初始化的代码,读者可自行查阅随书
28 }}
29 public int initTexture(int drawableId){/*此处省略了用于加载纹理的方法,读者可自行查阅*/}
30 }
1 public void initFRBuffers(){ //初始化帧缓冲和渲染缓冲的方法
2 int tia[]=new int[1]; //用于存放产生的帧缓冲编号的数组
3 GLES30.glGenFramebuffers(1, tia, 0); //创建一个帧缓冲
4 frameBufferId=tia[0]; //将帧缓冲编号记录到成员变量中
5 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId);//绑定帧缓冲
6 GLES30.glGenRenderbuffers(1, tia, 0); //创建一个渲染缓冲
7 renderDepthBufferId=tia[0]; //将渲染缓冲编号记录到成员变量中
8 //绑定指定的渲染缓冲
9 GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, renderDepthBufferId);
10 GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER,//为渲染缓冲初始化存储
11 GLES30.GL_DEPTH_COMPONENT16,GEN_TEX_WIDTH, GEN_TEX_HEIGHT);
12 int[] tempIds = new int[1]; //用于存放产生纹理id的数组
13 GLES30.glGenTextures (1,tempIds,0); //创建一个纹理
14 textureId=tempIds[0]; //将纹理id记录到成员变量中
15 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,textureId);//绑定纹理
16 ……//此处省略了设置纹理拉伸方式和采样方式的代码,读者可自行查阅随书
17 GLES30.glTexImage2D( //设置颜色附件纹理的格式
18 GLES30.GL_TEXTURE_2D, //纹理类型
19 0, //层次
20 GLES30.GL_RGBA, //内部格式
21 GEN_TEX_WIDTH, //宽度
22 GEN_TEX_HEIGHT, //高度
23 0, //边界宽度
24 GLES30.GL_RGBA, //格式
25 GLES30.GL_UNSIGNED_BYTE, //每个像素数据的格式
26 null);
27 GLES30.glFramebufferTexture2D( //设置自定义帧缓冲的颜色附件
28 GLES30.GL_FRAMEBUFFER, //帧缓冲类型
29 GLES30.GL_COLOR_ATTACHMENT0, //附件类型——颜色附件0
30 GLES30.GL_TEXTURE_2D, //附件为2D纹理
31 textureId, //纹理id
32 0); //纹理层次
33 GLES30.glFramebufferRenderbuffer ( //设置自定义帧缓冲的深度附件
34 GLES30.GL_FRAMEBUFFER, //帧缓冲类型
35 GLES30.GL_DEPTH_ATTACHMENT, //附件类型——深度附件
36 GLES30.GL_RENDERBUFFER, //附件为渲染缓冲
37 renderDepthBufferId); //深度渲染缓冲编号
38 }
1 public void generateTextImage(){ //通过绘制茶壶产生矩形纹理方法
2 GLES30.glViewport(0, 0, GEN_TEX_WIDTH, GEN_TEX_HEIGHT); //设置视口大小及位置
3 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId);//绑定自定义帧缓冲
4 //清除深度缓冲与颜色缓冲
5 GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
6 MatrixState.setProjectFrustum(-ratio, ratio, -1, 1, 2, 100); //设置透视投影
7 MatrixState.setCamera(0,0,0,0f,0f,-1f,0f,1.0f,0.0f);//调用此方法产生摄像机9参数矩阵
8 MatrixState.pushMatrix(); //保护现场
9 MatrixState.translate(0, -16f, -80f); //平移
10 MatrixState.rotate(yAngle, 0, 1, 0); //绕Y轴旋转
11 MatrixState.rotate(xAngle, 1, 0, 0); //绕X轴旋转
12 if(lovo!=null){ //若茶壶绘制对象不为空则绘制
13 lovo.drawSelf(textureIdGHXP); //绘制茶壶
14 }
15 MatrixState.popMatrix(); //恢复现场
16 }
17 public void drawShadowTexture(){ //绘制纹理矩形的方法
18 GLES30.glViewport(0,0,SCREEN_WIDTH,SCREEN_HEIGHT); //设置视口大小及位置
19 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); //绑定系统默认帧缓冲
20 //清除深度缓冲与颜色缓冲
21 GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT |GLES30.GL_COLOR_BUFFER_BIT);
22 MatrixState.setProjectOrtho(-ratio, ratio, -1, 1, 2, 100);//设置正交投影
23 MatrixState.setCamera(0,0,3,0f,0f,0f,0f,1.0f,0.0f); //调用此方法产生摄像机9参数矩阵
24 MatrixState.pushMatrix(); //保护现场
25 tr.drawSelf(textureId); //绘制纹理矩形
26 MatrixState.popMatrix(); //恢复现场
27 }
提示
到这里为止,如何通过自定义帧缓冲和渲染缓冲实现二次绘制的案例就介绍完了。由于篇幅有限,这里只介绍了案例中有代表性的内容,其他与前面案例中相似的部分有兴趣的读者可以自行查阅随书中的源代码。