1 前言

OpenGL 默认把 framebuffer 当作渲染目的地,它由窗口系统创建并管理。应用程序也可以创建额外非可显示的 framebuffer object(FBO),以区别窗口系统提供的 framebuffer。OpenGL 应用程序可以重定向渲染目的地,让它输出到 FBO 而不是窗口系统提供的 framebuffer。

与窗口系统提供的 framebuffer 类似,FBO 包含一系列渲染目的地:颜色缓冲区(color buffer)、深度缓冲区(depth buffer)、模板缓冲区(stencil buffer),FBO 中的这些逻辑缓冲区称为附着点,颜色附着点可以有多个,深度附着点和模板附着点只有一个,FBO 具有多个颜色附加点的原因是允许在同一时间将颜色缓冲区渲染到多个目的地。FBO本身不存放数据,它只有多个附着点。

OpenGL 中有两种可附着的 framebuffer:纹理(texture)、renderbuffer。如果纹理被附着到 FBO,OpenGL 将执行“渲染到纹理”。如果 renderbuffer 被附着到 FBO,则 OpenGL 将执行“离屏渲染”。渲染到纹理的一种传统方法是先绘制到缓冲区,再使用 glCopyTexSubImage2D() 将 framebuffer 图像复制到纹理,FBO 将场景直接渲染到纹理,消除了额外的数据拷贝(从帧缓存到纹理)。

离屏渲染是指:在后台渲染数据,处理完成后再送显到前台。原理是:在一块内存中渲染数据,处理完成后再取出来送显到前台。好处有:避免花屏,提升渲染效率。

本文将使用 ImageView 显示离屏渲染后的图片(将原图片变灰)。由于 Renderer 需要一个 GLSurfaceView 调用 requestRender() 方法驱动渲染,但是本文又不将离屏渲染后的图片送显给 GLSurfaceView,因此将 GLSurfaceView 的宽高都设置为1,并且设置为透明的。如果不使用 GLSurfaceView,可以参考:EGL+FBO离屏渲染。

读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

  • 绘制三角形​​​​​​
  • ​​​​​​绘制立方体
  • MVP矩阵变换
  • 透视变换原理

  • 纹理贴图
  • 正方形图片贴到圆形上
  • 凸镜贴图

本文完整代码资源见→ FBO离屏渲染

项目目录如下:

2 案例

MainActivity.java

package com.zhyan8.offscreen.activity;import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.offscreen.R;
import com.zhyan8.offscreen.model.Model;
import com.zhyan8.offscreen.opengl.MyGLSurfaceView;
import com.zhyan8.offscreen.opengl.MyRender;public class MainActivity extends AppCompatActivity implements Model.Callback {private FrameLayout mRootView;private ImageView mImageView;private MyGLSurfaceView mGlSurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mRootView = findViewById(R.id.rootView);mImageView = findViewById(R.id.imageView);initGLView();}private void initGLView() {mGlSurfaceView = new MyGLSurfaceView(this);MyRender render = new MyRender(getResources());render.setCallback(this);mGlSurfaceView.init(render);mGlSurfaceView.layout(mRootView);}@Overridepublic void onCall(final Bitmap bitmap) {runOnUiThread(() -> {mImageView.setImageBitmap(bitmap);});}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/rootView"><ImageViewandroid:id="@+id/imageView"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitCenter"/></FrameLayout>

MyGLSurfaceView.java

package com.zhyan8.offscreen.opengl;import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.WindowManager;public class MyGLSurfaceView extends GLSurfaceView {public MyGLSurfaceView(Context context) {super(context);}public MyGLSurfaceView(Context context, AttributeSet attrs) {super(context, attrs);}public void init(Renderer renderer) {setEGLContextClientVersion(3);setEGLConfigChooser(8, 8, 8, 8, 16, 0);getHolder().setFormat(PixelFormat.TRANSLUCENT);setZOrderOnTop(true);setRenderer(renderer);}public void layout(ViewGroup parent) {WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.width = 1;params.height = 1;parent.addView(this, params);}
}

MyRender.java

package com.zhyan8.offscreen.opengl;import android.content.res.Resources;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import com.zhyan8.offscreen.model.Model;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;public class MyRender implements GLSurfaceView.Renderer {private Model mModel;public MyRender(Resources resources) {mModel = new Model(resources);}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {//设置背景颜色GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);//启动深度测试gl.glEnable(GLES30.GL_DEPTH_TEST);//创建程序idmModel.onModelCreate();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mModel.onModelChange(width, height);}@Overridepublic void onDrawFrame(GL10 gl) {// 将颜色缓存区设置为预设的颜色GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);// 启用顶点的数组句柄GLES30.glEnableVertexAttribArray(0);GLES30.glEnableVertexAttribArray(1);// 绘制模型mModel.onModelDraw();// 禁止顶点数组句柄GLES30.glDisableVertexAttribArray(0);GLES30.glDisableVertexAttribArray(1);}public void setCallback(Model.Callback callback) {mModel.setCallback(callback);}
}

Model.java

package com.zhyan8.offscreen.model;import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.opengl.GLES30;
import com.zhyan8.offscreen.R;
import com.zhyan8.offscreen.utils.ArraysUtils;
import com.zhyan8.offscreen.utils.ShaderUtils;
import com.zhyan8.offscreen.utils.TextureUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;public class Model {private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度private Callback mCallback;private Resources mResources;private float mVertex[] = {-1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f};private float[] mFboTexture = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f};protected FloatBuffer mVertexBuffer;protected FloatBuffer mFboTextureBuffer;// 帧缓冲对象 - 颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点private int[] mFrameBufferId = new int[1];private int[] mTextureId = new int[2];private int mProgramId;private Point mBitmapSize = new Point();public Model(Resources resources) {mResources = resources;mVertexBuffer = ArraysUtils.getFloatBuffer(mVertex);mFboTextureBuffer = ArraysUtils.getFloatBuffer(mFboTexture);}// 模型创建public void onModelCreate() {mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);TextureUtils.loadTexture(mResources, R.raw.xxx, mBitmapSize, mTextureId, mFrameBufferId);}// 模型参数变化public void onModelChange(int width, int height) {GLES30.glViewport(0, 0, mBitmapSize.x, mBitmapSize.y);}// 模型绘制public void onModelDraw() {GLES30.glUseProgram(mProgramId);//准备顶点坐标和纹理坐标GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVertexBuffer);GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mFboTextureBuffer);//激活纹理GLES30.glActiveTexture(GLES30.GL_TEXTURE);//绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId[0]);// 绑定缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferId[0]);// 绘制贴图GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);showBitmap();}private void showBitmap() {// 分配字节缓区大小, 一个像素4个字节ByteBuffer byteBuffer = ByteBuffer.allocate(mBitmapSize.x * mBitmapSize.y * 4);GLES30.glReadPixels(0, 0, mBitmapSize.x, mBitmapSize.y, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer);Bitmap bitmap = Bitmap.createBitmap(mBitmapSize.x, mBitmapSize.y, Bitmap.Config.ARGB_8888);// 从缓存区读二进制缓冲数据bitmap.copyPixelsFromBuffer(byteBuffer);// 回调mCallback.onCall(bitmap);}public void setCallback(Callback callback) {mCallback = callback;}public interface Callback{void onCall(Bitmap bitmap);}
}

注意: 纹理贴图 中为防止显示的图片变形,对顶点坐标进行了正交投影变换;本文案例中却没有这样做,但显示的图片也未变形,主要因为缓存中的数据还未送显到屏幕,不知道屏幕尺寸,不必进行缩放调整。

ShaderUtils.java

package com.zhyan8.offscreen.utils;import android.content.res.Resources;
import android.opengl.GLES30;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;public class ShaderUtils {//创建程序idpublic static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);return linkProgram(vertexShaderId, fragmentShaderId);}//通过外部资源编译着色器private static int compileShader(Resources resources, int type, int shaderId){String shaderCode = readShaderFromResource(resources, shaderId);return compileShader(type, shaderCode);}//通过代码片段编译着色器private static int compileShader(int type, String shaderCode){int shader = GLES30.glCreateShader(type);GLES30.glShaderSource(shader, shaderCode);GLES30.glCompileShader(shader);return shader;}//链接到着色器private static int linkProgram(int vertexShaderId, int fragmentShaderId) {final int programId = GLES30.glCreateProgram();//将顶点着色器加入到程序GLES30.glAttachShader(programId, vertexShaderId);//将片元着色器加入到程序GLES30.glAttachShader(programId, fragmentShaderId);//链接着色器程序GLES30.glLinkProgram(programId);return programId;}//从shader文件读出字符串private static String readShaderFromResource(Resources resources, int shaderId) {InputStream is = resources.openRawResource(shaderId);BufferedReader br = new BufferedReader(new InputStreamReader(is));String line;StringBuilder sb = new StringBuilder();try {while ((line = br.readLine()) != null) {sb.append(line);sb.append("\n");}br.close();} catch (Exception e) {e.printStackTrace();}return sb.toString();}
}

TextureUtils.java

package com.zhyan8.offscreen.utils;import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.opengl.GLES30;
import android.opengl.GLUtils;public class TextureUtils {// 加载纹理贴图public static void loadTexture(Resources resources, int resourceId, Point bitmapSize, int[] textureId, int[] frameBufferId) {BitmapFactory.Options options = new BitmapFactory.Options();options.inScaled = false;Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());// 生成纹理idGLES30.glGenTextures(2, textureId, 0);for (int i = 0; i < 2; i++) {// 绑定纹理到OpenGLGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[i]);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);if (i == 0) {// 第一个纹理对象给渲染管线(加载bitmap到纹理中)GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0);} else {// 第二个纹理对象给帧缓冲区GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);}// 取消绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);}// 创建帧缓存idGLES30.glGenFramebuffers(1, frameBufferId, 0);// 绑定帧缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0]);// 将第二个纹理附着在帧缓存的颜色附着点上GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureId[1], 0);// 取消绑定帧缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);}
}

ArraysUtils.java

package com.zhyan8.offscreen.utils;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;public class ArraysUtils {public static FloatBuffer getFloatBuffer(float[] floatArr) {FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();fb.put(floatArr);fb.position(0);return fb;}
}

vertex_shader.glsl

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
out vec2 vTexCoord;
void main() {gl_Position  = vPosition;vTexCoord = aTextureCoord;
}

fragment_shader.glsl

#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {vec4 color = texture(uTextureUnit, vTexCoord);float rgb = color.g;vec4 c = vec4(rgb, rgb, rgb, color.a);fragColor = c;
}

3 运行效果

原图:

处理后:

【OpenGL ES】FBO离屏渲染相关推荐

  1. OpenGL ES之离屏渲染的帧缓冲区对象FBO的说明和使用

    一.什么是 FBO ? FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO). FBO 本身不能用于渲染,只有添加 ...

  2. Android OpenGL ES FrameBuffer离屏渲染

    作用 FrameBuffer Object,也称FBO,离屏渲染,可以摆脱屏幕的束缚,在后台做图像处理. 理解 FrameBuffer和Texture绑定,FrameBuffer犹如画板,而Textu ...

  3. OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)

    OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突) 1.水印签名罢工了? 不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区 ...

  4. WebGL—实现使用FBO离屏渲染(亦同拷贝纹理)off-screen rendering的两种方式

    1.离屏渲染使用场景: 1.游戏中的小地图: 2.画中画场景: 3.游戏中观战模式的多场景场合: 4.镜像场景--比如汽车游戏当中的倒车镜,采用的就是离屏渲染技术,在倒车镜上安装一个摄像机,把摄像机渲 ...

  5. iOS之从OpenGL深入探究离屏渲染及性能优化

    一.探究内容 到底什么是离屏渲染?是在GPU上面还是CPU上面执行的? 为什么要有离屏渲染?什么情况下会产生离屏渲染? 帧缓冲区是什么?当前屏幕缓冲区和屏幕外缓冲区又是什么? 切换缓冲区是什么操作?真 ...

  6. (01)OpenGL es中只在指定区域渲染view

    效果 图1 右侧显示view的内容 主要代码 (1)main_activity.java的内容 import androidx.appcompat.app.AppCompatActivity; imp ...

  7. OpenGL ES 3.0管线渲染流程

    OpenGL ES 3.0 实现了具有可编程着色功能的图形管线,由两个规范组成: OpenGL ES 3.0 API规范 OpenGL ES 着色语言3.0规范 下图概述了OpenGL ES 3.0 ...

  8. Android OpenGL ES 离屏渲染(offscreen render)

    通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理.模型显示等.这种情况下,只需要使用Android API中提供的GLSurfaceView类和Rende ...

  9. Android VR Player(全景视频播放器) [10]: VR全景视频渲染播放的实现(exoplayer,glsurfaceview,opengl es)

    前言 此博客的大部分内容来自我的毕业设计论文,因此语言上会偏正式一点,如果您有任何问题或建议,欢迎留言.在此感谢实验室的聂师兄,全景视频render部分的代码设计主要参考了他所编写的代码来完成,他对视 ...

  10. 基于Surface的视频编解码与OpenGL ES渲染

    http://blog.csdn.net/gh_home/article/details/52399959 1. 概述 这篇文章所做的事情是这样的:  1. 从一个.mp4文件中解码视频流到surfa ...

最新文章

  1. memset初始化内存
  2. 一不小心,我们办了场全球最 _____ 的技术大会......
  3. shiro中文api_Shiro
  4. 前端:HTML/07/综合案例:月福首页,开发网站的流程,网站布局结构,排版准备,图片热点,网页多媒体
  5. 并发减库存,怎么保证不超卖?
  6. 冲突域和CSMA/CD
  7. 信息安全之程序实现简单替换加密,并用字母频率统计进行破解
  8. atitit.添加win 系统服务 bat批处理程序服务的法总结instsrv srvany java linux
  9. android开发笔记之多媒体—播放音频(音乐)
  10. ios定位权限plist_iOS(定位一)后台定位和前台定位权限设置
  11. CAD图纸的缩放——范围缩放
  12. 全国各省市区县数据整理
  13. NOIP 2016 滚粗记
  14. C++排序求最值函数的调用
  15. 51单片机定时器中断按键消抖(无延时)
  16. c语言中英文字母的符号,C语言中的符号(国外英文资料).doc
  17. 默认ip_各品牌路由器登录网址大全 路由器默认用户名/密码
  18. 凹凸贴图(Bump Mapping)
  19. 机械臂正运动学-DH参数-Python快速实现
  20. 基于MATLAB交通标志识别系统

热门文章

  1. Connection error: QRedisClient compiled without ssh support
  2. 学 C++ ,能不能简单点?
  3. 如何启用计算机的远程服务,如何启用远程Windows命令行管理程序
  4. this.$nextTick() 学(cai)习(keng)
  5. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
  6. 李开复--生命是最严厉的导师
  7. 剑指offer-二叉树中值等于某个数的路径
  8. Android网易云信无脑接入(IM+音视频)【网易云信】
  9. 【SSL】谷仓的安保
  10. Disparity Map