OpenGLES,实现离屏渲染


 

书写本文的初衷是为了自我学习

 

出现背景

    明确两个概念

  • 窗口系统默认帧缓冲
  • 应用程序帧缓冲

    FBO(frame buffer object),帧缓冲区对象,在Android中,绘制三角形一般都是直接重写GLSurfaceView,因为Android已经集成好了OpenGLES的环境,渲染操作都是在默认的帧缓冲去做的,这个帧缓冲是我们在创建一个Surface的时候自动创建的(Surface,第二节讲一下)。但这仅限于demo级别,实际应用中,如果我们需要渲染到纹理,往往不使用窗口系统默认提供的帧缓冲区域,需要自己创建了。

何为帧缓冲?

    显示到屏幕上的每一帧数据其实对应的就是内存中的数据,在内存中对应分配着存储帧数据的缓冲区,包括写入颜色的颜色缓冲,写入深度值的深度缓冲,以及基于一些条件丢弃片元的模板缓冲,这几种缓冲一起称之为帧缓冲。

 

为什么要使用帧缓冲?

之前绘制使用的纹理都是使用图片加载得到的纹理,如果我们要对纹理在着色器中做一些处理,模糊、虚化、双屏、镜子等特效,那么使用帧缓冲可以很好的实现。此外,帧缓冲提供了一个很高效的机制,它能够快速的分离和附着纹理或渲染缓冲对象,这比在帧缓冲之间切换要快得多。

两种附件

纹理附件

纹理附件和通过图片加载的纹理类似,只不过这个纹理附加是通过渲染命令写入到纹理当中的,不是通过图片纹理得到的。

注意:除了附加颜色附件之外,还可以附件深度和模板纹理附件。例如,当我们开启了深度测试时,就需要附着一个深度附件,来表示深度信息,以便进行深度测试。为了附加一个深度缓冲,可用GL_DEPTH_ATTACHMENT作为附件类型。

 

渲染缓冲对象(RBO

渲染缓冲对象(RenderBuffer Object,简称RBO)是一个OpenGL格式的缓冲,即以OpenG原生(native)格式存储它的数据,因此它相当于是优化过的内部数据。当它绑定到FrameBuffer上时,渲染的像素信息就写到RBO中。

渲染缓冲对象将渲染数据存储到缓冲中,并且以原生格式存储,所以它成为一种快速可写入的介质。但是,只能写入,不能修改。RBO常常用来存储深度和模板信息,用于深度测试和模板测试,而且比用纹理存储的深度和模板方式要快得多。RBO可以用来实现双缓冲(double buffer)。

同样,渲染缓冲对象也可以写入颜色信息,然后将图像信息显示在屏幕上。

貌似RBO比纹理有点多,但也不是万能的,纹理自有纹理的优点,纹理能够在shader中进行操作或者需要读取像素时,做一些处理,此时RBO就无能为力了。

案例演示

网上的很多教程都只是说明了流程,然后演示的demo都混杂着各种其他的东西,初学者要费很大的力气才能理解。下面说明一个单纯只应用了FBO的案例:

环境是使用Android集成好的,GLSurfaceView ,egl环境也是。(关于这一点,可以看下一个文档,介绍EGL)。

撇开OpenGLES的API,单单说离屏渲染的流程如下:

  1. 创建一个帧缓冲的buffer,取名为FrameBuffer(一块显存)。
  2. 将要绘制的纹理数据写入FrameBuffer(即绑定)。
  3. 在渲染的任意时刻将帧缓冲FrameBuffer中的数据取出使用(在你想用的任意时刻)。

 

对比直接绘制到屏幕的渲染流程如下:

  1. 将要绘制的纹理数据写入默认的帧缓冲,取名defaultFrameBuffer。
  2. 在渲染的过程中连续的将defaultFrameBuffer中的数据取出使用。

我们只是使用OpenGLES的API去完成上面的流程。

首先是工程结构,FBO的实现应用了红线的几个文件(其他文件绘制了一个普通的三角形,):

instrument ios离屏渲染 fbo离屏渲染_离屏渲染

instrument ios离屏渲染 fbo离屏渲染_数据_02

MainActivity指定了View。

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView mGLView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLView = new MySurfaceView(this);
        setContentView(mGLView);
    }
}

MySurfaceView指定了渲染器,渲染模式。onDrawFrame开始离屏渲染。

public class MySurfaceView extends GLSurfaceView {
    private MyRenderer mRenderer;
    private FBORenderer fboRenderer;
    public MySurfaceView (Context context){
        super(context);
        this.setEGLContextClientVersion(2);
//      绘制普通三角形的渲染器
//      mRenderer=new MyRenderer(context);
//      this.setRenderer(mRenderer);
        fboRenderer = new FBORenderer(context);
        this.setRenderer(fboRenderer);
        this.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}
FBORenderer重写了GLSurfaceView的渲染器
public class FBORenderer implements GLSurfaceView.Renderer{
    public static int sScreenWidth;
    public static int sScreenHeight;
    private Shape_FBO mRectangle;
    float yAngle;
    float xAngle;
    private Context mContext;
    public FBORenderer(Context context) {
        super();
        mContext = context;
    }
 
 
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
        mRectangle = new Shape_FBO(mContext);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        sScreenWidth = width;
        sScreenHeight = height;
        GLES20.glViewport(0, 0, width, height);
        Matrix.perspectiveM(mProjectionMatrix, 0, 45, (float)width/height, 2, 5);
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 3,  0, 0, 0, 0, 1, 0);
    }
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final float[] mModuleMatrix = new float[16];
    private final float[] mViewProjectionMatrix = new float[16];
    private final float[] mMVPMatrix = new float[16];
    @Override
    public void onDrawFrame(GL10 gl) {
        Matrix.setIdentityM(mModuleMatrix, 0);
        Matrix.rotateM(mModuleMatrix, 0, xAngle, 1, 0, 0);
        Matrix.rotateM(mModuleMatrix, 0, yAngle, 0, 1, 0);
        Matrix.multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mViewProjectionMatrix, 0, mModuleMatrix, 0);
//        GLES20.glViewport(0, 0, 1024, 1024);
        mRectangle.draw(mMVPMatrix, mModuleMatrix);
        mRectangle.draw(mMVPMatrix, mModuleMatrix);
    }
}

本类就是核心的绘制类了。

1.colorTextureId这个纹理缓存指定到FrameBuffer,与FrameBuffer中的数据进行关联,也就是说,现在colorTextureId就是FrameBuffer中数据所生成的图片。

2.在FrameBuffer中绘制读入的图片mLoadedTextureId。

3.在默认的窗口defaultFrameBuffer中绘制colorTextureId。

对照代码理解这张图,更有体会。

一般情况我们都是直接走红线进行绘制,FBO离屏渲染走绿线

可以在项目中crtl+f( GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTxtureId);

将其中的colorTxtureId,替换为mLoadedTextureId,

并注释draw方法中/*================================================================*/以上的代码,你也会看到图片显示了出来。

instrument ios离屏渲染 fbo离屏渲染_离屏渲染_03

instrument ios离屏渲染 fbo离屏渲染_离屏渲染_04

首先是生成一张纹理mLoadedTextureId,然后绑定到FrameBuffer中,然后

public class Shape_FBO {
    private static String TAG = "ShapeFBO";
    private FloatBuffer mSqureBuffer;
    private FloatBuffer mSqureBufferfbo;
    private int mFrameBufferProgram;
    private int mWindowProgram;
    private int mLoadedTextureId;
    private Context mContext;
 
 
    public Shape_FBO(Context context) {
        this.mContext = context;
        this.initVetexData();
    }
    public void initVetexData() {
        //生成纹理
        mLoadedTextureId=initTexture(R.drawable.texture1);
        //准备绘制数据
        float [] bgVertex = new float[] {
                -1f,-1f,  0,1,
                -1f,1f,  0,0,
                1f,-1f,  1,1,
                1f,1f,  1,0
        };
        ByteBuffer vbb0 = ByteBuffer.allocateDirect(bgVertex.length * 4);
        vbb0.order(ByteOrder.nativeOrder());
        mSqureBuffer = vbb0.asFloatBuffer();
        mSqureBuffer.put(bgVertex);
        mSqureBuffer.position(0);
 
 
        float [] fboVertex = new float[] {
                -1f,-1f,  0,1,
                -1f,1f,  0,0,
                1f,-1f,  1,1,
                1f,1f,  1,0
        };
        ByteBuffer vbb1 = ByteBuffer.allocateDirect(fboVertex.length * 4);
        vbb1.order(ByteOrder.nativeOrder());
        mSqureBufferfbo = vbb1.asFloatBuffer();
        mSqureBufferfbo.put(fboVertex);
        mSqureBufferfbo.position(0);
    }
    public int initTexture(int res) {
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), res);
        int [] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        //绑定纹理缓存到纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
        //设置采样,拉伸方式
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_MIRRORED_REPEAT);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
        //指定纹理图片生成2D纹理
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        //释放bitmap
        bitmap.recycle();
        //解除绑定
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        return textures[0];
    }
    public void draw(float[] mvpMatrix, float[] mMatrix) {
        // 生成FrameBuffer
        int [] framebuffers = new int[1];
        GLES20.glGenFramebuffers(1, framebuffers, 0);
        // 生成Texture
        int [] textures = new int[2];
        GLES20.glGenTextures(2, textures, 0);
        int colorTxtureId = textures[0];
        //绑定纹理缓存到纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTxtureId);
        //设置采样,拉伸方式
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_MIRRORED_REPEAT);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
        //生成2D纹理
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, FBORenderer.sScreenWidth, FBORenderer.sScreenHeight,0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
        //绑定framebuffer
        int framebufferId = framebuffers[0];
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferId);
        //挂载textureID到framebufferId
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, colorTxtureId, 0);
        if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)== GLES20.GL_FRAMEBUFFER_COMPLETE) {
            Log.e("shapefbo", "glFramebufferTexture2D error");
        }
        int frameBufferVertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int frameBufferFagmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
        mFrameBufferProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mFrameBufferProgram, frameBufferVertexShader);
        GLES20.glAttachShader(mFrameBufferProgram, frameBufferFagmentShader);
        GLES20.glLinkProgram(mFrameBufferProgram);
        int positionHandle1 = GLES20.glGetAttribLocation(mFrameBufferProgram, "aPosition");
        int textureCoordHandle1 = GLES20.glGetAttribLocation(mFrameBufferProgram, "aTextureCoord");
        int textureHandle1 = GLES20.glGetUniformLocation(mFrameBufferProgram, "uTexture");
        mSqureBufferfbo.position(0);
        GLES20.glVertexAttribPointer(positionHandle1, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBufferfbo);
        mSqureBufferfbo.position(2);
        GLES20.glVertexAttribPointer(textureCoordHandle1, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBufferfbo);
        GLES20.glEnableVertexAttribArray(positionHandle1);
        GLES20.glEnableVertexAttribArray(textureCoordHandle1);
//        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mLoadedTextureId);
        GLES20.glUniform1i(textureHandle1, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
 
 
        /*================================================================*/
        // 切换到窗口系统的缓冲区
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, windowVertexShaderCode);
        int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, windowFragmentShaderCode);
        mWindowProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mWindowProgram, vertexShader);
        GLES20.glAttachShader(mWindowProgram, fragmentShader);
        GLES20.glLinkProgram(mWindowProgram);
        GLES20.glUseProgram(mWindowProgram);
        int positionHandle = GLES20.glGetAttribLocation(mWindowProgram, "aPosition");
        int textureCoordHandle = GLES20.glGetAttribLocation(mWindowProgram, "aTextureCoord");
        int textureHandle = GLES20.glGetUniformLocation(mWindowProgram, "uTexture");
        mSqureBuffer.position(0);
        GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBuffer);
        mSqureBuffer.position(2);
        GLES20.glVertexAttribPointer(textureCoordHandle, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBuffer);
        GLES20.glEnableVertexAttribArray(positionHandle);
        GLES20.glEnableVertexAttribArray(textureCoordHandle);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTxtureId);
        GLES20.glUniform1i(textureHandle, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glDeleteTextures(2, textures, 0);
        GLES20.glDeleteFramebuffers(1, framebuffers, 0);
    }
 
 
    private int loaderShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }
    private String windowVertexShaderCode = ""
            + "attribute vec2 aPosition;"
            + "attribute vec2 aTextureCoord;"
            + "varying vec2 vTextureCoord;"
            + "void main(){"
            + "gl_Position = vec4(aPosition,0,1);"
            + "vTextureCoord = aTextureCoord;"
            + "}";
    private String windowFragmentShaderCode = "precision mediump float;"
            + "uniform sampler2D uTexture;"
            + "varying vec2 vTextureCoord;"
            + "void main(){"
            + "gl_FragColor = texture2D(uTexture, vTextureCoord);"
            + "}";
    private String vertexShaderCode = ""
            + "attribute vec2 aPosition;"
            + "attribute vec2 aTextureCoord;"
            + "varying vec2 vTextureCoord;"
            + "void main(){"
            + "gl_Position = vec4(aPosition,0,1);"
            + "vTextureCoord = aTextureCoord;"
            + "}";
    private String fragmentShaderCode = "precision mediump float;"
            + "uniform sampler2D uTexture;"
            + "varying vec2 vTextureCoord;"
            + "void main(){"
            + "gl_FragColor = texture2D(uTexture, vTextureCoord);"
            + "}";

GitHub地址欢迎点星星