使用PBO更新NV21纹理,shader处理并渲染到FBO中,再进行二次渲染的例子

使用PBO对比glTexImage2d函数可以提升纹理更新效率,因为glTexImage2d更新纹理需要把内存数据复制到显存,而这完全由CPU进行操作:

但使用PBO则可以让显卡驱动利用DMA通道,绕开CPU直接从指定的内存位置拷贝数据:

 

使用两个PBO则可以进行异步更新进一步提升效能,例如一个在写的同时另外一个可以读:

但PBO也仅仅是通过DMA提升纹理更新效率,因此纹理还是要创建的,我们先创建Y通道和UV通道两个只占用显存空间但内存全为0的纹理。

双PBO + 双FBO架构如下:

   使用此方式可以把YUV转换后的数据离屏渲染到FBO中,作为一整个纹理可以灵活地使用卷积核、滤镜shader等实现降噪、抖音特效等画面的二次处理。

因为Y通道和UV通道的处理方式不一样,因此创建两个空纹理,Y通道一个,UV通道一个:

    private void startBindTexture() {
        createEmptyTexture(mGenYTextureId, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE);
        createEmptyTexture(mGenYTextureId, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE);
    }
    private void createEmptyTexture(int textureID, int imgWidth, int imgHeight, int pixelFormat) {
        GLES30.glGenTextures(1, new int[] {textureID}, 0); //只要值不重复即可
        //UV纹理初始化
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureID);
        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);
        //创建一个占用指定空间的纹理,但暂时不复制数据进去,等PBO进行数据传输,取代glTexImage2D,利用DMA提高数据拷贝速度
        GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, pixelFormat, imgWidth, imgHeight, 0, pixelFormat, GLES30.GL_UNSIGNED_BYTE, null); //因为这里使用了双字节,所以纹理大小对比使用单字节的Y通道纹理,宽度首先要缩小一般,而uv层高度本来就只有y层一般,所以高度也除以2
    }

       其中Y通道使用单字节方式读写,也就是GL_LUMINANCE或者GL_ALPHA,UV通道为两个字节为一组,所以更适合使用两字节的模式,也就是GL_LUMINANCE_ALPHA格式,但相对地,纹理宽度就变成了纯灰度格式的一半,YUV的压缩方式也决定了每2个Y对应一个UV,因此UV层宽度也得除以2,所以,UV纹理的长宽便时mImgWidth / 2, mImgHeight / 2。

创建PBO:

    /**
     * 创建2个framebuffer作为每次渲染结果的叠加专用纹理
     **/
    private void createPBO() {
        //创建Y通道PBO
        mImgPanelYByteSize = mImgWidth * mImgHeight;
        mYPanelPixelBuffferPointerArray = new int[2];
        GLES30.glGenBuffers(2, mYPanelPixelBuffferPointerArray, 0);

        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[0]);
        GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelYByteSize,  null, GLES30.GL_STREAM_DRAW);

        GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mYPanelPixelBuffferPointerArray[1]);
        GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mImgPanelYByteSize,  null, GLES30.GL_STREAM_DRAW);
        //创建UV通道PBO
        mImgPanelUVByteSize = mImgWidth * mImgHeight / 2;
        mUVPanelPixelBuffferPointerArray = new int[2];
        GLES30.glGenBuffers(2, mUVPanelPixelBuffferPointerArray, 0);

        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[0]);
        GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelUVByteSize,  null, GLES30.GL_STREAM_DRAW);

        GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mUVPanelPixelBuffferPointerArray[1]);
        GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mImgPanelUVByteSize,  null, GLES30.GL_STREAM_DRAW);
    }

此时PBO已创建完成。现在我们来完成纹理数据更新的方法吧:

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public void refreshBuffer(byte[] imgBytes) {
        //更新ypanel数据
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenYTextureId);
        GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureY"), 0); //获取纹理属性的指针
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[mFrameCount % 2]);
        GLES30.glTexSubImage2D(GLES30.GL_TEXTURE_2D, 0, 0, 0, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, null); //1字节为一个单位
        //更新图像数据,复制到 PBO 中
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[(mFrameCount + 1) % 2]);
        GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelYByteSize, null, GLES30.GL_STREAM_DRAW);
        Buffer buf = GLES30.glMapBufferRange(GLES30.GL_PIXEL_UNPACK_BUFFER, 0, mImgPanelYByteSize, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
        //填充像素
        ByteBuffer bytebuffer = ((ByteBuffer) buf).order(ByteOrder.nativeOrder());
        bytebuffer.position(0);
        bytebuffer.put(imgBytes, 0, mImgWidth * mImgHeight);
        bytebuffer.position(0);
        GLES30.glUnmapBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER);
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, 0);
        //更新uvpanel数据
        GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenUVTextureId);
        GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureUV"), 1); //获取纹理属性的指针
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[mFrameCount % 2]);
        GLES30.glTexSubImage2D(GLES30.GL_TEXTURE_2D, 0, 0, 0, mImgWidth / 2, mImgHeight / 2, GLES30.GL_LUMINANCE_ALPHA, GLES30.GL_UNSIGNED_BYTE, null); //2字节为一个单位,所以宽度因为单位为2字节一个,对比1字节时直接对半
        //更新图像数据,复制到 PBO 中
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[(mFrameCount + 1) % 2]);
        GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelUVByteSize, null, GLES30.GL_STREAM_DRAW);
        buf = GLES30.glMapBufferRange(GLES30.GL_PIXEL_UNPACK_BUFFER, 0, mImgPanelUVByteSize, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
        //填充像素
        bytebuffer = ((ByteBuffer) buf).order(ByteOrder.nativeOrder());
        bytebuffer.position(0);
        bytebuffer.put(imgBytes, mImgWidth * mImgHeight, mImgWidth * mImgHeight / 2);
        bytebuffer.position(0);
        GLES30.glUnmapBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER);
        GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, 0);
    }

 原理就是把yuv数组分段利用PBO写到对应的纹理中。

编写shader,实现YUV420转换为RGBA:

//https://blog.csdn.net/byhook/article/details/84037338 有各种YUV的排列方式
/**yuvtorgb
        R = Y + 1.4075 *(V-128)
       G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)
       B = Y + 1.779 *(U – 128)
**/

vec3 yuvToRGB(float y, float u, float v) {
    float r = y + 1.370705 * (v - 0.5);
    float g = y - 0.337633 * (u - 0.5) - 0.698001 * (v - 0.5);
    float b = y + 1.732446 * (u - 0.5);
    //    r = clamp(r, 0.0, 1.0);
    //    g = clamp(g, 0.0, 1.0);
    //    b = clamp(b, 0.0, 1.0);
    return vec3(r, g, b);
}

void convertYUV420SP(bool reverse, in vec2 fragVTexCoord, out vec4 fragColor){
    float y = texture(textureY, fragVTexCoord)[0];
    //uvuvuvuv
    float u = texture(textureUV, fragVTexCoord)[3];
    float v = texture(textureUV, fragVTexCoord)[0];
    vec3 rgb;
    if (reverse) {
        rgb = yuvToRGB(y, v, u);//NV21
    } else {
        rgb = yuvToRGB(y, u, v);//NV12
    }
    fragColor = vec4(rgb, 1.0);
}

创建两个FBO,刚刚YUV转换为RGBA后的画面可以渲染到这里:

    /**
     * 创建2个framebuffer保存每次渲染结果
     **/
    private void createDoubleFrameBuffer() {
        int frameBufferCount = 2;

        //生成framebuffer
        mFrameBufferPointerArray = new int[frameBufferCount];
        GLES30.glGenFramebuffers(mFrameBufferPointerArray.length, mFrameBufferPointerArray, 0);

        //生成渲染缓冲buffer
        mRenderBufferPointerArray = new int[frameBufferCount];
        GLES30.glGenRenderbuffers(mRenderBufferPointerArray.length, mRenderBufferPointerArray, 0);

        //生成framebuffer纹理pointer
        mFrameBufferTexturePointerArray = new int[frameBufferCount];
        GLES30.glGenTextures(mFrameBufferTexturePointerArray.length, mFrameBufferTexturePointerArray, 0);

        //遍历framebuffer并初始化
        for (int i = 0; i < frameBufferCount; i++) {
            //绑定帧缓冲,遍历两个framebuffer分别初始化
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[i]);
            //绑定缓冲pointer
            GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, mRenderBufferPointerArray[i]);
            //为渲染缓冲初始化存储,分配显存
            GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER,
                    GLES30.GL_DEPTH_COMPONENT16, mFrameBufferWidth, mFrameBufferHeight); //设置framebuffer的长宽

            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[i]); //绑定纹理Pointer

            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置MIN采样方式
                    GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置MAG采样方式
                    GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置S轴拉伸方式
                    GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置T轴拉伸方式
                    GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
            GLES30.glTexImage2D//设置颜色附件纹理图的格式
                    (
                            GLES30.GL_TEXTURE_2D,
                            0,                        //层次
                            GLES30.GL_RGBA,        //内部格式
                            mFrameBufferWidth,            //宽度
                            mFrameBufferHeight,            //高度
                            0,                        //边界宽度
                            GLES30.GL_RGBA,            //格式
                            GLES30.GL_UNSIGNED_BYTE,//每个像素数据格式
                            null
                    );
            GLES30.glFramebufferTexture2D        //设置自定义帧缓冲的颜色缓冲附件
                    (
                            GLES30.GL_FRAMEBUFFER,
                            GLES30.GL_COLOR_ATTACHMENT0,    //颜色缓冲附件
                            GLES30.GL_TEXTURE_2D,
                            mFrameBufferTexturePointerArray[i],                        //纹理id
                            0                                //层次
                    );
            GLES30.glFramebufferRenderbuffer    //设置自定义帧缓冲的深度缓冲附件
                    (
                            GLES30.GL_FRAMEBUFFER,
                            GLES30.GL_DEPTH_ATTACHMENT,        //深度缓冲附件
                            GLES30.GL_RENDERBUFFER,            //渲染缓冲
                            mRenderBufferPointerArray[i]                //渲染深度缓冲id
                    );
        }
        //绑回系统默认framebuffer,否则会显示不出东西
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧缓冲id
    }

绑定Y通道纹理和UV通道纹理,绘制纹理到FBO中,绘制的同时YUV会被转换为色彩正常的RGBA颜色:

    /**绘制画面到framebuffer**/
    private void drawToFrameBuffer(float[] cameraMatrix, float[] projMatrix) {
        if (mIsDestroyed) {
            return;
        }
        GLES30.glUseProgram(mYuvBufferDrawProgram);
        //设置视窗大小及位置
        GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight);
        //绑定帧缓冲id
        if (mFrameCount % 2 == 0) {
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[0]);
        } else {
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[1]);
        }
        //清除深度缓冲与颜色缓冲
        if (!mFrameBufferClean && !mFrameBufferCleanOnce) { //实现渲染画面叠加
            GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
            mFrameBufferCleanOnce = true;
        }
        if (mFrameBufferClean) {
            GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
            GLES30.glUniform1i(mGLFrameBufferProgramFunChoicePointer, -1); //第一次加载选择纹理方式渲染
        }
        //设置它的坐标系
        locationTrans(cameraMatrix, projMatrix, this.mGLFrameuMVPMatrixPointer);
        //设置图像分辨率
        GLES30.glUniform2fv(mResoulutionPointer, 1, new float[]{mWindowW, mWindowH}, 0);
        locationTrans(cameraMatrix, projMatrix, this.mGLFrameuMVPMatrixPointer);
        if (mPointBuf != null && mColorBuf != null) {
            if (mFrameCount < 0) {
                mFrameCount = 0;
            }
            mPointBuf.position(0);
            mColorBuf.position(0);
//            GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "sTexture"), 0); //获取纹理属性的指针
            //将顶点位置数据送入渲染管线
            GLES30.glVertexAttribPointer(mGLFrameObjectPositionPointer, 3, GLES30.GL_FLOAT, false, 0, mPointBuf); //三维向量,size为2
            //将顶点颜色数据送入渲染管线
            GLES30.glVertexAttribPointer(mGLFrameObjectVertColorArrayPointer, 4, GLES30.GL_FLOAT, false, 0, mColorBuf);
            //将顶点纹理坐标数据传送进渲染管线
            GLES30.glVertexAttribPointer(mGLFrameVTexCoordPointer, 2, GLES30.GL_FLOAT, false, 0, mTexCoorBuffer);  //二维向量,size为2
            GLES30.glEnableVertexAttribArray(mGLFrameObjectPositionPointer); //启用顶点属性
            GLES30.glEnableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);  //启用颜色属性
            GLES30.glEnableVertexAttribArray(mGLFrameVTexCoordPointer);  //启用纹理采样定位坐标
//            GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
            //绘制yuv

            GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenYTextureId);
            GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureY"), 0); //获取纹理属性的指针

            GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenUVTextureId);
            GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureUV"), 1); //获取纹理属性的指针

            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointBufferPos / 3); //绘制线条,添加的point浮点数/3才是坐标数(因为一个坐标由x,y,z3个float构成,不能直接用)
            GLES30.glDisableVertexAttribArray(mGLFrameObjectPositionPointer);
            GLES30.glDisableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);
            GLES30.glDisableVertexAttribArray(mGLFrameVTexCoordPointer);
        }
        //绑会系统默认framebuffer,否则会显示不出东西
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧缓冲id
    }

此时画面已离屏渲染到FBO中,现在我们把FBO画面在shader中进行边缘锐化卷积操作,并最后绑定当前窗口的framebuffer——也就是0号framebuffer,然后纹理进行bindTexuter后,显示出来。

shader处理程序(resolution为屏幕分辨率uniform变量):

void kernalEffect(vec2 TexCoords)
{
    float offset = 1.0 / resolution.y;
    vec2 offsets[9] = vec2[](
    vec2(-offset, offset), // 左上
    vec2(0.0, offset), // 正上
    vec2(offset, offset), // 右上
    vec2(-offset, 0.0), // 左
    vec2(0.0, 0.0), // 中
    vec2(offset, 0.0), // 右
    vec2(-offset, -offset), // 左下
    vec2(0.0, -offset), // 正下
    vec2(offset, -offset)// 右下
    );


    vec3 sampleTex[9];
    for (int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(textureFBO, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for (int i = 0; i < 9; i++)
    col += sampleTex[i] * kernel[i];

    fragColor = vec4(col, 1.0);
}

绘制FBO到窗口的代码:

@Override
    public void drawTo(float[] cameraMatrix, float[] projMatrix) {
        if (mIsDestroyed) {
            return;
        }
        locationTrans(cameraMatrix, projMatrix, mGLFrameuMVPMatrixPointer);
        //先绘制内容到frambuffer
        drawToFrameBuffer(cameraMatrix, projMatrix);
        //绘制
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧系统默认缓冲id
        locationTrans(cameraMatrix, projMatrix, mGLFrameuMVPMatrixPointer);

        if (mPointBuf != null && mColorBuf != null) {
            Log.i("cjztest", "drawFramebuffer");
            GLES30.glUniform1i(mGLFrameBufferProgramFunChoicePointer, -1); //选择纹理方式渲染
            mPointBuf.position(0);
            mColorBuf.position(0);
            GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
            //切换纹理到当前正在绘制的framebuffer
            if (mFrameCount % 2 == 1) { //另外一个当前不需要投上屏幕的FBO可以用于输出处理结果。
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[0]);
            } else {
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[1]);
            }
            GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureFBO"), 0); //获取纹理属性的指针
            //将顶点位置数据送入渲染管线
            GLES30.glVertexAttribPointer(mGLFrameObjectPositionPointer, 3, GLES30.GL_FLOAT, false, 0, mPointBuf); //三维向量,size为2
            //将顶点颜色数据送入渲染管线
            GLES30.glVertexAttribPointer(mGLFrameObjectVertColorArrayPointer, 4, GLES30.GL_FLOAT, false, 0, mColorBuf);
            //将顶点纹理坐标数据传送进渲染管线
            GLES30.glVertexAttribPointer(mGLFrameVTexCoordPointer, 2, GLES30.GL_FLOAT, false, 0, mTexCoorBuffer);  //二维向量,size为2
            GLES30.glEnableVertexAttribArray(mGLFrameObjectPositionPointer); //启用顶点属性
            GLES30.glEnableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);  //启用颜色属性
            GLES30.glEnableVertexAttribArray(mGLFrameVTexCoordPointer);  //启用纹理采样定位坐标
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointBufferPos / 3); //绘制线条,添加的point浮点数/3才是坐标数(因为一个坐标由x,y,z3个float构成,不能直接用)
            GLES30.glDisableVertexAttribArray(mGLFrameObjectPositionPointer);
            GLES30.glDisableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);
            GLES30.glDisableVertexAttribArray(mGLFrameVTexCoordPointer);
        }
        GLES30.glUniform1i(mFrameCountPointer, mFrameCount++);

    }

最后效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值