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
}
}
}
效果图
