OpenGLES帧缓冲FBO

FBO

Frame Buffer object

为什么要用FBO

我们需要对纹理进行多次渲染采样时,而这些渲染采样是不需要展示给用户看的,所以我们就可以用一个单独的缓冲对象(离屏渲染)来存储我们的这几次渲染采样的结果,等处理完后才显示到窗口上

优势

提高渲染效率,避免闪屏,可以很方便的实现纹理共享等。

渲染方式

  1. 渲染到纹理(Texture)- 图像渲染
  2. 渲染到缓冲区(Render)- 深度测试和模板测试

FBO纹理的坐标系

FBO坐标系

渲染到纹理

工作流程

创建FBO的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

//1. 创建FBO
int[] fbos = new int[1];
GLES20.glGenFramebuffers(1, fbos, 0);
fboId = fbos[0];

//2. 绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);

//3. 创建FBO纹理
fboTextureId = createTexture();

//4. 把纹理绑定到FBO
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, fboTextureId, 0);

//5. 设置FBO分配内存大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,bitmap.getWidth(), bitmap.getHeight(),0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

//6. 检测是否绑定从成功
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)!= GLES20.GL_FRAMEBUFFER_COMPLETE) {
Log.e("zzz", "glFramebufferTexture2D error");
}

//7. 解绑纹理和FBO
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

使用FBO的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1. 绑定fbo
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);

//2. FBO绘制
GLES20.glUseProgram(program);
//绑定渲染纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTextureId);
//...
//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
//解绑fbo
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

//3. 根据绑定到fbo上的纹理id,渲染
GLES20.glUseProgram(program);
//绑定渲染纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//...

示例代码如下:

TexureRender.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

import android.content.Context;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;

public class TexureRender implements EglSurfaceView.Renderer {
private BitmapFboTexture bitmapFboTexture;
private BitmapRenderTexture bitmapRenderTexture;

public TexureRender(Context context) {
bitmapFboTexture = new BitmapFboTexture(context);
bitmapFboTexture.setBitmap(BitmapFactory.decodeResource(context.getResources(),R.mipmap.bg));

bitmapRenderTexture = new BitmapRenderTexture(context);
}

@Override
public void onSurfaceCreated() {
bitmapFboTexture.onSurfaceCreated();
bitmapRenderTexture.onSurfaceCreated();
}

@Override
public void onSurfaceChanged(int width, int height) {
//宽高
GLES20.glViewport(0, 0, width, height);

bitmapFboTexture.onSurfaceChanged(width, height);
bitmapRenderTexture.onSurfaceChanged(width, height);
}

@Override
public void onDrawFrame() {
//清空颜色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//设置背景颜色
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
//FBO处理
bitmapFboTexture.draw();
//通过FBO处理之后,拿到纹理id,然后渲染
bitmapRenderTexture.draw(bitmapFboTexture.getFboTextureId());
}
}

FBO处理类: BitmapFboTexture.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


//纹理 根据坐标系映射
public class BitmapFboTexture {


//顶点坐标
static float vertexData[] = { // in counterclockwise order:
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
-1f, 1f, 0.0f, // top left
1f, 1f, 0.0f, // top right
};

//正常纹理坐标 对应顶点坐标 与之映射
// static float textureData[] = { // in counterclockwise order:
// 0f, 1f, 0.0f, // bottom left
// 1f, 1f, 0.0f, // bottom right
// 0f, 0f, 0.0f, // top left
// 1f, 0f, 0.0f, // top right
// };

//fbo 纹理坐标
static float textureData[] = { // in counterclockwise order:
0f, 0f, 0.0f, // bottom left
1f, 0f, 0.0f, // bottom right
0f, 1f, 0.0f, // top left
1f, 1f, 0.0f, // top right
};

//每一次取点的时候取几个点
static final int COORDS_PER_VERTEX = 3;

private final int vertexCount = vertexData.length / COORDS_PER_VERTEX;
//每一次取的总的点 大小
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex


private Context context;

//位置
private FloatBuffer vertexBuffer;
//纹理
private FloatBuffer textureBuffer;
private int program;
private int avPosition;
//纹理位置
private int afPosition;
//需要渲染的纹理id
private int imageTextureId;
//fbo纹理id
private int fboTextureId;
//fbo Id
private int fboId;

private Bitmap bitmap;

public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}

public BitmapFboTexture(Context context) {
this.context = context;

vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);

textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureData);
textureBuffer.position(0);
}


public void onSurfaceCreated() {
String vertexSource = ShaderUtil.readRawTxt(context, R.raw.vertex_shader);
String fragmentSource = ShaderUtil.readRawTxt(context, R.raw.fragment_shader);
program = ShaderUtil.createProgram(vertexSource, fragmentSource);

if (program > 0) {
//获取顶点坐标字段
avPosition = GLES20.glGetAttribLocation(program, "av_Position");
//获取纹理坐标字段
afPosition = GLES20.glGetAttribLocation(program, "af_Position");
createFBO();
imageTextureId = createImageTexture();
}
}

public void draw() {

//绑定fbo
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);

//使用程序
GLES20.glUseProgram(program);

//绑定渲染纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTextureId);

GLES20.glEnableVertexAttribArray(avPosition);
GLES20.glEnableVertexAttribArray(afPosition);
//设置顶点位置值
GLES20.glVertexAttribPointer(avPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//设置纹理位置值
GLES20.glVertexAttribPointer(afPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureBuffer);
//绘制 GLES20.GL_TRIANGLE_STRIP:复用坐标
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
GLES20.glDisableVertexAttribArray(avPosition);
GLES20.glDisableVertexAttribArray(afPosition);

//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

//解绑fbo
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}


private void createFBO() {
if (bitmap == null) {
throw new IllegalArgumentException("bitmap is null");
}

//1. 创建FBO
int[] fbos = new int[1];
GLES20.glGenFramebuffers(1, fbos, 0);
fboId = fbos[0];
//2. 绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);

//3. 创建FBO纹理
fboTextureId = createTexture();

//4. 把纹理绑定到FBO
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fboTextureId, 0);

//5. 设置FBO分配内存大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

//6. 检测是否绑定从成功
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)
!= GLES20.GL_FRAMEBUFFER_COMPLETE) {
Log.e("zzz", "glFramebufferTexture2D error");
}
//7. 解绑纹理和FBO
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}

private int createImageTexture() {
int[] textureIds = new int[1];
//创建纹理
GLES20.glGenTextures(1, textureIds, 0);
if (textureIds[0] == 0) {
return 0;
}
//绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
//环绕(超出纹理坐标范围) (s==x t==y GL_REPEAT 重复)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤(纹理像素映射到坐标点) (缩小、放大:GL_LINEAR线性)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

//测试图片
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textureIds[0];
}


private int createTexture() {
int[] textureIds = new int[1];
//创建纹理
GLES20.glGenTextures(1, textureIds, 0);
if (textureIds[0] == 0) {
return 0;
}
//绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
//环绕(超出纹理坐标范围) (s==x t==y GL_REPEAT 重复)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤(纹理像素映射到坐标点) (缩小、放大:GL_LINEAR线性)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
return textureIds[0];
}

public int getFboTextureId() {
return fboTextureId;
}

public void onSurfaceChanged(int width, int height) {

}
}

渲染类:BitmapRenderTexture.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

import android.content.Context;
import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


//纹理 根据坐标系映射
public class BitmapRenderTexture {


//顶点坐标
static float vertexData[] = { // in counterclockwise order:
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
-1f, 1f, 0.0f, // top left
1f, 1f, 0.0f, // top right
};

//纹理坐标 对应顶点坐标 与之映射
static float textureData[] = { // in counterclockwise order:
0f, 1f, 0.0f, // bottom left
1f, 1f, 0.0f, // bottom right
0f, 0f, 0.0f, // top left
1f, 0f, 0.0f, // top right
};

//每一次取点的时候取几个点
static final int COORDS_PER_VERTEX = 3;

private final int vertexCount = vertexData.length / COORDS_PER_VERTEX;
//每一次取的总的点 大小
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

private Context context;
//位置
private FloatBuffer vertexBuffer;
//纹理
private FloatBuffer textureBuffer;
private int program;
private int avPosition;
//纹理位置
private int afPosition;


public BitmapRenderTexture(Context context) {
this.context = context;

vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);

textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureData);
textureBuffer.position(0);
}


public void onSurfaceCreated() {
String vertexSource = ShaderUtil.readRawTxt(context, R.raw.vertex_shader);
String fragmentSource = ShaderUtil.readRawTxt(context, R.raw.fragment_shader);
program = ShaderUtil.createProgram(vertexSource, fragmentSource);

if (program > 0) {
//获取顶点坐标字段
avPosition = GLES20.glGetAttribLocation(program, "av_Position");
//获取纹理坐标字段
afPosition = GLES20.glGetAttribLocation(program, "af_Position");
}
}

public void draw(int textureId) {

//使用程序
GLES20.glUseProgram(program);

//绑定渲染纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

GLES20.glEnableVertexAttribArray(avPosition);
GLES20.glEnableVertexAttribArray(afPosition);
//设置顶点位置值
GLES20.glVertexAttribPointer(avPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//设置纹理位置值
GLES20.glVertexAttribPointer(afPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureBuffer);
//绘制 GLES20.GL_TRIANGLE_STRIP:复用坐标
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
GLES20.glDisableVertexAttribArray(avPosition);
GLES20.glDisableVertexAttribArray(afPosition);

//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}

public void onSurfaceChanged(int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
-------------The End-------------