1.OpenGLES——FBO方式的離屏渲染


參考

https://blog.csdn.net/u011371324/article/details/78011211

 

書寫本文的初衷是為了自我學習

 

出現背景

    明確兩個概念

  • 窗口系統默認幀緩沖

  • 應用程序幀緩沖

    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的實現應用了紅線的幾個文件(其他文件繪制了一個普通的三角形,):

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方法中/*================================================================*/以上的代碼,你也會看到圖片顯示了出來。

首先是生成一張紋理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地址歡迎點星星


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM