回顾一下渲染管线流程图:
我们学习了顶点、片元着色器,还有纹理,其实可以渲染比较真实的物体了。后面的模板测试、深度测试、颜色混合、帧缓冲就是属于高级特征了。
模板测试
当片元着色器处理完片元之后,模板测试(Stencil Test) 就开始执行了,它能丢弃一些片元。模板测试基于模板缓冲(Stencil Buffer),允许在渲染时更新它来获取有意思的效果。
模板缓冲中的模板值(Stencil Value)通常是8位的,因此每个片元/像素共有256种不同的模板值。
上图很明确的说了模板测试的意图。通过测试的片元会被画出来,不通过就是丢弃。其实就是一个不规则的罩板。通过不通过可以自定义,一般情况下,我们设置0、1两个值表示通不过和通过。
模板测试最经典的例子水塘的倒影。
上图可以看到树比较大,水面较小,水面倒影只有一部分。只需要画出水面区域的倒影纹理。使用模板很容易做到。
使用模板测试的核心代码如下:
GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
GLES20.glEnable(GLES20.GL_STENCIL_TEST);
GLES20.glStencilFunc(GLES20.GL_ALWAYS,1,1);
GLES20.glStencilOp(GLES20.GL_KEEP,GLES20.GL_KEEP,GLES20.GL_REPLACE);
GLES20.glDisable(GLES20.GL_STENCIL_TEST);
深度测试
深度测试主要是以防止被遮挡的面渲染,可以提升效率。当深度测试启用的时候, OpenGL 测试深度缓冲区内的深度值。OpenGL 执行深度测试的时候,如果此测试通过,则渲染片元。如果深度测试失败,则丢弃该片元。操作和模板类似。
帧缓冲对象
用于写入颜色值的颜色缓冲,用于写入深度信息的深度缓冲,以及允许我们基于一些条件丢弃指定片段的模板缓冲。把这几种缓冲结合起来叫做帧缓冲(Framebuffer),它被储存于内存中。OpenGL给了我们自己定义帧缓冲的自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
我们目前所做的渲染操作都是在默认的帧缓冲之上进行的。当你创建了你的窗口的时候默认帧缓冲就被创建和配置好了。默认的framebuffer连接在屏幕上,表现就是会填充窗口提供的Surface.
当然我也可以通过创建我们自己的帧缓冲获得一种额外的渲染方式。这种渲染是offscreen渲染。
帧缓冲对象结构图
从结构图也可以看出来,framebuffer不是我们理解的buffer,是一个二维数据形式的一块内存。它是一个对象,其实是没有任何存储空间的。可以把它理解为一个插线板,他上面可以附加一些外设(包含两种:texture和render buffer),而这两种外设是需要分配内存的,同时一个framebuffer可以附加的外设的数量也是有限的,这个最大数量GL_MAX_COLOR_ATTACHMENTS与硬件相关,可以通过glget函数查询。
帧缓冲在相机中应用
我们相机的三方图像算法就是使用FBO把图像画到一张Texture上,然后让我们把这张Texture渲染到屏上的。还有一个应用就是获取预览数据做高斯模糊时,也用到了FBO,把预览数据渲染到一个Texture2D的纹理中,然后读出来。这样读取的速度比较快,实测快3-5倍,不同机器表现不一样。
实现代码如下:
public Bitmap getPreviewBitmapByFBO(SurfaceTexture texture, int textureId,
int width, int height, IPreviewCallbacker callbacker) {
Bitmap bitmap = null;
if (texture == null || textureId <= 0) {
return bitmap;
}
texture.getTransformMatrix(mTransformMatrix);
IntBuffer framebuffer = IntBuffer.allocate(1);
// IntBuffer maxRenderbufferSize = IntBuffer.allocate(1);
// IntBuffer depthRenderbuffer = IntBuffer.allocate(1);
IntBuffer texture2d = IntBuffer.allocate(1);
// GLES20.glGetIntegerv(GLES20.GL_MAX_RENDERBUFFER_SIZE, maxRenderbufferSize);
GLES20.glGenFramebuffers(1, framebuffer);
// GLES20.glGenRenderbuffers(1, depthRenderbuffer);
GLES20.glGenTextures(1, texture2d);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture2d.get(0));
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
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.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
// GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthRenderbuffer.get(0));
// GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
// width, height);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer.get(0));
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, texture2d.get(0), 0);
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) {
// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
boolean handled = callbacker != null ? callbacker.drawOffScreen(texture, textureId, width, height, framebuffer.get(0)) : false;
if (handled) {
bitmap = createBitmapFromFBO(0, 0, width, height);
}
Log.i(TAG, "getPreviewBitmapByFBO bitmap = " + bitmap);
if (bitmap == null) {
drawTexture(textureId);
bitmap = createBitmapFromFBO(0, 0, width, height);
}
}
// GLES20.glDeleteRenderbuffers(1, depthRenderbuffer);
GLES20.glDeleteFramebuffers(1, framebuffer);
GLES20.glDeleteTextures(1, texture2d);
return bitmap;
}
private Bitmap createBitmapFromFBO(int x, int y, int w, int h) {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
try {
GLES20.glReadPixels(x, y, w, h, GL10.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
Log.e(TAG, "createBitmapFromGLSurface: " + e.getMessage(), e);
return null;
}
Bitmap bmp = Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
if (IS_SAVE_FOR_TEST) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("/sdcard/screen.png");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
try {
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
return bmp;
}