OpenGLES,为什么 glReadPixels() 无法从 FBO 的 Renderbuffer 的颜色缓冲区中读取数据?

OpenGLES , why glReadPixels() can't read data from FBO's Renderbuffer's color buffer?

这是完整的代码:


public class FBORenderer2 implements GLSurfaceView.Renderer {

    private static final String TAG = "FBORenderer2";
    
    protected FloatBuffer mVerBuffer = ShaderUtils.floatBuffer(-1.0f,  1.0f,
            -1.0f, -1.0f,
            1.0f, 1.0f,
            1.0f,  -1.0f);
    protected FloatBuffer mTexBuffer = ShaderUtils.floatBuffer(0.0f, 0.0f,
            0.0f,  1.0f,
            1.0f,  0.0f,
            1.0f, 1.0f);

    private Bitmap mBitmap;
    private ByteBuffer mBuffer;

    private final int[] fFrame = new int[1];
    private final int[] fRender = new int[1];
    private final int[] fTexture = new int[3];

    protected int mHPosition;
    protected int mHCoord;
    protected int mHTexture;
    protected int mHMatrix;

    protected int program;

    private Context context;

    private float[] mMatrix = {
            1,0,0,0,
            0,1,0,0,
            0,0,1,0,
            0,0,0,1
    };

    private Callback mCallback;

    public FBORenderer2(Context context){
        this.context = context;
        mMatrix = flip(mMatrix,false,true);
    }

    public static float[] flip(float[] m,boolean x,boolean y){
        if(x||y){
            Matrix.scaleM(m,0,x?-1:1,y?-1:1,1);
        }
        return m;
    }

    public void setBitmap(Bitmap bitmap){
        this.mBitmap = bitmap;
    }

    public void setmCallback(Callback callback){
        this.mCallback = callback;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        program = ShaderUtils.createProgram(ShaderUtils.loadFromAssets("base_vertex.vert",context.getResources()),
                ShaderUtils.loadFromAssets("gray_fragment.frag",context.getResources()));
        GLES30.glEnable(GL_DEPTH_TEST);
        mHPosition= GLES30.glGetAttribLocation(program, "vPosition");
        mHCoord=GLES30.glGetAttribLocation(program,"vCoord");
        mHMatrix=GLES30.glGetUniformLocation(program,"vMatrix");
        mHTexture=GLES30.glGetUniformLocation(program,"vTexture");

        // Texture  source Texture
        GLES30.glGenTextures(1, fTexture, 0);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, fTexture[0]);
        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, mBitmap, 0);

        // RenderBuffer
        GLES30.glGenRenderbuffers(1, fRender, 0);
        GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, fRender[0]);
        GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER, GLES30.GL_RGBA8, mBitmap.getWidth(), mBitmap.getHeight());
        LogUtils.checkError(TAG,"glRenderbufferStorage");

        // FrameBuffer
        GLES30.glGenFramebuffers(1, fFrame, 0);
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fFrame[0]);
        GLES30.glFramebufferRenderbuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_RENDERBUFFER, fRender[0]);
        LogUtils.checkFrameBuffer(TAG,GLES30.glCheckFramebufferStatus(GL_FRAMEBUFFER));

        mBuffer = ByteBuffer.allocate(mBitmap.getWidth() * mBitmap.getHeight() * 4);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        if(mBitmap != null && !mBitmap.isRecycled()){

            clear();
            GLES30.glBindFramebuffer(GL_FRAMEBUFFER,fFrame[0]);
            GLES30.glUseProgram(program);

            // set GLSL's variable value
            GLES30.glUniformMatrix4fv(mHMatrix,1,false, mMatrix,0);
            GLES30.glUniform1i(mHTexture,0);
            GLES30.glEnableVertexAttribArray(mHPosition);
            GLES30.glVertexAttribPointer(mHPosition,2, GLES30.GL_FLOAT, false, 0,mVerBuffer);
            GLES30.glEnableVertexAttribArray(mHCoord);
            GLES30.glVertexAttribPointer(mHCoord, 2, GLES30.GL_FLOAT, false, 0, mTexBuffer);

            // bind source Texture
            GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,fTexture[0]); 

            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,4); 
            GLES30.glDisableVertexAttribArray(mHPosition);
            GLES30.glDisableVertexAttribArray(mHCoord);

            GLES30.glReadPixels(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), GLES30.GL_RGBA,
                    GLES30.GL_UNSIGNED_BYTE, mBuffer);
            LogUtils.checkError(TAG,"glReadPixels");

            GLES30.glDeleteTextures(1, fTexture, 0);
            GLES30.glDeleteRenderbuffers(1, fRender, 0);
            GLES30.glDeleteFramebuffers(1, fFrame, 0);

            if(mCallback!=null){
                mCallback.onCall(mBuffer);
            }
            mBitmap.recycle();
            mBuffer.clear();
        }
    }

    private void clear(){
        GLES30.glClearColor(1.0f,1.0f,1.0f,1.0f);
        GLES30.glClear(GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT);
    }

    interface Callback{
        void onCall(ByteBuffer data);
    }

}

我调试得到的mBuffer数据是:

[0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 
-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0,
 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, +586,656 more]

如果我在 glReadPixels() 之后使用附加到 FBO 的纹理替换 Renderbuffer,我可以获得这样的数据:

[-10, -10, -10, -1, -10, -10, -10, -1, -10, -10, -10, -1, -10, -10, -10, -1, -10, -10, 
-10, -1, -10, -10, -10, -1, -10, -10, -10, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11,
 -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, 
-1, -11, -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -12, -12, -12, -1, -11, -11, 
-11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11, -11, -11, -1, -11,
 -11, -11, -1, -11, -11, -11, -1, +586,656 more]

并且可以在ImageView上显示完整的渲染结果。使用 RenderBuffer 只能得到黑色视图。

我想这可能是因为 GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER, GLES30.GL_RGBA8, mBitmap.getWidth(), mBitmap.getHeight()) 的 GL_RGBA8 没有被 glReadPixels() 接受,但我不知道如何解决这个问题。

我不确定为什么这不起作用 - 它可能只是您正在使用的设备上的驱动程序错误。

解决方案就是使用纹理。对于所有实际用途,它们与渲染缓冲区相同。