Open GL ES ->GLSurfaceView在正交投影下的图片旋转、缩放、位移

XML文件

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

    <com.example.myapplication.MyMatrixGLSurfaceView
        android:id="@+id/glSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4" />

    <!-- 旋转控制按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="旋转:"
            android:textSize="16sp" />

        <Button
            android:id="@+id/rotateXBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="X轴" />

        <Button
            android:id="@+id/rotateYBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Y轴" />

        <Button
            android:id="@+id/rotateZBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Z轴" />

    </LinearLayout>

    <!-- 缩放控制按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="缩放:"
            android:textSize="16sp" />

        <Button
            android:id="@+id/scaleXBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="X轴" />

        <Button
            android:id="@+id/scaleYBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Y轴" />

    </LinearLayout>

    <!-- 位移控制按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="位移:"
            android:textSize="16sp" />

        <Button
            android:id="@+id/translateXPosBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="X轴" />

        <Button
            android:id="@+id/translateYPosBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Y轴" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="正交投影:" />

        <Button
            android:id="@+id/resetBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="重置" />
    </LinearLayout>

</LinearLayout>

Activity代码

kotlin 复制代码
class MainActivity5 : AppCompatActivity(), View.OnClickListener {

    companion object {
        // 变换增量
        private const val ROTATION_DELTA = 10.0f
        private const val SCALE_DELTA = 0.25f
        private const val TRANSLATE_DELTA = 0.25f
    }

    private lateinit var glSurfaceView: MyMatrixGLSurfaceView

    // 变换参数
    private var rotationX = 0f
    private var rotationY = 0f
    private var rotationZ = 0f
    private var scaleX = 1.0f
    private var scaleY = 1.0f
    private var translateX = 0f
    private var translateY = 0f

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main5)

        // 初始化GLSurfaceView
        glSurfaceView = findViewById(R.id.glSurfaceView)

        // 初始化所有按钮并设置点击监听器
        findViewById<Button>(R.id.rotateXBtn).setOnClickListener(this)
        findViewById<Button>(R.id.rotateYBtn).setOnClickListener(this)
        findViewById<Button>(R.id.rotateZBtn).setOnClickListener(this)
        findViewById<Button>(R.id.scaleXBtn).setOnClickListener(this)
        findViewById<Button>(R.id.scaleYBtn).setOnClickListener(this)
        findViewById<Button>(R.id.translateXPosBtn).setOnClickListener(this)
        findViewById<Button>(R.id.translateYPosBtn).setOnClickListener(this)
        findViewById<Button>(R.id.resetBtn).setOnClickListener(this)
    }

    override fun onClick(v: View) {
        when (v.id) {
            // 处理旋转
            R.id.rotateXBtn -> {
                rotationX += ROTATION_DELTA
                glSurfaceView.updateRotation(rotationX, AXIS.X)
            }

            R.id.rotateYBtn -> {
                rotationY += ROTATION_DELTA
                glSurfaceView.updateRotation(rotationY, AXIS.Y)
            }

            R.id.rotateZBtn -> {
                rotationZ += ROTATION_DELTA
                glSurfaceView.updateRotation(rotationZ, AXIS.Z)
            }

            // 处理缩放
            R.id.scaleXBtn -> {
                scaleX += SCALE_DELTA
                glSurfaceView.updateScale(scaleX, AXIS.X)
            }

            R.id.scaleYBtn -> {
                scaleY += SCALE_DELTA
                glSurfaceView.updateScale(scaleY, AXIS.Y)
            }

            // 处理位移
            R.id.translateXPosBtn -> {
                translateX += TRANSLATE_DELTA
                glSurfaceView.updateTranslate(translateX, AXIS.X)
            }

            R.id.translateYPosBtn -> {
                translateY += TRANSLATE_DELTA
                glSurfaceView.updateTranslate(translateY, AXIS.Y)
            }

            // 重置所有变换
            R.id.resetBtn -> {
                resetTransformations()
            }
        }

        // 更新渲染
        glSurfaceView.requestRender()
    }

    private fun resetTransformations() {
        // 重置所有变换参数
        rotationX = 0f
        rotationY = 0f
        rotationZ = 0f
        scaleX = 1.0f
        scaleY = 1.0f
        translateX = 0f
        translateY = 0f
        glSurfaceView?.reset()

    }
}

enum class AXIS {
    X,
    Y,
    Z
}

GLSurfaceView代码

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

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

    fun updateRotation(angle: Float, axis: AXIS) {
        mRenderer?.mDrawData?.apply {
            resetMatrix()
            computeRotateModelMatrix(angle, axis)
            computeMVPMatrix(width, height)
            drawCurrentOutput()
        }
    }

    fun updateScale(scale: Float, axis: AXIS) {
        mRenderer?.mDrawData?.apply {
            resetMatrix()
            computeScaleModelMatrix(scale, axis)
            computeMVPMatrix(width, height)
            drawCurrentOutput()
        }
    }

    fun updateTranslate(translate: Float, axis: AXIS) {
        mRenderer?.mDrawData?.apply {
            resetMatrix()
            computeTranslateModelMatrix(translate, axis)
            computeMVPMatrix(width, height)
            drawCurrentOutput()
        }
    }

    fun reset() {
        mRenderer?.mDrawData?.apply {
            resetMatrix()
            computeMVPMatrix(width, height)
            drawCurrentOutput()
        }
    }
}

GLSurfaceView.Renderer代码

kotlin 复制代码
class MyMatrixRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
    var mDrawData: MatrixDrawData? = 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 = MatrixDrawData().apply {
            initTexture0(mContext, R.drawable.picture)
            initShader()
            initVertexBuffer()
        }
    }

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

    override fun onDrawFrame(gl: GL10?) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        mDrawData?.drawCurrentOutput()
    }
}

GLSurfaceView.Renderer需要的绘制数据

kotlin 复制代码
class MatrixDrawData {
    private var NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2
    private var mProgram: Int = -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
    var mTextureID = IntArray(1)

    // 有上下翻转的变换矩阵
    private var mMVPMatrix = FloatArray(16)

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

    // 观察矩阵
    private val mViewMatrix = FloatArray(16)

    // 模型矩阵
    private val mModelMatrix = FloatArray(16)

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

    // 准备顶点坐标,分配直接内存
    // 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;
         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.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 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 resetMatrix() {
        Matrix.setIdentityM(mModelMatrix, NO_OFFSET)
        Matrix.setIdentityM(mViewMatrix, NO_OFFSET)
        Matrix.setIdentityM(mProjectionMatrix, NO_OFFSET)
        Matrix.setIdentityM(mMVPMatrix, NO_OFFSET)
    }

        // 计算GLSurfaceView变换矩阵
    fun computeMVPMatrix(width: Int, height: Int) {
        val isLandscape = width > height
        mViewPortRatio = if (isLandscape) width.toFloat() / height else height.toFloat() / width

        // 计算包围图片的球半径
        val radius = sqrt(1f + mViewPortRatio * mViewPortRatio)
        val near = 0.1f
        val far = near + 2 * radius
        // 视图矩阵View Matrix
        Matrix.setLookAtM(
            mViewMatrix, NO_OFFSET,
            0f, 0f, near + radius,  // 相机位置
            0f, 0f, 0f,             // 看向原点
            0f, 1f, 0f              // 上方向
        )

        // 投影矩阵Projection Matrix
        Matrix.orthoM(
            mProjectionMatrix, NO_OFFSET,
            if (isLandscape) -mViewPortRatio else -1f,  // 左边界
            if (isLandscape) mViewPortRatio else 1f,    // 右边界
            if (isLandscape) -1f else -mViewPortRatio,  // 下边界
            if (isLandscape) 1f else mViewPortRatio,    // 上边界
            near, // 近平面
            far // 远平面
        )

        // 最终变换矩阵,第一次变换,模型矩阵 x 视图矩阵 = Model x View, 但是OpenGL ES矩阵乘法是右乘,所以是View x Model
        Matrix.multiplyMM(
            mMVPMatrix,
            NO_OFFSET,
            mViewMatrix,
            NO_OFFSET,
            mModelMatrix,
            NO_OFFSET
        )

        // 最终变换矩阵,第二次变换,模型矩阵 x 视图矩阵 x 投影矩阵 = Model x View x Projection, 但是OpenGL ES矩阵乘法是右乘,所以是Projection x View x Model
        Matrix.multiplyMM(
            mMVPMatrix,
            NO_OFFSET,
            mProjectionMatrix,
            NO_OFFSET,
            mMVPMatrix,
            NO_OFFSET
        )

        // 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
        // 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
        Matrix.scaleM(
            mMVPMatrix,
            NO_OFFSET,
            1f,
            -1f,
            1f,
        )
    }
    
    fun computeRotateModelMatrix(angleX: Float, axis: AXIS) {
        when (axis) {
            AXIS.X -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 1f, 0f, 0f)
            AXIS.Y -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 0f, 1f, 0f)
            AXIS.Z -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 0f, 0f, 1f)
        }
    }

    fun computeScaleModelMatrix(scale: Float, axis: AXIS) {
        when (axis) {
            AXIS.X -> Matrix.scaleM(mModelMatrix, NO_OFFSET, scale, 1f, 1f)
            AXIS.Y -> Matrix.scaleM(mModelMatrix, NO_OFFSET, 1f, scale, 1f)
            else -> {}
        }
    }

    fun computeTranslateModelMatrix(translate: Float, axis: AXIS) {
        when (axis) {
            AXIS.X -> Matrix.translateM(mModelMatrix, NO_OFFSET, translate, 0f, 0f)
            AXIS.Y -> Matrix.translateM(mModelMatrix, NO_OFFSET, 0f, translate, 0f)
            else -> {}
        }
    }

    // 加载纹理
    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)
        Log.e(
            "yang",
            "loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}"
        )
        return textureId[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")
        if (textureSampleHandle != -1) {
            GLES30.glUniform1i(textureSampleHandle, 0)
        }
    }

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

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


    // GLSurfaceView实时绘制
    fun drawCurrentOutput() {
        val state = saveGLState()
        try {
            GLES30.glUseProgram(mProgram)
            enableTexture0(mProgram, mTextureID[0])
            drawSomething(mProgram, mMVPMatrix)
            disableTexture0()
        } finally {
            restoreGLState(state)
        }
    }
    
    // 保存OpenGL状态
    private fun saveGLState(): GLState {
        val viewport = IntArray(4)
        val program = IntArray(1)
        val framebuffer = IntArray(1)
        GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, 0)
        GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, program, 0)
        GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, framebuffer, 0)
        return GLState(viewport, program[0], framebuffer[0])
    }

    // 恢复OpenGL状态
    private fun restoreGLState(state: GLState) {
        GLES30.glViewport(
            state.viewport[0],
            state.viewport[1],
            state.viewport[2],
            state.viewport[3]
        )
        GLES30.glUseProgram(state.program)
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, state.framebuffer)
    }

    // OpenGL状态数据类
    data class GLState(
        val viewport: IntArray,
        val program: Int,
        val framebuffer: Int
    )

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

效果图

相关推荐
ŧ榕树先生2 分钟前
稳定的Android studio版本安装教程
android·ide·android studio
啥都鼓捣的小yao5 分钟前
利用C++编写操作OpenCV常用操作
开发语言·c++·opencv
灼华十一7 分钟前
Golang系列 - 内存对齐
开发语言·后端·golang
程序媛学姐13 分钟前
SpringRabbitMQ消息模型:交换机类型与绑定关系
java·开发语言·spring
努力努力再努力wz20 分钟前
【c++深入系列】:类与对象详解(中)
java·c语言·开发语言·c++·redis
Johnny_Cheung28 分钟前
字符串、列表、元组、字典
开发语言·python
早上好啊! 树哥32 分钟前
常见的文件加密方式之【异或加密】,代入原理看例子,帮助更好的理解。
android·java·junit
东方雴翾44 分钟前
Scala语言的分治算法
开发语言·后端·golang
李慕瑶1 小时前
Scala语言的移动UI设计
开发语言·后端·golang