学习OpenGL ES for Android(二十二)— 帧缓冲
我们学过的颜色缓冲,深度缓冲已经模板缓冲结合起来就叫帧缓冲。默认系统会定义一个帧缓冲(在移动端就是EGL创建的),而且我们还可以创建自定义的帧缓冲来替代系统创建的。大概的步骤如下:
- 创建一个帧缓冲并绑定;
- 正常绘制我们的图像(此时绘制的图像会绘制到自定义的帧缓冲上);
- 重新绑定到系统帧缓冲上;
- 绘制我们自定义帧缓冲的内容(此时我们可以对帧缓冲的内容进行各种处理以此实现各种效果)。
创建帧缓冲非常简单,使用以下方法
glGenFramebuffers( int n, int[] framebuffers, int offset ):n表示数量(通常是1);framebuffers用于存储创建后的帧缓冲;offset:偏移量;默认情况下我们只需要创建一个自定义的帧缓冲就可以了。
之后我们可以使用glBindFramebuffer( int target, int framebuffer )来绑定到自定义的帧缓冲;target必须是GLES20.GL_FRAMEBUFFER;framebuffer就是我们创建好的帧缓冲。
之后我们需要附加至少一个缓冲(颜色、深度或模板缓冲),至少有一个颜色附件(Attachment)。我们最常用的是纹理附件,我们绘制的结果会存储在一个纹理图像内,可以方便的使用和处理它。创建一个纹理附件和生成一个纹理非常相似,但是它不是使用GLUtils.texImage2D生成纹理,而是使用GLES20.glTexImage2D创建一个用来存储我们需要绘制的结果。我们把创建帧缓冲的过程封装一下,代码如下
public static int[] createFrameBuffer(int width, int height) {
int[] values = new int[1];
// 纹理缓冲
GLES20.glGenTextures(1, values, 0);
int mOffscreenTexture = values[0]; // expected > 0
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);
// 创建纹理存储。
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
// 设置参数。
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
// 自定义帧缓冲
GLES20.glGenFramebuffers(1, values, 0);
int mFramebuffer = values[0]; // expected > 0
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer);
// 深度缓冲
GLES20.glGenRenderbuffers(1, values, 0);
int mDepthBuffer = values[0]; // expected > 0
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);
// 为深度缓冲区分配存储空间。
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
width, height);
// 将深度缓冲区和纹理(颜色缓冲区)附加到帧缓冲区对象。
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, mDepthBuffer);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);
// 判断是否创建成功
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
// 未创建成功
throw new RuntimeException("Framebuffer not complete, status=" + status);
}
// 切换到默认缓冲
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
return new int[]{mOffscreenTexture, mFramebuffer, mDepthBuffer};
}
代码中的glBindRenderbuffer是创建一个渲染缓冲区,使用glFramebufferRenderbuffer和帧缓冲绑定之后,可以减少内存的浪费。
下面是使用帧缓冲的绘制流程关键代码
@Override
public void onDrawFrame(GL10 gl) {
int[] result = OpenGLUtil.createFrameBuffer(disWidth, disHeight);
frameBufferTexture = result[0];
frameBuffer = result[1];
renderBuffer = result[2];
// 绑定到我们自定义的帧缓冲
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer);
super.onDrawFrame(gl);
drawFloor();
drawCube();
// 重新绑定到系统的帧缓冲
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// 绘制我们自定义帧缓冲的内容
drawFrameBuffers();
release();
}
我们绘制了地板和两个箱子,不做任何处理的情况下显示效果如下,
就像我们把两张图片合成了一张然后再显示。然后我们可以对“合成后的图像”进行各种后期处理了。
反相效果,我们用1减去颜色即可,修改着色器代码
……
main(){
vec4 tex = texture2D(texture, TextCoord);
gl_FragColor = vec4(vec3(1.0 - tex.rgb), 1.0);
}
显示效果如下,
灰度:移除场景中除了黑白灰以外所有的颜色,让整个图像灰度化(Grayscale)。正常情况对颜色值平均值即可,也可以用加权来处理。虽然可能看不出差别,但是在一些更复杂的情况下效果会比较好。着色器代码如下,
……
void main() {
vec4 tex = texture2D(texture, TextCoord);
float average = (tex.r + tex.g + tex.b) / 3.0;
// 加权
//float average = 0.2126 * tex.r + 0.7152 * tex.g + 0.0722 * tex.b;
gl_FragColor = vec4(average, average, average, 1.0);
}
显示效果
核效果,模糊和边缘检测:这三种效果都是需要一个核(Kernel)的数组,它类似矩阵,需要当前像素点和它周围八个像素点的处理效果,当然还需要这些像素点的坐标位置offsets。在这里我们在java中处理好数据后再传入到着色器中,减小着色器的计算压力。修改后的着色器代码
……
uniform float kernel[9];
uniform vec2 offsets[9];
void main() {
vec4 sum = vec4(0.0);
for (int i = 0; i < 9; i++){
vec4 texc = texture2D(texture, TextCoord + offsets[i]);
sum += texc * kernel[i];
}
gl_FragColor = sum;
}
这些显示效果如下
当然我们还可以只在指定的范围内做处理,在其他范围内不做处理。
关于帧缓冲更详细的内容可以参考https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/05%20Framebuffers/,本章源码https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/renderer/advanced/opengl/FrameBuffersRenderer.java