OpenGL ES ->乒乓缓冲,计算只用两个帧缓冲对象(Frame Buffer Object)+叠加多个滤镜作用后的Bitmap

news2025/3/24 15:55:07

乒乓缓冲核心思想

  • 不使用乒乓缓冲,如果要每个滤镜作用下的绘制内容,也就是这个滤镜作用下的帧缓冲,需要创建一个Frame Buffer Object加上对应的Frame Buffer Object Texture
  • 使用乒乓缓冲,只用两个Frame Buffer Object加上对应的Frame Buffer Object TextureOpen GL渲染管线不允许读取一个帧缓冲的同时,对这个帧缓冲的纹理进行写入,这样会导致读写冲突绘制内容出错,所以用到乒乓缓冲,绑定一个帧缓冲的同时,使用另外一个帧缓冲的纹理,交替使用

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

    <ImageView
        android:id="@+id/image_view_1"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_margin="10dp"
        android:background="#33000000" />

    <ImageView
        android:id="@+id/image_view_2"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:layout_margin="10dp"
        android:background="#33000000" />

    <ImageView
        android:id="@+id/image_view_3"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="10dp"
        android:background="#33000000" />

    <ImageView
        android:id="@+id/image_view_4"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="10dp"
        android:background="#33000000" />
    <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="10dp"
        android:text="滤镜渲染"
        android:padding="12dp" />

</RelativeLayout>

Activity代码

class MainActivity : AppCompatActivity() {
    private lateinit var glSurfaceView: MyGLSurfaceView
    private lateinit var imageView1: ImageView
    private lateinit var imageView2: ImageView
    private lateinit var imageView3: ImageView
    private lateinit var imageView4: ImageView
    private lateinit var captureButton: Button

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        glSurfaceView = findViewById(R.id.gl_surface_view)
        imageView1 = findViewById(R.id.image_view_1)
        imageView2 = findViewById(R.id.image_view_2)
        imageView3 = findViewById(R.id.image_view_3)
        imageView4 = findViewById(R.id.image_view_4)
        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 -> {
                        glSurfaceView?.getDrawData()?.getEdgeFilterBitmap()?.let {
                            imageView1.setImageBitmap(it)
                        }
                        glSurfaceView?.getDrawData()?.getPixelFilterBitmap()?.let {
                            imageView2.setImageBitmap(it)
                        }
                        glSurfaceView?.getDrawData()?.getColorFilterBitmap()?.let {
                            imageView3.setImageBitmap(it)
                        }
                        glSurfaceView?.getDrawData()?.getOriginBitmap()?.let {
                            imageView4.setImageBitmap(it)
                        }
                    }

                    MotionEvent.ACTION_UP -> {
                        imageView1.setImageBitmap(null)
                        imageView2.setImageBitmap(null)
                        imageView3.setImageBitmap(null)
                        imageView4.setImageBitmap(null)
                    }
                }
                return true
            }
        })
    }
}

自定义GLSurfaceView代码

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 getDrawData(): DrawData? {
        return mRenderer?.getDrawData()
    }
}

自定义GLSurfaceView.Renderer代码

class MyGLRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
    private var mDrawData: DrawData? = null

    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 {
            initTexture0(mContext, R.drawable.picture)
            initShader()
            initVertexBuffer()
            initFrameBuffer()
            initEdgeFilterShader()
            initPixelFilterShader()
            initColorFilterShader()
            initPingFrameBuffer()
            initPongFrameBuffer()
        }
    }

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

    override fun onDrawFrame(gl: GL10?) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        // 绘制到FBO(离屏渲染)
        mDrawData?.drawOriginFrameBuffer()
        // 缓存像素着色器
        mDrawData?.drawPixelFilterBitmap()
        // 缓存彩色旋涡
        mDrawData?.drawColorFilterBitmap()
        // 缓存边缘滤镜
        mDrawData?.drawEdgeFilterBitmap()
        // 在GLSurfaceView绘制FBO内容
        mDrawData?.drawGLSurfaceView()
    }

    fun getDrawData(): DrawData? {
        return mDrawData
    }
}

GLSurfaceView.Renderer需要的绘制数据

class DrawData {
    private var NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2
    private var mProgram: Int = -1
    private var mEdgeProgram : Int = -1 // 边缘检测
    private var mPixelProgram : Int = -1 // 像素着色
    private var mColorProgram : Int = -1 // 彩色旋涡

    // FBO(Frame Buffer Object), 帧缓冲对象,用于存储渲染后的图像
    private var mFBO = IntArray(1)
    private var mEdgeFBO = IntArray(1)
    private var mPixelFBO = IntArray(1)
    private var mColorFBO = IntArray(1)
    private var mPingFBO = IntArray(1)
    private var mPongFBO = 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(1)

    // FBO中的纹理ID
    private var mFBOTextureID = IntArray(1)
    private var mEdgeFBOTextureID = IntArray(1)
    private var mPixelFBOTextureID = IntArray(1)
    private var mColorFBOTextureID = IntArray(1)
    private var mPingTextureID = IntArray(1)
    private var mPongTextureID = IntArray(1)
    // 有上下翻转的变换矩阵
    private var mMVPMatrix = FloatArray(16)

    // 投影矩阵
    private val mProjectionMatrix = FloatArray(16)

    // 相机矩阵
    private val mViewMatrix = FloatArray(16)

    // 视口比例
    private var mViewPortRatio = 1f

    // 帧缓冲宽高
    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)
    private var mOriginBitmap : Bitmap ?= null
    private var mEdgeFilterBitmap : Bitmap ?= null
    private var mPixelFilterBitmap : Bitmap ?= null
    private var mColorFilterBitmap : Bitmap ?= null

    // 初始化着色器程序
    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;
         in vec2 vTexCoord;
         out vec4 fragColor;
         void main() {
             fragColor = texture(uTexture_0, 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)
    }

    // 初始化帧缓冲
    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)
    }

    // FrameBuffer内容绘制(离屏渲染)
    fun drawOriginFrameBuffer() {
        val previousProgram = IntArray(1)
        val previousFrameBuffer = IntArray(1)
        val viewPort = IntArray(4)
        try {
            GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)
            GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)
            GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)
            GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
            enableTexture0(mProgram, mTextureID[0])
                // 绑定FBO
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
                    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
                    computeFrameBufferMVPMatrix()
                    drawSomething(mProgram, mFrameBufferMVPMatrix)
                    mOriginBitmap = savePixelBufferBitmap()
                // 解绑FBO
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            disableTexture0()
        }finally {
            GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])
            GLES30.glUseProgram(previousProgram[0])
        }
    }

    // GLSurfaceView内容绘制
    fun drawGLSurfaceView() {
        val previousProgram = IntArray(1)
        val previousFrameBuffer = IntArray(1)
        try {
            GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)
            GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)
            enableTexture0(mProgram, mPingTextureID[0])
                drawSomething(mProgram, mMVPMatrix)
            disableTexture0()
        } finally {
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])
            GLES30.glUseProgram(previousProgram[0])
        }
    }


    fun drawEdgeFilterBitmap() {
        takeIf { mEdgeFilterBitmap == null }?.let {
            val previousProgram = IntArray(1)
            val previousFrameBuffer = IntArray(1)
            val viewPort = IntArray(4)
            try {
                GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)
                GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)
                GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)
                GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
                GLES30.glUseProgram(mEdgeProgram)
                    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])
                        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
                        enableTexture2(mEdgeProgram, mPongTextureID[0])
                            // 纹理大小
                            val textureSizeHandle = GLES30.glGetUniformLocation(mEdgeProgram, "uTextureSize")
                            GLES30.glUniform2f(textureSizeHandle, mFrameBufferWidth.toFloat(), mFrameBufferHeight.toFloat())
                            drawSomething(mEdgeProgram, mFrameBufferMVPMatrix)
                            mEdgeFilterBitmap = savePixelBufferBitmap()
                        disableTexture2()
                    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            } finally {
                GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])
                GLES30.glUseProgram(previousProgram[0])
            }
        }
    }

    fun drawPixelFilterBitmap() {
        takeIf { mPixelFilterBitmap == null }?.let {
            val previousProgram = IntArray(1)
            val previousFrameBuffer = IntArray(1)
            val viewPort = IntArray(4)
            try {
                GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)
                GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)
                GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)
                GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
                GLES30.glUseProgram(mPixelProgram)
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])
                    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
                    enableTexture3(mPixelProgram, mFBOTextureID[0])
                        // 纹理大小
                        val textureSizeHandle = GLES30.glGetUniformLocation(mPixelProgram, "uTextureSize")
                        GLES30.glUniform2f(textureSizeHandle, mFrameBufferWidth.toFloat(), mFrameBufferWidth.toFloat())
                        // 像素尺寸
                        val pixelSizeHandle = GLES30.glGetUniformLocation(mPixelProgram, "uPixelSize")
                        GLES30.glUniform1f(pixelSizeHandle, 15.0f) // 可调节
                        drawSomething(mPixelProgram, mFrameBufferMVPMatrix)
                        mPixelFilterBitmap = savePixelBufferBitmap()
                    disableTexture3()
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            } finally {
                GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])
                GLES30.glUseProgram(previousProgram[0])
            }
        }
    }

    fun drawColorFilterBitmap() {
        takeIf { mColorFilterBitmap == null }?.let {
            val previousProgram = IntArray(1)
            val previousFrameBuffer = IntArray(1)
            val viewPort = IntArray(4)
            try {
                GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)
                GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
                GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)
                GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)
                GLES30.glUseProgram(mColorProgram)
                    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPongFBO[0])
                    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
                    enableTexture4(mColorProgram, mPingTextureID[0])
                    // 流动时间
                    val timeHandle = GLES30.glGetUniformLocation(mColorProgram, "uTime")
                    if (timeHandle != -1) { // 检查是否存在该参数
                        GLES30.glUniform1f(timeHandle, (System.currentTimeMillis() % 10000) / 10000.0f)
                    }

                    // 设置旋涡强度
                    val twistIntensityHandle = GLES30.glGetUniformLocation(mColorProgram, "uTwistIntensity")
                    GLES30.glUniform1f(twistIntensityHandle, 0.15f)
                    drawSomething(mColorProgram, mFrameBufferMVPMatrix)
                    mColorFilterBitmap = savePixelBufferBitmap()
                    disableTexture4()
                    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            } finally {
                GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])
                GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])
                GLES30.glUseProgram(previousProgram[0])
            }
        }
    }

    // 使用着色器程序绘制图形
    fun drawSomething(program: Int, mvpMatrix: FloatArray) {
        // 解析变换矩阵
        val matrixHandle = GLES30.glGetUniformLocation(program, "uMVPMatrix")
        GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mvpMatrix, 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 initEdgeFilterShader() {
        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_2;
            uniform vec2 uTextureSize;
            in vec2 vTexCoord;
            out vec4 fragColor;
        
            void main() {
                float dx = 1.0 / uTextureSize.x;
                float dy = 1.0 / uTextureSize.y;
                
                // 获取周围像素
                vec4 center = texture(uTexture_2, vTexCoord);
                vec4 left = texture(uTexture_2, vTexCoord - vec2(dx, 0.0));
                vec4 right = texture(uTexture_2, vTexCoord + vec2(dx, 0.0));
                vec4 top = texture(uTexture_2, vTexCoord - vec2(0.0, dy));
                vec4 bottom = texture(uTexture_2, vTexCoord + vec2(0.0, dy));
                
                // 计算边缘
                vec4 horizontal = abs(right - left);
                vec4 vertical = abs(bottom - top);
                float edge = (horizontal.r + horizontal.g + horizontal.b + 
                              vertical.r + vertical.g + vertical.b) / 6.0;
                
                // 边缘增强
                fragColor = vec4(vec3(1.0 - edge * 3.0), 1.0);
            }""".trimIndent()

        // 加载着色器并创建程序
        val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader =
            LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
        mEdgeProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mEdgeProgram, vertexShader)
        GLES30.glAttachShader(mEdgeProgram, fragmentShader)
        GLES30.glLinkProgram(mEdgeProgram)

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

    // 像素着色器
    fun initPixelFilterShader() {
        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_3;
            uniform vec2 uTextureSize;
            uniform float uPixelSize; // 像素块大小 (10.0-30.0)
            in vec2 vTexCoord;
            out vec4 fragColor;
            void main() {
                float dx = uPixelSize / uTextureSize.x;
                float dy = uPixelSize / uTextureSize.y;
                
                // 计算像素块的中心坐标
                vec2 pixelatedCoord;
                pixelatedCoord.x = dx * floor(vTexCoord.x / dx) + dx * 0.5;
                pixelatedCoord.y = dy * floor(vTexCoord.y / dy) + dy * 0.5;
                
                // 采样像素块中心的颜色
                vec4 pixelColor = texture(uTexture_3, pixelatedCoord);
                
                // 减少颜色深度,增强复古效果
                const float colorLevels = 5.0;
                pixelColor = floor(pixelColor * colorLevels) / colorLevels;
                
                // 添加微小的像素边框
                vec2 pixelPos = fract(vTexCoord / vec2(dx, dy));
                float borderFactor = step(0.95, max(pixelPos.x, pixelPos.y));
                pixelColor.rgb *= mix(1.0, 0.8, borderFactor);
                
                fragColor = pixelColor;
            }""".trimIndent()

        // 加载着色器并创建程序
        val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader =
            LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
        mPixelProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mPixelProgram, vertexShader)
        GLES30.glAttachShader(mPixelProgram, fragmentShader)
        GLES30.glLinkProgram(mPixelProgram)

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

    // 彩色旋涡滤镜
    fun initColorFilterShader() {
        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_4;
        uniform float uTime; // 动画时间 (如果需要)
        uniform float uTwistIntensity; // 旋涡强度 (0.1-0.3)
        in vec2 vTexCoord;
        out vec4 fragColor;
        
        // 更高效的RGB转HSV函数
        vec3 rgb2hsv(vec3 c) {
            vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
            vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
            vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
            
            float d = q.x - min(q.w, q.y);
            float e = 1.0e-10;
            return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
        }
        
        // 更高效的HSV转RGB函数
        vec3 hsv2rgb(vec3 c) {
            vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
            vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
            return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
        }
        
        void main() {
            // 将纹理坐标转换为中心为原点的坐标
            vec2 center = vec2(0.5, 0.5);
            vec2 texCoordFromCenter = vTexCoord - center;
            
            // 计算距离和角度
            float distance = length(texCoordFromCenter);
            float angle = atan(texCoordFromCenter.y, texCoordFromCenter.x);
            
            // 应用旋涡扭曲
            angle += uTwistIntensity * (1.0 - distance);
            
            // 将极坐标转换回纹理坐标
            vec2 newCoord;
            newCoord.x = center.x + distance * cos(angle);
            newCoord.y = center.y + distance * sin(angle);
            
            // 采样扭曲后的颜色
            vec4 color = texture(uTexture_4, newCoord);
            
            // 增强颜色对比度和饱和度
            vec3 hsv = rgb2hsv(color.rgb);
            hsv.y = hsv.y * 1.4; // 增加饱和度
            hsv.z = hsv.z * 0.9 + 0.1; // 增加亮度和对比度
            
            // 添加彩虹色效果
            hsv.x = hsv.x + distance * 0.5;
            
            fragColor = vec4(hsv2rgb(hsv), color.a);
        }""".trimIndent()

        // 加载着色器并创建程序
        val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader =
            LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
        mColorProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mColorProgram, vertexShader)
        GLES30.glAttachShader(mColorProgram, fragmentShader)
        GLES30.glLinkProgram(mColorProgram)

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

    // 边缘滤镜的帧缓冲
    fun initEdgeFrameBuffer() {
        // 创建FBO
        GLES30.glGenFramebuffers(mEdgeFBO.size, mEdgeFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mEdgeFBO[0])
        // 创建空纹理
        GLES30.glGenTextures(mEdgeFBOTextureID.size, mEdgeFBOTextureID, NO_OFFSET)
        // 绑定空纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mEdgeFBOTextureID[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, // 纹理类型
            mEdgeFBOTextureID[0], // 纹理ID
            0
        )

        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    // 像素滤镜的帧缓冲
    fun initPixelFrameBuffer() {
        // 创建FBO
        GLES30.glGenFramebuffers(mPixelFBO.size, mPixelFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPixelFBO[0])
        // 创建空纹理
        GLES30.glGenTextures(mPixelFBOTextureID.size, mPixelFBOTextureID, NO_OFFSET)
        // 绑定空纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPixelFBOTextureID[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, // 纹理类型
            mPixelFBOTextureID[0], // 纹理ID
            0
        )

        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    // 彩色旋涡的帧缓冲
    fun initColorFrameBuffer() {
        // 创建FBO
        GLES30.glGenFramebuffers(mColorFBO.size, mColorFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mColorFBO[0])
        // 创建空纹理
        GLES30.glGenTextures(mColorFBOTextureID.size, mColorFBOTextureID, NO_OFFSET)
        // 绑定空纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorFBOTextureID[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, // 纹理类型
            mColorFBOTextureID[0], // 纹理ID
            0
        )

        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    fun initPingFrameBuffer() {
        // 创建FBO
        GLES30.glGenFramebuffers(mPingFBO.size, mPingFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])
        // 创建空纹理
        GLES30.glGenTextures(mPingTextureID.size, mPingTextureID, NO_OFFSET)
        // 绑定空纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPingTextureID[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, // 纹理类型
            mPingTextureID[0], // 纹理ID
            0
        )

        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    fun initPongFrameBuffer() {
        // 创建FBO
        GLES30.glGenFramebuffers(mPongFBO.size, mPongFBO, NO_OFFSET)
        // 绑定FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPongFBO[0])
        // 创建空纹理
        GLES30.glGenTextures(mPongTextureID.size, mPongTextureID, NO_OFFSET)
        // 绑定空纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPongTextureID[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, // 纹理类型
            mPongTextureID[0], // 纹理ID
            0
        )

        // 解绑FBO纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        // 解绑FBO
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
    }

    fun savePixelBufferBitmap(): Bitmap? {
        // 分配缓冲区来存储像素数据
        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)

        return bitmap
    }

    // 计算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 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,
//        )
    }
    // 加载纹理
    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]
    }

    // 激活纹理编号0
    fun enableTexture0(program: Int, id: Int) {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
        val textureSampleHandle = GLES30.glGetUniformLocation(program, "uTexture_0")
        GLES30.glUniform1i(textureSampleHandle, 0)
    }



    // 激活纹理编号2
    fun enableTexture2(program: Int, id: Int) {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE2)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
        val textureSampleHandle2 = GLES30.glGetUniformLocation(program, "uTexture_2")
        GLES30.glUniform1i(textureSampleHandle2, 2)
    }

    // 激活纹理编号3
    fun enableTexture3(program: Int, id: Int) {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE3)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
        val textureSampleHandle3 = GLES30.glGetUniformLocation(program, "uTexture_3")
        GLES30.glUniform1i(textureSampleHandle3, 3)
    }

    // 激活纹理编号4
    fun enableTexture4(program: Int, id: Int) {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE4)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
        val textureSampleHandle4 = GLES30.glGetUniformLocation(program, "uTexture_4")
        GLES30.glUniform1i(textureSampleHandle4, 4)
    }

    fun disableTexture0() {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
    }



    fun disableTexture2() {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE2)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 2)
    }

    fun disableTexture3() {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE3)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 3)
    }

    fun disableTexture4(){
        GLES30.glActiveTexture(GLES30.GL_TEXTURE4)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 4)
    }

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

    fun getEdgeFilterBitmap(): Bitmap? {
        return mEdgeFilterBitmap
    }

    fun getPixelFilterBitmap(): Bitmap? {
        return mPixelFilterBitmap
    }

    fun getColorFilterBitmap(): Bitmap? {
        return mColorFilterBitmap
    }

    fun getOriginBitmap(): Bitmap? {
        return mOriginBitmap
    }

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

效果图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库练习2

目录 1.向heros表中新增一列信息&#xff0c;添加一些约束&#xff0c;并尝试查询一些信息 2.课堂代码练习 3.题目如下 一、单表查询 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 3、求出所有职工的人数。 4…

macOS Sequoia 15.3 一直弹出“xx正在访问你的屏幕”

&#x1f645; 问题描述 macOS 系统升级后&#xff08;15.2或者15.3均出现过此问题&#xff09;&#xff0c;不管是截图还是开腾讯会议&#xff0c;只要跟捕捉屏幕有关&#xff0c;都一直弹出这个选项&#xff0c;而且所有软件我都允许访问屏幕了&#xff0c;这个不是询问是否…

C# 调用 VITS,推理模型 将文字转wav音频调试 -数字人分支

Microsoft.ML.OnnxRuntime.OnnxRuntimeException: [ErrorCode:InvalidArgument] Input name: input_name is not in the metadata在 Microsoft.ML.OnnxRuntime.InferenceSession.LookupInputMetadata(String nodeName) 位置 D:\a\_work\1\s\csharp\src\Microsoft.ML.OnnxRuntim…

【Docker系列一】Docker 简介

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C++进阶——封装红黑树实现map和set

目录 1、源码及框架分析 2、模拟实现map和set 2.1 复用的红黑树框架及Insert 2.2 iterator的实现 2.2.1 iterator的核心源码 2.2.2 iterator的实现思路 2.3 map支持[ ] 2.4 map和set的代码实现 2.4.1 MyMap.h 2.4.2 MySet.h 2.4.3 RBTree.h 2.4.4 Test.cpp 1、源码及…

‘闭包‘, ‘装饰器‘及其应用场景

‘闭包’, 装饰器’及其应用场景 一, 闭包及其应用场景 图解 闭包的定义 概述: 内部函数 使用了 外部函数 的变量, 这种写法就称之为闭包. 格式: def 外部函数名(形参列表):外部函数的(局部)变量def 内部函数名(形参列表):内部函数的(局部)变量return 内部函数名前提条件: …

IDEA 快捷键ctrl+shift+f 无法全局搜索内容的问题及解决办法

本篇文章主要讲解IDEA、phpStrom、webStrom、pyCharm等jetbrains系列编辑器无法进行全局搜索内容问题的主要原因及解决办法。 日期&#xff1a;2025年3月22日 作者&#xff1a;任聪聪 现象描述&#xff1a; 1.按下ctrlshiftf 输入法转为了繁体。 2.快捷键ctrlshiftr 可以全局检…

Powershell WSL导出导入ubuntu22.04.5子系统

导出Linux子系统 导出位置在C盘下,根据自己的实际情况更改即可Write-Host "export ubuntu22.04.5" -ForegroundColor Green wsl --export Ubuntu-22.04 c:\Ubuntu-22.04.tar 导入Linux子系统 好处是目录可用在任意磁盘路径,便于迁移不同的设备之间Write-Host &quo…

论文笔记(七十三)Gemini Robotics: Bringing AI into the Physical World

Gemini Robotics: Bringing AI into the Physical World 文章概括1. 引言2. Gemini 2.0的具身推理2.1. 具身推理问答&#xff08;ERQA&#xff09;基准测试2.2. Gemini 2.0的具身推理能力2.3. Gemini 2.0支持零样本和少样本机器人控制 3. 使用 Gemini Robotics 执行机器人动作3…

AI + 医疗 Qwq大模型离线本地应用

通义千问Qwq-32b-FP16可用于社区医院、乡镇卫生院、诊所等小型医疗机构&#xff0c;替代专业合理用药系统&#xff0c;作为药品知识库&#xff0c;实现以下功能&#xff1a; 药品信息智能查询&#xff1a;检索药品的详细说明书、适应症、禁忌症、不良反应及药物相互作用等关键信…

元音辅音及其字母组合发音

文章目录 单元音长元音/ɑː//ɔ://u://i://ɜː/// 短元音/ʌ//ɒ//ʊ//ɪ//ə//e/ 双元音/eɪ//aɪ//ɔɪ//ɪə//eə//ʊə//əʊ//aʊ/ 辅音3个鼻辅音m n ŋ 5个独立浊辅音w j r l h 20个清浊相对的辅音s zʃ ʒf vθ p bt dk gts dztʃ dʒtr dr 以下是列举的部分字母组合…

【Vitis AIE】FPGA图像处理 11 双线性插值 Bilinear Interpolation

双线性插值 https://github.com/Xilinx/Vitis-Tutorials/tree/2024.2/AI_Engine_Development/AIE/Design_Tutorials/11-Bilinear_Interpolation 简介 双线性插值是一种使用重复线性插值来插值两个变量函数的方法。它通常用于以下应用&#xff1a; 图像处理和计算机视觉&…

Linux | 安装 Samba将ubuntu 的存储空间指定为windows 上的一个磁盘

01 安装 samba 文件来实现。比如把我们 ubuntu 的存储空间指定为我们 windows 上的一个磁盘,然后我们在这个磁盘里面创建 .c 文件,进行我们代码的修改和编写,可以安装 samba 文件来实现。 samba 是一种网络共享服务,可以通过网络访问我们指定的文件夹 02 第一步:下…

一文说清预训练与微调:AI的双重训练法则

什么是预训练&#xff1f; 预训练是大型语言模型训练的第一步。它在资金和计算能力的支持下&#xff0c;通过深入分析大量的文本数据&#xff0c;使模型建立起语言的基本构架。在这一阶段&#xff0c;模型通过学习海量的书籍、文章和网页&#xff0c;识别出语言的语法、句法和…

996引擎-接口测试:音效测试NPC

996引擎-接口测试:音效测试NPC 参考资料local offset = 1 -- 默认偏移量function main(player, newOffset)offset = newOffset or offset -- 更新偏移量local buttonWidth =

miniconda安装保姆级教程|win11|深度学习环境配置

一、官网安装miniconda miniconda官网&#xff1a;Miniconda - Anaconda 点击Download按钮 在红框位置输入邮箱并点击submit&#xff0c;下载链接将会发到邮箱中 邮箱中将会收到如图所示邮件&#xff0c;点击下载 选择windows对应的miniconda安装包 miniconda安装包安装完成如…

Linux shell脚本-概述、语法定义、自定义变量、环境变量、预设变量、变量的特殊用法(转义字符、单双引号、大小括号)的验证

目录 1.shell概述 1.1作为应用程序&#xff1a; 1.2 shell 作为一门语言 2.shell 语法 2.1 shell脚本的定义与执行 &#xff08;1&#xff09;新建文件 &#xff08;2&#xff09;程序开头第一行 必须写shell的类型 &#xff08;3&#xff09;程序编写完后&#xff0c…

数据驱动进化:AI Agent如何重构手机交互范式?

如果说AIGC拉开了内容生成的序幕&#xff0c;那么AI Agent则标志着AI从“工具”向“助手”的跨越式进化。它不再是简单的问答机器&#xff0c;而是一个能够感知环境、规划任务并自主执行的智能体&#xff0c;更像是虚拟世界中的“全能员工”。 正如行业所热议的&#xff1a;“大…

DL学习笔记:穿戴设备上的轻量级人体活动识别方法

Hello&#xff0c;大家好&#xff01;这里是《Dream 的深度学习笔记》,本系列将聚焦三个学习方面&#xff1a; 论文解读&#xff1a;拆解经典论文与最新突破 技术实现&#xff1a;从模型搭建到实际部署 应用案例&#xff1a;涵盖图像识别、深度学习、人工智能等热门方向 让…

windows安装配置FFmpeg教程

1.先访问官网&#xff1a;https://www.gyan.dev/ffmpeg/builds/ 2.选择安装包Windows builds from gyan.dev 3. 下滑找到release bulids部分&#xff0c;选择ffmpeg-7.0.2-essentials_build.zip 4. 然后解压将bin目录添加path系统变量&#xff1a;\ffmpeg-7.0.2-essentials_bui…