Open GL ES-> 无深度测试,获取帧缓冲FBO+颜色附着实现3D立方体图像的截屏Bitmap

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">

    <com.example.myapplication.EGLSurfaceView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/image"
        android:layout_marginTop="10dp"
        android:layout_marginStart="10dp"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@drawable/ic_launcher_background" />
</FrameLayout>

Activity代码

private lateinit var mImage : ImageView
class MainActivity8 : AppCompatActivity() {
   
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
   
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main8)
        mImage = findViewById<ImageView>(R.id.image)
    }
}

SurfaceView代码

class EGLSurfaceView(context: Context, attrs: AttributeSet? = null) : SurfaceView(context, attrs) {
   
   
    private val renderer = EGLRenderer(context)

    init {
   
   
        holder.addCallback(renderer.surfaceCallback)
    }

    // 可以提供额外配置方法
    fun setRenderStrategy(strategy: RenderStrategy) {
   
   
        renderer.setRenderStrategy(strategy)
    }
}

渲染器代码

class EGLRenderer(context: Context) {
   
   
    // 外观模式 封装了渲染引擎和渲染策略
    private val strategy: RenderStrategy = MyRenderStrategy(context)
    private val engine: EGLRenderEngine = EGLRenderEngine(strategy)

    // 策略模式 可以提供配置方法
    fun setRenderStrategy(strategy: RenderStrategy) {
   
   
        // 允许用户自定义渲染策略
    }


    // 适配器模式 提供给SurfaceView使用的回调
    val surfaceCallback = object : SurfaceHolder.Callback {
   
   
        override fun surfaceCreated(holder: SurfaceHolder) {
   
   
            engine.onSurfaceCreated(holder.surface)
        }

        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
   
   
            engine.onSurfaceChanged(width, height)
        }

        override fun surfaceDestroyed(holder: SurfaceHolder) {
   
   
            engine.onSurfaceDestroyed()
        }
    }
}

渲染引擎代码

class EGLRenderEngine(private val strategy: RenderStrategy) {
   
   
    private var mEGLEnvironment: EGLEnvironment? = null
    private var mRenderThread: RenderThread? = null

    fun onSurfaceCreated(surface: Surface) {
   
   
        mRenderThread = RenderThread(surface, strategy).apply {
   
   
            start()
        }
    }

    fun onSurfaceChanged(width: Int, height: Int) {
   
   
        mRenderThread?.updateSize(width, height)
    }

    fun onSurfaceDestroyed() {
   
   
        mRenderThread?.shutdown()
        mRenderThread = null
    }

    private inner class RenderThread(
        private val surface: Surface,
        private val strategy: RenderStrategy
    ) : Thread() {
   
   
        @Volatile private var running = true
        @Volatile private var sizeChanged = false
        private var width = 0
        private var height = 0

        fun updateSize(width: Int, height: Int) {
   
   
            this.width = width
            this.height = height
            sizeChanged = true
        }

        fun shutdown() {
   
   
            running = false
            interrupt()
            strategy.onSurfaceDestroyed()
            mEGLEnvironment?.release()
        }

        override fun run() {
   
   
            try {
   
   
                // 使用构建者创建EGL环境
                mEGLEnvironment = EGLEnvironmentBuilder().build(surface)

                // 初始化渲染策略
                strategy.onSurfaceCreated()

                // 渲染循环
                while (running) {
   
   
                    if (sizeChanged) {
   
   
                        strategy.onSurfaceChanged(width, height)
                        sizeChanged = false
                    }

                    strategy.onDrawFrame()
                    mEGLEnvironment?.swapBuffers()

                }
            } catch (e: Exception) {
   
   
                Log.e("RenderEngine", "Render thread error: ${
     
     e.message}")
            }
        }
    }
}

自定义渲染策略代码

// 策略模式:定义不同的渲染策略
interface RenderStrategy {
   
   
    fun onSurfaceCreated()
    fun onSurfaceChanged(width: Int, height: Int)
    fun onDrawFrame()
    fun onSurfaceDestroyed()
}

// 可以提供多种渲染策略实现
class MyRenderStrategy(private val context: Context) : RenderStrategy {
   
   
    var mDrawData: MyDrawData2? = null

    // 实现代码与前面相同
    override fun onSurfaceCreated() {
   
   
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
        mDrawData = MyDrawData2().apply {
   
   
            initTexture0(context, R.drawable.picture)
            initShaderProgram()
            initVertexBuffer()
        }
    }

    override fun onSurfaceChanged(width: Int, height: Int) {
   
   
        GLES30.glViewport(0, 0, width, height)
        mDrawData?.setSurfaceSize(width, height)
    }

    override fun onDrawFrame() {
   
   
        GLES30.glEnable(GLES30.GL_DEPTH_TEST)
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
        mDrawData?.drawCurrentOutput()
    }

    override fun onSurfaceDestroyed() {
   
   
        mDrawData?.release()
    }
}

搭建EGL环境代码

// 构建者模式:配置和构建EGL环境
class EGLEnvironmentBuilder(private val factory: EGLComponentFactory = DefaultEGLFactory()) {
   
   
    private lateinit var mEGL: EGL10
    private lateinit var mEGLDisplay: EGLDisplay
    private lateinit var mEGLConfig: EGLConfig
    private lateinit var mEGLContext: EGLContext
    private lateinit 
<think> 我们需要使用OpenGL ES 3.0来实现一个离屏渲染的框架,具体流程如上图所示。主要步骤包括: 1. 创建一个自定义的FBO(离屏渲染) 2. 将渲染目标设置为FBO,并绑定一个RGBA纹理作为颜色附着 3. 使用YUV转换着色器将RGBA纹理转换为Y平面和UV平面(这里实际上是将RGBA图像转换为YUV格式,并分离出Y和UV分量) 4. 对UV平面进行下采样(因为UV通常是Y的一半,即4:2:0) 5. 将Y平面和经过下采样的UV平面组合成NV12格式的数据 6. 将NV12数据保存到文件 注意:在OpenGL ES中,我们通常使用两个纹理来存储Y和UV(或者U和V)数据。NV12格式中,Y是一个平面,而UV是交错存储的另一个平面(即U和V交替出现)。 步骤: 1. 初始化:创建程序、纹理、FBO等。 2. 离屏渲染:将场景渲染到FBO的RGBA纹理。 3. 将RGBA纹理转换为YUV:使用另一个着色器程序(包含YUV转换)将RGBA纹理转换为两个纹理:一个用于Y(亮度),一个用于UV(色度,但这里我们用一个纹理存储UV交错数据)。 4. 下采样UV:由于NV12要求UV分量在水平和垂直方向都是Y分量的一半,所以我们需要对UV纹理进行下采样(可以使用FBO和另一个着色器进行下采样,或者使用glGenerateMipmap然后取下一级,但注意NV12的UV是交错存储的,所以下采样需要特殊处理)。 5. 从纹理中读取数据:将Y纹理和下采样后的UV纹理读取到内存,然后组合成NV12格式。 6. 保存为NV12文件。 但是,由于OpenGL ES不支持直接渲染到NV12格式的纹理(NV12是一种特定格式,通常作为外部纹理),我们这里采用分别渲染到两个纹理(Y纹理和UV纹理),然后分别读取它们的数据,再组合成NV12。 另外,在转换步骤中,我们可能需要两个步骤: 第一步:将RGBA转换为YUV444(全分辨率的YUV),然后分离出Y和UV(此时UV是交错存储?还是分开?注意NV12中UV是交错的,所以我们需要生成一个包含U和V交错数据的纹理)。 第二步:对UV纹理进行下采样(因为NV12是4:2:0,所以UV的分辨率是Y的一半)。 因此,我们需要: 1. 一个FBO附着RGBA纹理(用于原始渲染)。 2. 两个额外的FBO:一个用于Y平面(全分辨率),一个用于UV平面(全分辨率)。注意:这里我们将UV存储在一个纹理中,格式为RG(R通道存U,G通道存V)?但是NV12要求UV交错,所以我们可以使用一个纹理,其内部格式为GL_RG8,每个纹素包含U和V(即两个分量)。 3. 下采样UV:再使用一个FBO附着另一个纹理(尺寸为原图的一半),然后使用一个简单的着色器(比如双线性采样)对UV纹理进行下采样。 但是,由于NV12的UV平面要求:每个像素位置存储一个U和一个V(交错),而我们下采样后的UV纹理是RG8格式,每个纹素包含一个U和一个V(分别对应R和G)。那么当我们读取这个下采样的UV纹理时,可以直接将其作为UV平面(每个字节一个U,紧接着一个V,然后下一组),这样就是NV12的UV部分。 具体步骤: 1. 创建FBO1,附着纹理tex1(RGBA8),用于离屏渲染场景。 2. 创建FBO2,附着两个颜色附着:texY(R8,用于存储Y分量)和texUV(RG8,用于存储UV分量,全分辨率)。注意:FBO2需要两个颜色附着,所以我们要使用多目标渲染(MRT)。在OpenGL ES 3.0中,我们可以声明多个out变量,然后使用glDrawBuffers指定多个颜色附着。 3. 使用一个着色器程序(YUV转换程序),将tex1(RGBA)转换为Y和UV(全分辨率),并分别存入texY和texUV。 4. 创建FBO3,附着纹理texUVHalf(RG8,宽度和高度为原图一半),然后将全分辨率的texUV下采样到texUVHalf(使用另一个着色器,简单的双线性过滤)。 5. 读取texY(全分辨率)和texUVHalf(半分辨率)的数据到内存,然后组合成NV12格式(先所有Y,然后UV交错排列)。 注意:由于NV12格式要求Y平面是8位单通道,UV平面是8位双通道(交错)但分辨率减半,所以: - Y平面大小 = 宽度 * 高度 - UV平面大小 = (宽度 * 高度) / 2 (因为每个UV纹素对应原图的2x2区域,包含2个字节:U和V) 因此,NV12总大小 = 宽度 * 高度 * 3 / 2。 代码结构: 1. 初始化:创建并编译着色器,创建纹理和FBO。 2. 主循环(这里假设我们只渲染一帧): a. 绑定FBO1,渲染场景(输出RGBA)。 b. 绑定FBO2(带两个颜色附着),使用YUV转换着色器,绘制一个全屏四边形(输入是FBO1的纹理),输出Y和UV(全分辨率)。 c. 绑定FBO3,使用下采样着色器(输入是FBO2的UV纹理),绘制一个全屏四边形(但视口设置为半分辨率),输出下采样后的UV纹理。 d. 从Y纹理(全分辨率)和UV纹理(半分辨率)读取数据。 e. 组合成NV12并保存文件。 由于代码较长,我们将分步骤实现。 注意:在OpenGL ES中,读取纹理数据使用glReadBuffer(指定要读取的附着)和glReadPixels。但是,由于我们有两个纹理(Y和UV),而且它们分别在不同的FBO中,我们需要分别读取。 另外,由于我们使用了多个FBO和多个纹理,需要小心管理状态。 以下是一个简化的实现,我们只关注核心步骤。 </think> 下面我将使用 OpenGL ES 3.0 实现您的离屏渲染框架。这个框架的核心是将渲染结果转换为 NV12 格式并保存到文件。 ### 实现方案 ```cpp #include <GLES3/gl3.h> #include <fstream> // 纹理尺寸 const int WIDTH = 640; const int HEIGHT = 480; // 着色器代码 const char* vsSource = R"( #version 300 es layout(location=0) in vec4 position; void main() { gl_Position = position; } )"; const char* fsSourceRGBA = R"( #version 300 es precision mediump float; out vec4 fragColor; void main() { fragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色测试图案 } )"; const char* fsSourceYUV = R"( #version 300 es precision mediump float; uniform sampler2D rgbaTex; out vec2 yuvOut[2]; // 双输出目标 const mat3 rgb2yuv = mat3( 0.299, -0.169, 0.499, 0.587, -0.331, -0.418, 0.114, 0.499, -0.0813 ); void main() { vec2 uv = gl_FragCoord.xy / vec2(float($W), float($H)); vec3 rgb = texture(rgbaTex, uv).rgb; vec3 yuv = rgb2yuv * rgb; // Y 平面输出 (全分辨率) yuvOut[0] = vec2(yuv.r, 0.0); // UV 平面输出 (半分辨率) if (mod(gl_FragCoord.x, 2.0) < 1.0 && mod(gl_FragCoord.y, 2.0) < 1.0) { yuvOut[1] = vec2(yuv.g, yuv.b); } } )".replace("$W", std::to_string(WIDTH)).replace("$H", std::to_string(HEIGHT)); // 创建着色器程序 GLuint createProgram(const char* vs, const char* fs) { GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vs, nullptr); glCompileShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fs, nullptr); glCompileShader(fragmentShader); GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return program; } // 主渲染函数 void render() { // 1. 创建离屏FBO和RGBA纹理 GLuint fbo, rgbaTex; glGenFramebuffers(1, &fbo); glGenTextures(1, &rgbaTex); glBindTexture(GL_TEXTURE_2D, rgbaTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, WIDTH, HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rgbaTex, 0); // 2. 渲染到离屏FBO GLuint programRGBA = createProgram(vsSource, fsSourceRGBA); glUseProgram(programRGBA); glViewport(0, 0, WIDTH, HEIGHT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 3. 创建YUV转换FBO和纹理 GLuint yuvFBO; glGenFramebuffers(1, &yuvFBO); // Y平面纹理 (全分辨率) GLuint yTex; glGenTextures(1, &yTex); glBindTexture(GL_TEXTURE_2D, yTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, WIDTH, HEIGHT, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); // UV平面纹理 (半分辨率) GLuint uvTex; glGenTextures(1, &uvTex); glBindTexture(GL_TEXTURE_2D, uvTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, WIDTH/2, HEIGHT/2, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); // 绑定到FBO glBindFramebuffer(GL_FRAMEBUFFER, yuvFBO); GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yTex, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, uvTex, 0); glDrawBuffers(2, buffers); // 4. YUV转换 GLuint programYUV = createProgram(vsSource, fsSourceYUV); glUseProgram(programYUV); glUniform1i(glGetUniformLocation(programYUV, "rgbaTex"), 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, rgbaTex); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 5. 读取NV12数据 std::vector<uint8_t> nv12Data(WIDTH * HEIGHT * 3 / 2); // 读取Y平面 glBindFramebuffer(GL_FRAMEBUFFER, yuvFBO); glReadBuffer(GL_COLOR_ATTACHMENT0); glReadPixels(0, 0, WIDTH, HEIGHT, GL_RED, GL_UNSIGNED_BYTE, nv12Data.data()); // 读取UV平面 glReadBuffer(GL_COLOR_ATTACHMENT1); glReadPixels(0, 0, WIDTH/2, HEIGHT/2, GL_RG, GL_UNSIGNED_BYTE, nv12Data.data() + WIDTH * HEIGHT); // 6. 保存为NV12文件 std::ofstream file("output.nv12", std::ios::binary); file.write(reinterpret_cast<char*>(nv12Data.data()), nv12Data.size()); // 清理资源 glDeleteTextures(1, &rgbaTex); glDeleteTextures(1, &yTex); glDeleteTextures(1, &uvTex); glDeleteFramebuffers(1, &fbo); glDeleteFramebuffers(1, &yuvFBO); glDeleteProgram(programRGBA); glDeleteProgram(programYUV); } ``` ### 代码解释 #### 1. 离屏渲染 (FBO + RGBA纹理) - 创建离屏FBO并附加RGBA8格式的颜色纹理 - 使用简单着色器渲染测试图案(红色) #### 2. YUV转换 - **双输出FBO**:同时输出Y平面和UV平面 - **Y平面**:使用R8纹理格式(单通道),全分辨率 - **UV平面**:使用RG8纹理格式(双通道),半分辨率(宽高各减半) - **RGB转YUV矩阵**:在片段着色器中进行颜色空间转换 - **下采样**:通过只处理偶像素实现UV分量2x下采样 #### 3. NV12数据生成 - **数据布局**: - Y平面:`WIDTH * HEIGHT` 字节 - UV平面:`(WIDTH * HEIGHT) / 2` 字节(U/V交替存储) - **纹理读取**: - 先读取Y平面到NV12数据起始位置 - 再读取UV平面到Y平面数据之后 #### 4. 关键优化点 - **多渲染目标(MRT)**:同时输出Y和UV平面 - **整数下采样**:在着色器中使用模运算实现2x下采样 - **带宽优化**:UV平面使用半分辨率纹理减少内存占用 #### 5. 文件输出 - 将内存中的NV12数据直接写入二进制文件 - 文件格式符合YUV420sp标准 ### 注意事项 1. 需要处理OpenGL ES上下文初始化 2. 顶点数据未展示(需提供全屏四边形) 3. 实际使用时需添加错误检查 4. 颜色转换矩阵可根据标准调整(如BT.601/BT.709) 5. NV12文件可用FFmpeg验证:`ffplay -f rawvideo -pix_fmt nv12 -s 640x480 output.nv12` 这个方案完整实现了从离屏渲染到NV12文件生成的整个流程,充分利用了OpenGL ES 3.0的多渲染目标和纹理特性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值