【OpenGL ES】EGL+FBO离屏渲染

1 前言

        FBO离屏渲染 中使用 GLSurfaceView 来驱动 Renderer 渲染图片,为了隐藏 GLSurfaceView,将其设置为透明的,并且宽高都设置为1。本文将使用 EGL 代替 GLSurfaceView 生成 OpenGL ES 的渲染环境,实现离屏渲染,将渲染后的图片显示在 ImageView 上。

        EGL 为 OpenGL ES 提供了绘制表面(或渲染画布),是 OpenGL ES 与显示设备的桥梁让 OpenGL ES 绘制的内容能够在呈现当前设备上。

        EGL 环境创建分为以下5步:

        1)创建EGLDisplay

EGLDisplay mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] versions = new int[2];
EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);

        2)创建EGLConfig

int[] mEGLConfigAttrs = {
    EGL14.EGL_RED_SIZE, 8,
    EGL14.EGL_GREEN_SIZE, 8,
    EGL14.EGL_BLUE_SIZE, 8,
    EGL14.EGL_ALPHA_SIZE, 8,
    EGL14.EGL_DEPTH_SIZE, 8,
    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
    EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] configNum = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
EGLConfig mEGLConfig = configs[0];

        3)创建EGLContext

int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
EGLContext mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);

        4)创建EGLSurface

int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
EGLSurface mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);

        5)绑定EGLSurface和EGLContext到显示设备(EGLDisplay)

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

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

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

        项目目录如下: 

2 案例

        本案例实现了将彩色图片转换为灰色,并且使用 ImageView 显示转换后的图片。

        MainActivity.java

package com.zhyan8.egl.activity;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.egl.R;
import com.zhyan8.egl.model.Model;
import com.zhyan8.egl.opengl.MyEGLSurface;
import com.zhyan8.egl.opengl.MyRender;

public class MainActivity extends AppCompatActivity implements Model.Callback {
    private ImageView mImageView;
    private MyEGLSurface mEGlSurface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView = findViewById(R.id.imageView);
        initEGLSurface();
        mEGlSurface.requestRender();
    }

    private void initEGLSurface() {
        mEGlSurface = new MyEGLSurface(this);
        MyRender render = new MyRender(getResources());
        render.setCallback(this);
        mEGlSurface.init(render);
    }

    @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:background="#556688">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter"/>

</FrameLayout>

        BaseEGLSurface.java

package com.zhyan8.egl.opengl;

import android.content.Context;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class BaseEGLSurface {
    protected EGLDisplay mEGLDisplay;
    protected EGLConfig mEGLConfig;
    protected EGLContext mEGLContext;
    protected EGLSurface mEGLSurface;
    protected Context mContext;
    protected Renderer mRenderer;
    protected EglStatus mEglStatus = EglStatus.INVALID;
    protected int mWidth;
    protected int mHeight;

    public BaseEGLSurface(Context context) {
        mContext = context;
        WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
        mWidth = displayMetrics.widthPixels;
        mHeight = displayMetrics.heightPixels;
    }

    public BaseEGLSurface(Context context, int width, int height) {
        mContext = context;
        mWidth = width;
        mHeight = height;
    }

    // 设置渲染器
    public void setRenderer(Renderer renderer) {
        mRenderer = renderer;
    }

    // EGLDisplay宽高发生变化
    public void onSurfaceChanged(int width, int height) {
        mWidth = width;
        mHeight = height;
        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
        createSurface();
        mEglStatus = EglStatus.CREATED;
    }

    // 请求渲染
    public void requestRender() {
        if (mEglStatus == mEglStatus.INVALID) {
            return;
        }
        if (mEglStatus == EglStatus.INITIALIZED) {
            mRenderer.onSurfaceCreated();
            mRenderer.onSurfaceChanged(mWidth, mHeight);
            mEglStatus = EglStatus.CREATED;
        }
        if (mEglStatus == EglStatus.CREATED || mEglStatus == EglStatus.DRAW) {
            mRenderer.onDrawFrame();
            mEglStatus = EglStatus.DRAW;
        }
    }

    // 创建EGL环境
    public void createEGLEnv() {
        createDisplay();
        createConfig();
        createContext();
        createSurface();
        makeCurrent();
    }

    // 销毁EGL环境
    public void destroyEGLEnv() {
        // 与显示设备解绑
        EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
        // 销毁 EGLSurface
        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
        // 销毁EGLContext
        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
        // 销毁EGLDisplay(显示设备)
        EGL14.eglTerminate(mEGLDisplay);
        mEGLContext = null;
        mEGLSurface = null;
        mEGLDisplay = null;
    }

    // 1.创建EGLDisplay
    private void createDisplay() {
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
        int[] versions = new int[2];
        EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);
    }

    // 2.创建EGLConfig
    private void createConfig() {
        EGLConfig[] configs = new EGLConfig[1];
        int[] configNum = new int[1];
        EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
        if (configNum[0] > 0) {
            mEGLConfig = configs[0];
        }
    }

    // 3.创建EGLContext
    private void createContext() {
        if (mEGLConfig != null) {
            mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);
        }
    }

    // 4.创建EGLSurface
    private void createSurface() {
        if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {
            int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
            mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);
        }
    }

    // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)
    private void makeCurrent() {
        if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {
            EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
            mEglStatus = EglStatus.INITIALIZED;
        }
    }

    // EGLConfig参数
    private int[] mEGLConfigAttrs = {
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_DEPTH_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_NONE
    };

    // EGLContext参数
    private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};

    // EGL状态
    enum EglStatus {
        INVALID, INITIALIZED, CREATED, DRAW
    }

    // 渲染器接口
    interface Renderer {
        void onSurfaceCreated();
        void onSurfaceChanged(int width, int height);
        void onDrawFrame();
    }
}

        MyEGLSurface.java

package com.zhyan8.egl.opengl;

import android.content.Context;

public class MyEGLSurface extends BaseEGLSurface {
    public MyEGLSurface(Context context) {
        super(context);
    }

    public MyEGLSurface(Context context, int width, int height) {
        super(context, width, height);
    }

    public void init(Renderer renderer) {
        setRenderer(renderer);
        createEGLEnv();
    }
}

        MyRender.java

package com.zhyan8.egl.opengl;

import android.content.res.Resources;
import android.opengl.GLES30;
import com.zhyan8.egl.model.Model;

public class MyRender implements BaseEGLSurface.Renderer {
    private Model mModel;

    public MyRender(Resources resources) {
        mModel = new Model(resources);
    }

    @Override
    public void onSurfaceCreated() {
        //设置背景颜色
        GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        //启动深度测试
        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
        //创建程序id
        mModel.onModelCreate();
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        mModel.onModelChange(width, height);
    }

    @Override
    public void onDrawFrame() {
        GLES30.glClearColor(0.5f, 0.7f, 0.3f, 1.0f);
        // 将颜色缓存区设置为预设的颜色
        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.egl.model;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.opengl.GLES30;
import com.zhyan8.egl.R;
import com.zhyan8.egl.utils.ArraysUtils;
import com.zhyan8.egl.utils.ShaderUtils;
import com.zhyan8.egl.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 * Integer.BYTES);
        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.egl.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.egl.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.egl.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 运行效果

        原图:

        处理后:

猜你喜欢

转载自blog.csdn.net/m0_37602827/article/details/123887797