OpenGL ES -> SurfaceView + EGL实现立方体纹理贴图+透视效果

XML文件

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

Activity代码

kotlin 复制代码
class MainActivity7 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main7)
    }
}

// 立方体信息数据类
data class CubeInfo(
    var x: Float,       // x位置
    var y: Float,       // y位置
    var angle: Float,   // 当前旋转角度
    var rotationSpeed: Float,  // 旋转速度
    var scale : Float   // 缩放
)

// 添加五个立方体的数组
private val cubes = arrayOf(
    CubeInfo(x = -1.0f, y = 1.0f, angle = 0f, rotationSpeed = 0.3f, scale = 0.3f),
    CubeInfo(x = 1.0f, y = 1.0f, angle = 45f, rotationSpeed = 0.5f, scale = 0.4f),
    CubeInfo(x = 0f, y = 0f, angle = 90f, rotationSpeed = 0.7f, scale = 0.2f),
    CubeInfo(x = -1.0f, y = -1.0f, angle = 135f, rotationSpeed = 0.4f, scale = 0.5f),
    CubeInfo(x = 1.0f, y = -1.0f, angle = 180f, rotationSpeed = 0.2f, scale = 0.7f)
)

SurfaceView代码+渲染线程代码

kotlin 复制代码
class MySurfaceView(context: Context, attrs: AttributeSet) : SurfaceView(context, attrs),
    SurfaceHolder.Callback {
    init {
        holder.addCallback(this)
    }

    private var mEGLThread: MyEGLThread? = null
    private var mEGLHelper = MyEGLHelper()
    private var mEGLRender = MyEGLRender(context)

    override fun surfaceCreated(holder: SurfaceHolder) {
        // 创建并启动渲染线程
        mEGLThread = MyEGLThread(holder.surface).apply {
            start()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        mEGLThread?.changeSize(width, height)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        mEGLThread?.release()
    }

    inner class MyEGLThread(private val mSurface: Surface) : Thread() {
        private var mWidth = 0
        private var mHeight = 0

        @Volatile
        private var mRunning = true

        @Volatile
        private var mSizeChanged = false
        override fun run() {
            super.run()
            try {
                mEGLHelper.initEGL(mSurface)
                mEGLRender.onSurfaceCreated()
                while (mRunning) {
                    // 宽高变化,回调渲染器的onSurfaceChanged方法
                    if (mSizeChanged) {
                        mEGLRender.onSurfaceChanged(mWidth, mHeight)
                        mSizeChanged = false
                    }
                    // 渲染一帧, 回调渲染器的onDrawFrame方法
                    mEGLRender.onDrawFrame()
                    mEGLHelper.swapBuffer()
                }
            } catch (e: Exception) {
                Log.e("yang", "EGL thread error ${e.message}")
            }
        }

        fun changeSize(width: Int, height: Int) {
            mWidth = width
            mHeight = height
            mSizeChanged = true
        }

        fun release() {
            mRunning = false
            mEGLRender.onSurfaceDestroyed()
            mEGLHelper.releaseEGL()
            interrupt()
        }
    }
}

EGL工具类代码

kotlin 复制代码
class MyEGLHelper {
    private lateinit var mEGL: EGL10
    private lateinit var mEGLDisplay: EGLDisplay
    private lateinit var mEGLContext: EGLContext
    private lateinit var mEGLSurface: EGLSurface

    // 初始化EGL
    fun initEGL(surface: Surface) {
        if (::mEGL.isInitialized &&
            ::mEGLDisplay.isInitialized &&
            ::mEGLContext.isInitialized &&
            ::mEGLSurface.isInitialized) {
            Log.e("yang", "EGL already initialized")
            return
        }
        // 1. 获取EGL实例
        mEGL = EGLContext.getEGL() as EGL10

        // 2. 获取默认的显示设备(就是窗口)
        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
        takeIf { mEGLDisplay == EGL10.EGL_NO_DISPLAY }?.apply {
            throw RuntimeException("eglGetDisplay failed")
        }

        // 3. 初始化默认显示设备
        val version = IntArray(2)
        takeIf { !mEGL.eglInitialize(mEGLDisplay, version) }?.apply {
            throw RuntimeException("eglInitialize failed")
        }

        // 4. 设置显示设备的属性
        val display_attribute_list = intArrayOf(
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 8,
            EGL_STENCIL_SIZE, 4,
            EGL_NONE
        )

        // 5. 查找配置并进行 attribute_list 的匹配, 匹配成功返回一个数组
        val num_config = IntArray(1)
        takeIf {
            !mEGL.eglChooseConfig(
                mEGLDisplay,
                display_attribute_list,
                null,
                0,
                num_config
            )
        }?.apply {
            throw RuntimeException("eglChooseConfig failed")
        }
        // 匹配是否成功
        takeIf { num_config[0] <= 0 }?.apply {
            throw RuntimeException("eglChooseConfig#1 failed")
        }
        val eglConfigs = arrayOfNulls<EGLConfig>(num_config[0])

        takeIf {
            !mEGL.eglChooseConfig(
                mEGLDisplay,
                display_attribute_list,
                eglConfigs,
                num_config[0],
                num_config
            )
        }?.apply {
            throw RuntimeException("eglChooseConfig#2 failed")
        }

        // 6. 创建EGLContext
        val context_display_list = intArrayOf(
            EGL_CONTEXT_CLIENT_VERSION, 3,
            EGL_NONE
        )
        takeIf { ::mEGLContext.isInitialized == false }?.apply {
            mEGLContext = mEGL.eglCreateContext(
                mEGLDisplay,
                eglConfigs[0],
                EGL10.EGL_NO_CONTEXT,
                context_display_list
            )
        }
        takeIf { mEGLContext == EGL10.EGL_NO_CONTEXT }?.apply {
            throw RuntimeException("eglCreateContext failed")
        }

        // 7. 创建EGLSurface
        takeIf { ::mEGLSurface.isInitialized == false }?.apply {
            mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, eglConfigs[0], surface, null)
        }
        takeIf { mEGLSurface == EGL10.EGL_NO_SURFACE }?.apply {
            throw RuntimeException("eglCreateWindowSurface failed")
        }

        // 8. 绑定EGLContext和EGLSurface
        takeIf { !mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext) }?.apply {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    // 释放EGL
    fun releaseEGL() {
        takeIf { ::mEGL.isInitialized }?.apply {
            // 解绑EGLContext和EGLSurface
            mEGL.eglMakeCurrent(
                mEGLDisplay,
                EGL10.EGL_NO_SURFACE,
                EGL10.EGL_NO_SURFACE,
                EGL10.EGL_NO_CONTEXT
            )
            // 释放EGLSurface
            mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)
            // 释放EGLContext
            mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)
            // 释放EGLDisplay
            mEGL.eglTerminate(mEGLDisplay)
        }
    }

    // 交换渲染数据
    fun swapBuffer() {
        takeIf { ::mEGL.isInitialized && ::mEGLDisplay.isInitialized }?.apply {
            takeIf { !mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface) }?.apply {
                throw RuntimeException("eglSwapBuffers failed")
            }
        }
    }
}

渲染器接口

kotlin 复制代码
interface EGLRender {
    fun onSurfaceCreated()
    fun onSurfaceChanged(width: Int, height: Int)
    fun onDrawFrame()
    fun onSurfaceDestroyed()
}

渲染器实现类

kotlin 复制代码
class MyEGLRender(private val mContext: Context) : EGLRender {
    var mDrawData: MyDrawData2? = null

    override fun onSurfaceCreated() {
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
        mDrawData = MyDrawData2().apply {
            initTexture0(mContext, R.drawable.picture)
            initShaderProgram()
            initVertexBuffer()
        }
    }

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

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

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

渲染器实现类需要的绘制数据

kotlin 复制代码
class MyDrawData2 {
    private var mProgram: Int = -1
    private var NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2
    private val STRIDE = (VERTEX_POS_DATA_SIZE + TEXTURE_POS_DATA_SIZE) * 4 // 每个顶点的总字节数

    // VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
    private var mVAO = IntArray(1)

    // VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
    private var mVBO = IntArray(1) // 只需要一个VBO

    // 纹理ID
    private 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

    // Surface宽高
    private var mSurfaceWidth = 0
    private var mSurfaceHeight = 0

    // 顶点和纹理坐标合并在一个数组中
    // 格式:x, y, z, u, v (顶点坐标后跟纹理坐标)
    val vertexData = floatArrayOf(
        // 顶点坐标            // 纹理坐标
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    )

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

    // 初始化着色器程序
    fun initShaderProgram() {
        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 - 只需要一个VBO存储所有数据
        GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertexData.size * 4,
            vertexDataBuffer,
            GLES30.GL_STATIC_DRAW
        )

        // 设置顶点属性指针 - 顶点坐标
        val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
        GLES30.glEnableVertexAttribArray(positionHandle)
        GLES30.glVertexAttribPointer(
            positionHandle,
            VERTEX_POS_DATA_SIZE,
            GLES30.GL_FLOAT,
            false,
            STRIDE,     // 步长,每个顶点5个float (x,y,z,u,v)
            NO_OFFSET   // 偏移量,位置数据在前
        )

        // 设置顶点属性指针 - 纹理坐标
        val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
        GLES30.glEnableVertexAttribArray(textureHandle)
        GLES30.glVertexAttribPointer(
            textureHandle,
            TEXTURE_POS_DATA_SIZE,
            GLES30.GL_FLOAT,
            false,
            STRIDE,                          // 步长,每个顶点5个float (x,y,z,u,v)
            VERTEX_POS_DATA_SIZE * 4         // 偏移量,纹理数据在位置数据之后
        )

        // 解绑VAO
        GLES30.glBindVertexArray(0)
        // 解绑VBO
        GLES30.glBindBuffer(GLES30.GL_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.glDrawArrays(GLES30.GL_TRIANGLES, NO_OFFSET, vertexData.size / (VERTEX_POS_DATA_SIZE + TEXTURE_POS_DATA_SIZE))
        // 解绑VAO
        GLES30.glBindVertexArray(0)
    }

    fun setSurfaceSize(width: Int, height: Int){
        mSurfaceWidth = width
        mSurfaceHeight = height
    }

    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, cube: CubeInfo) {
        mSurfaceWidth = width
        mSurfaceHeight = height

        // 更新立方体的旋转角度
        cube.angle += cube.rotationSpeed
        cube.angle %= 360
        Matrix.scaleM(mModelMatrix, NO_OFFSET, cube.scale, cube.scale, cube.scale)

        Matrix.translateM(mModelMatrix, NO_OFFSET, cube.x, cube.y, 0f)

        Matrix.rotateM(mModelMatrix, NO_OFFSET, cube.angle, 0.5f, 0.5f, 0f)

        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
        val distance = near / (near + radius)
        // 视图矩阵View Matrix
        Matrix.setLookAtM(
            mViewMatrix, NO_OFFSET,
            0f, 0f, near + radius,  // 相机位置
            0f, 0f, 0f,             // 看向原点
            0f, 1f, 0f              // 上方向
        )

        // 投影矩阵Projection Matrix
        Matrix.frustumM(
            mProjectionMatrix, NO_OFFSET,
            if (isLandscape) (-mViewPortRatio * distance) else (-1f * distance),  // 左边界
            if (isLandscape) (mViewPortRatio * distance) else (1f * distance),    // 右边界
            if (isLandscape) (-1f * distance) else (-mViewPortRatio  * distance),  // 下边界
            if (isLandscape) (1f * distance) else (mViewPortRatio * distance),    // 上边界
            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 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])
            // 为每个立方体计算MVP矩阵并绘制
            for (cube in cubes) {
                resetMatrix()
                computeMVPMatrix(mSurfaceWidth, mSurfaceHeight, cube)
                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)
    }


    fun release(){
        // 删除VAO
        if (mVAO[0] != 0) {
            GLES30.glDeleteVertexArrays(1, mVAO, 0)
            mVAO[0] = 0
        }

        // 删除VBO
        if (mVBO[0] != 0) {
            GLES30.glDeleteBuffers(1, mVBO, 0)
            mVBO[0] = 0
        }

        // 删除纹理
        if (mTextureID[0] != 0) {
            GLES30.glDeleteTextures(1, mTextureID, 0)
            mTextureID[0] = 0
        }


        // 删除着色器程序
        if (mProgram != -1) {
            GLES30.glDeleteProgram(mProgram)
            mProgram = -1
        }

        // 清空缓冲区
        vertexDataBuffer.clear()
    }

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

效果图

相关推荐
用户2018792831671 分钟前
AMS和app通信的小秘密
android
用户2018792831673 分钟前
ThreadPoolExecutor之市场雇工的故事
android
诺诺Okami6 分钟前
Android Framework-Launcher-InvariantDeviceProfile
android
Antonio9151 小时前
【音视频】Android NDK 与.so库适配
android·音视频
sun00770010 小时前
android ndk编译valgrind
android
AI视觉网奇11 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空11 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet12 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin12 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo0305198714 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin