草庐IT

【OpenGL ES】FBO离屏渲染

little_fat_sheep 2023-06-07 原文

1 前言

        OpenGL 默认把 framebuffer 当作渲染目的地,它由窗口系统创建并管理。应用程序也可以创建额外非可显示的 framebuffer objectFBO),以区别窗口系统提供的 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离屏渲染的更多相关文章

  1. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  2. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  3. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  4. ruby-on-rails - Rails 渲染带有驼峰命名法的 json 对象 - 2

    我在一个简单的RailsAPI中有以下Controller代码:classApi::V1::AccountsControllerehead:not_foundendendend问题在于,生成的json具有以下格式:{id:2,name:'Simpleaccount',cash_flows:[{id:1,amount:34.3,description:'simpledescription'},{id:2,amount:1.12,description:'otherdescription'}]}我需要我生成的json是camelCase('cashFlows'而不是'cash_flows'

  5. ruby-on-rails - 使用 header 渲染 JSON - 2

    我想在我的Controller中使用以下corsheader呈现JSON:'Access-Control-Allow-Origin'='*'.我试过这个:defmy_actionrender(json:some_params)response.headers['Access-Control-Allow-Origin']='*'end但是我得到了一个AbstractController::DoubleRenderError。有没有办法使用header呈现JSON? 最佳答案 您不能在渲染后设置header,因为已发送响应。所以在没有意

  6. ruby-on-rails - Ruby 数组到 JSON 和 Rails JSON 渲染 - 2

    我有一个Ruby数组,如何在Rails3.0中将其呈现为JSONView?我的Controller方法是defautocomplete@question=Question.allend 最佳答案 如果自动完成操作仅呈现JSON,您可以将re5et的解决方案简化为:defautocompletequestions=Question.allrender:json=>questionsend(请注意,我将“问题”复数化以反射(reflect)它是一个数组并删除了@符号-一个局部变量就足够了,因为您可能只使用它来呈现内联JSON)作为一种附

  7. ruby-on-rails - 使用 ApplicationController.renderer.render 从 Controller 外部渲染的 Rails 5 不会在自身上设置变量 - 2

    我正在使用Rails5ApplicationController.renderer.render方法从模型中进行渲染。我需要将一些变量传递给我的布局,这是我使用locals选项完成的;如果直接访问此变量,则该变量在布局中可用,但不能通过self访问。这是我设置渲染的方式html_string=ApplicationController.renderer.render(file:"/#{template_path}/base/show",:formats=>[:pdf,:html],locals:{:@routing_form=>self,:controller_name=>contro

  8. ruby-on-rails - 为什么我的 Rails 服务器渲染时间不加起来? - 2

    我的Rails应用程序在暂存服务器上运行速度非常慢,这让我遇到了一些麻烦。最令人困惑的是每个请求的日志输出的最后一行。看起来View和数据库时间甚至不接近整个渲染时间。在一页上,完成时间大约1000毫秒,View大约450毫秒,数据库大约20毫秒。渲染页面所需的其余时间从何而来? 最佳答案 当事情变得神秘时......分析器是你的friend!分析器将统计哪些方法被调用最多以及每个方法调用花费多长时间。ruby-prof当我在RubyLand时,它会帮我解决这个问题,它会生成一个漂亮的调用图(如果需要,可以是html格式),这使得查

  9. ruby - Sinatra json 渲染没有按预期工作 - 2

    我在Sinatra中遇到问题,我无法仅使用json进行响应,而且我在任何地方都找不到好的sinatra文档,大部分内容似乎都已过时。无论如何,这是代码:moduleMemcachedManagerclassApp我得到的回应是:"\n{\"hello\":\"world\"}\n"它应该只是json部分的地方。为什么它在我没有要求时呈现html标签? 最佳答案 你见过thisblogpost吗??require'json'get'/example.json'docontent_type:json{:key1=>'value1',:k

  10. Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理) - 2

    快速导航(持续更新中…)Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理)Cesium源码解析二(metadataAvailability的含义)Cesium源码解析三(metadata元数据拓展中行列号的分块规则解析)Cesium源码解析四(Quantized-Mesh(.terrain)格式文件在CesiumJS和UE中加载情况的对比)目录1.前言2.本篇的由来3.terrain文件的加载3.1更新环境3.2更新和执行渲染命令3.3数据优化3.4结束当前帧4.总结1.前言  目前市场上三维比较火的实现方案主要有两种,b/s的方案主要是Cesium,c/s的方案主要是u

随机推荐