【OpenGL ES】FBO离屏渲染
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 不太熟悉,请回顾以下内容:
本文完整代码资源见→ 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;
@Override
protected 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);
}
@Override
public 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">
<ImageView
android: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);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
//设置背景颜色
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//启动深度测试
gl.glEnable(GLES30.GL_DEPTH_TEST);
//创建程序id
mModel.onModelCreate();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mModel.onModelChange(width, height);
}
@Override
public 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 {
//创建程序id
public 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());
// 生成纹理id
GLES30.glGenTextures(2, textureId, 0);
for (int i = 0; i < 2; i++) {
// 绑定纹理到OpenGL
GLES30.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);
}
// 创建帧缓存id
GLES30.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离屏渲染
【OpenGL ES】FBO离屏渲染的更多相关文章
- NDK OpenGLES3.0 开发(五):FBO 离屏渲染
什么是 FBOFBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO). FBO 本身不能用于渲染,只有添加了纹理或者 ...
- Opengl ES之YUV数据渲染
YUV回顾 记得在音视频基础知识介绍中,笔者专门介绍过YUV的相关知识,可以参考: <音视频基础知识-YUV图像> YUV数据量相比RGB较小,因此YUV适用于传输,但是YUV图不能直接用 ...
- Android OpenGL ES 离屏渲染(offscreen render)
通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理.模型显示等.这种情况下,只需要使用Android API中提供的GLSurfaceView类和Rende ...
- Opengl ES之FBO
FBO介绍 FBO帧缓冲对象,它的主要作用一般就是用作离屏渲染,例如做Camera相机图像采集进行后期处理时就可能会用到FBO.假如相机出图的是OES纹理,为了方便后期处理, 一般先将OES纹理通过F ...
- OpenGL ES: (3) EGL、EGL绘图的基本步骤、EGLSurface、ANativeWindow
1. EGL概述 EGL 是 OpenGL ES 渲染 API 和本地窗口系统(native platform window system)之间的一个中间接口层,它主要由系统制造商实现. EGL提供如 ...
- Opengl ES之踩坑记
前因 最近在尝试使用Opengl ES实现一些LUT滤镜效果,在实现这些滤镜效果的时候遇到一些兼容性的坑,踩过这些坑后我希望把这几个坑分享给读者朋友们, 希望同在学习Opengl ES的朋友们能少走弯 ...
- Opengl ES之矩阵变换(上)
前言 说到矩阵变换,我们第一时间想到的就是大学时代的线性代数这些复杂的东西,突然有了一种令人从入门到放弃的念头,不慌,作为了一个应用层的CV工程师, 在实际应用中线性代数哪些复杂的计算根本不用我们自己 ...
- IOS 中openGL使用教程4(openGL ES 入门篇 | 离屏渲染)
通常情况下,我们使用openGL将渲染好的图片绘制到屏幕上,但有时候我们不想显示处理结果,这时候就需要使用离屏渲染了. 正常情况下,我们将屏幕,也就是一个CAEAGLLayer对象作为渲染目标,离屏渲 ...
- OpenGL ES 3.0之Fragment buffer objects(FBO)详解(二)
我们可以使用帧缓冲对象来实现离屏渲染.帧缓冲对象支持下列操作 1.只使用OpenGL ES 函数创建帧缓冲区对象. 2.使用EGL context创建多个FBO. 3.创建离屏颜色.深度.模板渲染缓冲 ...
- OpenGL ES 3.0之Fragment buffer objects(FBO)详解 (转)
http://www.cnblogs.com/salam/p/4957250.html 片段操作图 这篇文章将介绍从写入帧缓冲和读取帧缓冲的方式. Buffers(缓冲) OpenGL ES支持三种缓 ...
随机推荐
- 使用QQ屏幕识图实现识别表格功能
1.问题 目前市场上的OCR工具对于识别表格功能均是采取了收费制度,但我们时常要进行一些表格的复制(原表格为图片) 便可以使用QQ或钉钉自带的功能来实现 2.解决 1.QQ屏幕识图 先使用屏幕识图功能 ...
- [转帖]OceanBase实验4:迁移MySQL数据到OceanBase集群
服务器环境 1)12核48G,操作系统为centos 7.9系统,单节点三副本1-1-1集群. 2)源MySQL数据库:与OceanBase同一台服务器,版本为MySQL 5.7. 1.使用 mysq ...
- 【转帖】Alpaca 7B:斯坦福从LLaMA-7B微调的语言模型
https://www.jianshu.com/p/f8f8f660d2c3 https://crfm.stanford.edu/2023/03/13/alpaca.html https://crfm ...
- [转帖]Kibana查询语言(KQL)
时间 2020-12-27 标签 html java 数据库 ide ui 翻译 日志 htm 对象 blog 栏目 HTML 繁體版 原文 https://www.cnblogs.com/-b ...
- [转帖]一次操作系统报错OutOfMemory Error的处理记录
在启动公司内嵌的tomcat容器时出现报错, 如下: # There is insufficient memory for the Java Runtime Environment to contin ...
- [转帖]Cat导致内存不足原因分析
背景 线上几亿的数据在回刷的时候容器服务会出现OOM而重启,导致任务中断 内存泄露分析 jmap -histo pid 找出了有几十亿的java.lang.StackTraceElement对象,找不 ...
- pytest-assume插件-多重校验
自动化接口测试我们通常会对一条case设置多条断言,这样就会出现一个问题,如果前面一 个校验不通过,那么后面的校验就不会走到,如下图,可以看到校验走到assert False就不往 下走了,这个时候p ...
- 记一次 .NET某工控自动化系统 崩溃分析
一:背景 1. 讲故事 前些天微信上有位朋友找到我,说他的程序偶发崩溃,分析了个把星期也没找到问题,耗费了不少人力物力,让我能不能帮他看一下,给我申请了经费,哈哈,遇到这样的朋友就是爽快,刚好周二晚上 ...
- Vue2 里如何优雅的清除一个定时器
绝大多数人清除定时器的方法 <script> export default { data() { return { timer: null } }, mounted() { this.ti ...
- Map结构映射,避免每一个字段赋值
var query1 = (from fore in forecastShippingDate join ship in shipOutOfStock on fore.Id equals ship.F ...