OpenGL ES ->帧缓冲对象(Frame Buffer Object)离屏渲染获取纹理贴图

XML文件

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- OpenGL渲染区域 -->
    <com.example.myapplication.MyGLSurfaceView
        android:id="@+id/gl_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- 用于显示FBO截图的ImageView -->
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:layout_margin="16dp"
        android:background="#33000000"
        android:contentDescription="FBO截图预览" />

    <!-- 捕获FBO图像的按钮 -->
    <Button
        android:id="@+id/capture_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:text="捕获FBO图像"
        android:padding="12dp" />

</RelativeLayout>

Activity代码

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    private lateinit var glSurfaceView: MyGLSurfaceView
    private lateinit var imageView: ImageView
    private lateinit var captureButton: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        glSurfaceView = findViewById(R.id.gl_surface_view)
        imageView = findViewById(R.id.image_view)
        captureButton = findViewById(R.id.capture_button)
        captureButton.setOnTouchListener(object : View.OnTouchListener {
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                when(event?.action){
                    MotionEvent.ACTION_DOWN -> {
                        getFrameBufferBitmap()?.let {
                            imageView.setImageBitmap(it)
                        }
                    }
                    MotionEvent.ACTION_UP -> {
                        getFrameBufferBitmap()?.let {
                            imageView.setImageBitmap(null)
                        }
                    }
                    else -> {}
                }
                return true
            }
        })
    }

    private fun getFrameBufferBitmap() : Bitmap? {
        return glSurfaceView?.getFrameBufferBitmap()
    }
}

自定义GLSurfaceView代码

kotlin 复制代码
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
    private var mRenderer = MyGLRenderer(context)

    init {
        // 设置 OpenGL ES 3.0 版本
        setEGLContextClientVersion(3)
        setRenderer(mRenderer)
        // 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
        renderMode = RENDERMODE_WHEN_DIRTY
    }

    fun getFrameBufferBitmap(): Bitmap? {
        return mRenderer?.getFrameBufferBitmap()
    }
}

自定义GLSurfaceView.Renderer代码

kotlin 复制代码
class MyGLRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
    private var mDrawData: DrawData? = null
    private var mWidth = 0
    private var mHeight = 0
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为青蓝色 (Red=0, Green=0.5, Blue=0.5, Alpha=1)
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
        mDrawData = DrawData().apply {
            initShader()
            initVertexBuffer()
            initTexture0(mContext, R.drawable.pic)
            initTexture1(mContext, R.drawable.bitmap_shader)
        }
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
        GLES30.glViewport(0, 0, width, height)
        mWidth = width
        mHeight = height
        mDrawData?.computeMVPMatrix(width, height)
        mDrawData?.initFrameBuffer(width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 每一帧绘制时调用, 清除颜色缓冲区
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        mDrawData?.enableTexture()
        mDrawData?.drawOffScreen()
        mDrawData?.disableTexture()
    }

    fun getFrameBufferBitmap(): Bitmap? {
       return mDrawData?.getScreenBitmap()
    }
}

GLSurfaceView.Renderer需要的绘制数据

kotlin 复制代码
class DrawData {
    private var mProgram: Int = -1
    private var NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2
    // FBO(Frame Buffer Object), 帧缓冲对象,用于存储渲染后的图像
    private var mFBO = IntArray(1)
    // VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
    private var mVAO = IntArray(1)
    // VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
    private var mVBO = IntArray(2)
    // IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据
    private var mIBO = IntArray(1)
    // 纹理ID
    private var mTextureID = IntArray(2)
    // FBO中的纹理ID
    private var mFBOTextureID = IntArray(1)
    // 最终变化矩阵
    private val mMVPMatrix = FloatArray(16)
    // 投影矩阵
    private val mProjectionMatrix = FloatArray(16)
    // 相机矩阵
    private val mViewMatrix = FloatArray(16)
    // 视口比例
    private var mViewPortRatio = 1f
    // 帧缓冲中的Bitmap
    private var mFrameBufferBitmap: Bitmap? = null
    // 帧缓冲宽高
    private var mFrameBufferWidth = 0
    private var mFrameBufferHeight = 0
    // 帧缓冲最终变换矩阵
    private val mFrameBufferMVPMatrix = FloatArray(16)

    // 准备顶点坐标,分配直接内存
    // OpenGL ES坐标系:原点在中心,X轴向右为正,Y轴向上为正,Z轴向外为正
    val vertex = floatArrayOf(
        -1.0f, 1.0f, 0.0f, // 左上
        -1.0f, -1.0f, 0.0f, // 左下
        1.0f, 1.0f, 0.0f, // 右上
        1.0f, -1.0f, 0.0f, // 右下
    )

    val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertex)
        .position(NO_OFFSET)

    // 准备纹理坐标,分配直接内存
    // 纹理坐标系:原点在左下角,X轴向右为正,Y轴向上为正
    val textureCoords = floatArrayOf(
        0.0f, 1.0f, // 左上
        0.0f, 0.0f, // 左下
        1.0f, 1.0f, // 右上
        1.0f, 0.0f, // 右下
    )

    val textureBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(textureCoords)
        .position(NO_OFFSET)

    // 索引坐标,分配直接内存
    val index = shortArrayOf(
        0, 1, 2, // 第一个三角形
        1, 3, 2, // 第二个三角形
    )

    val indexBuffer = ByteBuffer.allocateDirect(index.size * 2)
        .order(ByteOrder.nativeOrder())
        .asShortBuffer()
        .put(index)
        .position(NO_OFFSET)

    // 初始化着色器程序
    fun initShader() {
        val vertexShaderCode = """#version 300 es
                uniform mat4 uMVPMatrix; // 变换矩阵
                in vec4 aPosition; // 顶点坐标
                in vec2 aTexCoord; // 纹理坐标 
                out vec2 vTexCoord; 
                void main() {
                    // 输出顶点坐标和纹理坐标到片段着色器
                    gl_Position = uMVPMatrix * aPosition; 
                    vTexCoord = aTexCoord;
                }""".trimIndent()

        val fragmentShaderCode = """#version 300 es
             precision mediump float;
             uniform sampler2D uTexture_0;
             uniform sampler2D uTexture_1;
             in vec2 vTexCoord;
             out vec4 fragColor;
             void main() {
                 fragColor = texture(uTexture_0, vTexCoord) + texture(uTexture_1, vTexCoord);
             }""".trimIndent()

        // 加载顶点着色器和片段着色器, 并创建着色器程序
        val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader = LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
        mProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mProgram, vertexShader)
        GLES30.glAttachShader(mProgram, fragmentShader)
        GLES30.glLinkProgram(mProgram)
        GLES30.glUseProgram(mProgram)

        // 删除着色器对象
        GLES30.glDeleteShader(vertexShader)
        GLES30.glDeleteShader(fragmentShader)
    }

    // 创建VAO, VBO, IBO
    fun initVertexBuffer() {
        // 绑定VAO
        GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
        GLES30.glBindVertexArray(mVAO[0])
            // 绑定VBO
            GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
                // 绑定顶点缓冲区数据到VBO[0]
                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
                GLES30.glBufferData(
                    GLES30.GL_ARRAY_BUFFER,
                    vertex.size * 4,
                    vertexBuffer,
                    GLES30.GL_STATIC_DRAW
                )
                // 解析顶点缓冲区数据到VBO[0]
                val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
                GLES30.glEnableVertexAttribArray(positionHandle)
                GLES30.glVertexAttribPointer(
                    positionHandle,
                    VERTEX_POS_DATA_SIZE,
                    GLES30.GL_FLOAT,
                    false,
                    0,
                    NO_OFFSET
                )
                // 解绑顶点缓冲区
                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

                // 绑定纹理缓冲区数据到VBO[1]
                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
                GLES30.glBufferData(
                    GLES30.GL_ARRAY_BUFFER,
                    textureCoords.size * 4,
                    textureBuffer,
                    GLES30.GL_STATIC_DRAW
                )
                // 解析纹理缓冲区数据到VBO[1]
                val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
                GLES30.glEnableVertexAttribArray(textureHandle)
                GLES30.glVertexAttribPointer(
                    textureHandle,
                    TEXTURE_POS_DATA_SIZE,
                    GLES30.GL_FLOAT,
                    false,
                    0,
                    NO_OFFSET
                )
                // 解绑纹理缓冲区
                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

            // 绑定IBO
            GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
            // 绑定索引缓冲区数据到IBO[0]
            GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
            GLES30.glBufferData(
                GLES30.GL_ELEMENT_ARRAY_BUFFER,
                index.size * 2,
                indexBuffer,
                GLES30.GL_STATIC_DRAW
            )

        // 解绑VAO
        GLES30.glBindVertexArray(0)
        // 解绑IBO
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
    }

    // 计算GLSurfaceView变换矩阵
    fun computeMVPMatrix(width: Int, height: Int) {
        // 正交投影矩阵
        takeIf { width > height }?.let {
            mViewPortRatio = (width * 1f) / height
            Matrix.orthoM(
                mProjectionMatrix, // 正交投影矩阵
                NO_OFFSET, // 偏移量
                -mViewPortRatio, // 近平面的坐标系左边界
                mViewPortRatio, // 近平面的坐标系右边界
                -1f, // 近平面的坐标系的下边界
                1f, // 近平面坐标系的上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        } ?: run {
            mViewPortRatio = (height * 1f) / width
            Matrix.orthoM(
                mProjectionMatrix, // 正交投影矩阵
                NO_OFFSET, // 偏移量
                -1f, // 近平面坐标系左边界
                1f, // 近平面坐标系右边界
                -mViewPortRatio, // 近平面坐标系下边界
                mViewPortRatio, // 近平面坐标系上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        }

        // 设置相机矩阵
        // 相机位置(0f, 0f, 1f)
        // 物体位置(0f, 0f, 0f)
        // 相机方向(0f, 1f, 0f)
        Matrix.setLookAtM(
            mViewMatrix, // 相机矩阵
            NO_OFFSET, // 偏移量
            0f, // 相机位置x
            0f, // 相机位置y
            1f, // 相机位置z
            0f, // 物体位置x
            0f, // 物体位置y
            0f, // 物体位置z
            0f, // 相机上方向x
            1f, // 相机上方向y
            0f // 相机上方向z
        )

        // 最终变化矩阵
        Matrix.multiplyMM(
            mMVPMatrix, // 最终变化矩阵
            NO_OFFSET, // 偏移量
            mProjectionMatrix, // 投影矩阵
            NO_OFFSET, // 投影矩阵偏移量
            mViewMatrix, // 相机矩阵
            NO_OFFSET // 相机矩阵偏移量
        )

        // 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
        // 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
        Matrix.scaleM(
            mMVPMatrix,
            NO_OFFSET,
            1f,
            -1f,
            1f,
        )
    }

    // 初始化帧缓冲
    fun initFrameBuffer(){
        // 创建FBO
        GLES30.glGenFramebuffers(mFBO.size, mFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
            // 创建空纹理
            GLES30.glGenTextures(mFBOTextureID.size, mFBOTextureID, NO_OFFSET)
            // 绑定空纹理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFBOTextureID[0])
            // 设置纹理参数
            GLES30.glTexParameteri(
                GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_MIN_FILTER,
                GLES30.GL_LINEAR
            ) // 纹理缩小时使用线性插值
            GLES30.glTexParameteri(
                GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_MAG_FILTER,
                GLES30.GL_LINEAR
            ) // 纹理放大时使用线性插值
            GLES30.glTexParameteri(
                GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_WRAP_S,
                GLES30.GL_CLAMP_TO_EDGE
            ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
            GLES30.glTexParameteri(
                GLES30.GL_TEXTURE_2D,
                GLES30.GL_TEXTURE_WRAP_T,
                GLES30.GL_CLAMP_TO_EDGE
            ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充

            GLES30.glTexImage2D(
                GLES30.GL_TEXTURE_2D, // 纹理类型
                NO_OFFSET, // 偏移量
                GLES30.GL_RGBA, // 颜色通道
                mFrameBufferWidth, // 纹理宽度
                mFrameBufferHeight, // 纹理高度
                NO_OFFSET, // 偏移量
                GLES30.GL_RGBA, // 颜色通道
                GLES30.GL_UNSIGNED_BYTE, // 颜色数据类型
                null // 不传入颜色数据
            )
        // 绑定空纹理到FBO,用于绘制到FBO
        GLES30.glFramebufferTexture2D(
            GLES30.GL_FRAMEBUFFER, // FBO
            GLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区
            GLES30.GL_TEXTURE_2D, // 纹理类型
            mFBOTextureID[0], // 纹理ID
            0
        )

        if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) {
            Log.e("yang", "initFrameBuffer: FBO初始化失败")
        }
        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    // 离屏渲染
    fun drawOffScreen() {
        takeIf { mFrameBufferBitmap == null }?.let {
            // 帧缓冲绘制纹理需要修改视口大小,这里需要进行视口的保存和恢复
            val viewport = IntArray(4)
            GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, NO_OFFSET)
                // 绑定FBO
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
                    // 设置视口为FBO尺寸
                    GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)

                    // 计算帧缓冲纹理宽高的变换矩阵
                    computeFrameBufferMVPMatrix()

                    // 清除FBO
                    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

                    // 绘制图形到FBO, FBO上的内容不会显示
                    drawFrameBuffer()

                    // 缓存FBO内容到Bitmap
                    getBitmapFromFrameBuffer()
                // 解绑FBO,恢复默认帧缓冲
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            // 恢复原始视口
            GLES30.glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
        }
    }

    // 使用着色器程序绘制图形
    fun drawSomething() {
        // 解析变换矩阵
        val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
        GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)

        // 绑定VAO
        GLES30.glBindVertexArray(mVAO[0])
            // 绘制图形
            GLES30.glDrawElements(
                GLES30.GL_TRIANGLES,
                index.size,
                GLES30.GL_UNSIGNED_SHORT,
                NO_OFFSET
            )
        // 解绑VAO
        GLES30.glBindVertexArray(0)
    }

    fun drawFrameBuffer(){
        // 解析帧缓冲变换矩阵
        val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
        GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mFrameBufferMVPMatrix, NO_OFFSET)

        // 绑定VAO
        GLES30.glBindVertexArray(mVAO[0])
            // 绘制图形
            GLES30.glDrawElements(
                GLES30.GL_TRIANGLES,
                index.size,
                GLES30.GL_UNSIGNED_SHORT,
                NO_OFFSET
            )
        // 解绑VAO
        GLES30.glBindVertexArray(0)
    }

    fun enableTexture() {
        // 激活纹理编号0
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])
        val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture_0")
        GLES30.glUniform1i(textureSampleHandle, 0)

        // 激活纹理编号1
        GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[1])
        val textureSampleHandle1 = GLES30.glGetUniformLocation(mProgram, "uTexture_1")
        GLES30.glUniform1i(textureSampleHandle1, 1)
    }

    fun disableTexture() {
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
    }

    fun initTexture0(context: Context, resourceId: Int){
        mTextureID[0] = loadTexture(context, resourceId)
    }

    fun initTexture1(context: Context, resourceId: Int){
        mTextureID[1] = loadTexture(context, resourceId)
    }

    // 加载纹理
    fun loadTexture(context: Context, resourceId: Int): Int {
        val textureId = IntArray(1)
        // 生成纹理
        GLES30.glGenTextures(1, textureId, 0)
        // 绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
        // 设置纹理参数
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_MIN_FILTER,
            GLES30.GL_LINEAR
        ) // 纹理缩小时使用线性插值
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_MAG_FILTER,
            GLES30.GL_LINEAR
        ) // 纹理放大时使用线性插值
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_WRAP_S,
            GLES30.GL_CLAMP_TO_EDGE
        ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_WRAP_T,
            GLES30.GL_CLAMP_TO_EDGE
        ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
        // 加载图片
        val options = BitmapFactory.Options().apply {
            inScaled = false // 不进行缩放
        }
        val bitmap = BitmapFactory.decodeResource(context.resources, resourceId, options)
        // 将图片数据加载到纹理中
        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
        // 释放资源
        bitmap.recycle()
        // 解绑纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        mFrameBufferWidth = max(mFrameBufferWidth, bitmap.width)
        mFrameBufferHeight = max(mFrameBufferHeight, bitmap.height)
        Log.e("yang", "loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}")
        return textureId[0]
    }

    fun getBitmapFromFrameBuffer(){
        // 分配缓冲区来存储像素数据
        val pixelBuffer = ByteBuffer.allocateDirect(mFrameBufferWidth * mFrameBufferHeight * 4)
            .order(ByteOrder.LITTLE_ENDIAN)

        // 读取像素数据
        GLES30.glReadPixels(
            0, 0, mFrameBufferWidth, mFrameBufferHeight,
            GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
            pixelBuffer
        )

        // 将ByteBuffer转换为Bitmap
        val bitmap = Bitmap.createBitmap(mFrameBufferWidth, mFrameBufferHeight, Bitmap.Config.ARGB_8888)
        pixelBuffer.rewind()
        bitmap.copyPixelsFromBuffer(pixelBuffer)

        // OpenGL和Android的Y轴方向相反,需要围绕中心垂直翻转
        val matrix = android.graphics.Matrix().apply {
            setScale(1f, -1f, mFrameBufferWidth / 2f, mFrameBufferHeight / 2f)
        }

        mFrameBufferBitmap = Bitmap.createBitmap(
            bitmap, 0, 0, bitmap.width, bitmap.height,
            matrix, true
        )

        bitmap.recycle()
    }

    fun getScreenBitmap() : Bitmap?{
        return mFrameBufferBitmap
    }

    fun computeFrameBufferMVPMatrix() {
        // 正交投影矩阵
        takeIf { mFrameBufferWidth > mFrameBufferHeight }?.let {
            mViewPortRatio = (mFrameBufferWidth * 1f) / mFrameBufferHeight
            Matrix.orthoM(
                mProjectionMatrix, // 正交投影矩阵
                NO_OFFSET, // 偏移量
                -mViewPortRatio, // 近平面的坐标系左边界
                mViewPortRatio, // 近平面的坐标系右边界
                -1f, // 近平面的坐标系的下边界
                1f, // 近平面坐标系的上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        } ?: run {
            mViewPortRatio = (mFrameBufferHeight * 1f) / mFrameBufferWidth
            Matrix.orthoM(
                mProjectionMatrix, // 正交投影矩阵
                NO_OFFSET, // 偏移量
                -1f, // 近平面坐标系左边界
                1f, // 近平面坐标系右边界
                -mViewPortRatio, // 近平面坐标系下边界
                mViewPortRatio, // 近平面坐标系上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        }

        // 设置相机矩阵
        // 相机位置(0f, 0f, 1f)
        // 物体位置(0f, 0f, 0f)
        // 相机方向(0f, 1f, 0f)
        Matrix.setLookAtM(
            mViewMatrix, // 相机矩阵
            NO_OFFSET, // 偏移量
            0f, // 相机位置x
            0f, // 相机位置y
            1f, // 相机位置z
            0f, // 物体位置x
            0f, // 物体位置y
            0f, // 物体位置z
            0f, // 相机上方向x
            1f, // 相机上方向y
            0f // 相机上方向z
        )

        // 最终变化矩阵
        Matrix.multiplyMM(
            mFrameBufferMVPMatrix, // 最终变化矩阵
            NO_OFFSET, // 偏移量
            mProjectionMatrix, // 投影矩阵
            NO_OFFSET, // 投影矩阵偏移量
            mViewMatrix, // 相机矩阵
            NO_OFFSET // 相机矩阵偏移量
        )

        // 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
        // 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
        Matrix.scaleM(
            mFrameBufferMVPMatrix,
            NO_OFFSET,
            1f,
            -1f,
            1f,
        )
    }

    object LoadShaderUtil {
        // 创建着色器对象
        fun loadShader(type: Int, source: String): Int {
            val shader = GLES30.glCreateShader(type)
            GLES30.glShaderSource(shader, source)
            GLES30.glCompileShader(shader)
            return shader
        }
    }
}

效果图

相关推荐
大胃粥10 分钟前
Android app 冷启动(7) 执行动画
android
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧14 分钟前
C语言_数据结构总结4:不带头结点的单链表
c语言·开发语言·数据结构·算法·链表·visualstudio·visual studio
yi诺千金16 分钟前
Android U 分屏——SystemUI侧处理
android
顾林海18 分钟前
Flutter Dart 流程控制语句详解
android·前端·flutter
极客代码28 分钟前
Linux IPC:System V共享内存汇总整理
linux·c语言·开发语言·并发·共享内存·通信·system v
Cui晨32 分钟前
Android 滑块开关 自定义Switch
android
orangapple34 分钟前
一个差劲的软件设计
开发语言·c#
&有梦想的咸鱼&35 分钟前
Android Retrofit 框架注解定义与解析模块深度剖析(一)
android·retrofit
烬奇小云35 分钟前
安卓7.0到11.0的更新变化(简单理解)
android·安卓逆向
算法与编程之美37 分钟前
冒泡排序
java·开发语言·数据结构·算法·排序算法