kotlin
复制代码
class DrawData {
private var mProgram: Int = -1
private var NO_OFFSET = 0
private val VERTEX_POS_DATA_SIZE = 3
private val TEXTURE_POS_DATA_SIZE = 2
// FBO(Frame Buffer Object), 帧缓冲对象,用于存储渲染后的图像
private var mFBO = IntArray(1)
// VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
private var mVAO = IntArray(1)
// VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
private var mVBO = IntArray(2)
// IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据
private var mIBO = IntArray(1)
// 纹理ID
private var mTextureID = IntArray(2)
// FBO中的纹理ID
private var mFBOTextureID = IntArray(1)
// 最终变化矩阵
private val mMVPMatrix = FloatArray(16)
// 投影矩阵
private val mProjectionMatrix = FloatArray(16)
// 相机矩阵
private val mViewMatrix = FloatArray(16)
// 视口比例
private var mViewPortRatio = 1f
// 帧缓冲中的Bitmap
private var mFrameBufferBitmap: Bitmap? = null
// 帧缓冲宽高
private var mFrameBufferWidth = 0
private var mFrameBufferHeight = 0
// 帧缓冲最终变换矩阵
private val mFrameBufferMVPMatrix = FloatArray(16)
// 准备顶点坐标,分配直接内存
// OpenGL ES坐标系:原点在中心,X轴向右为正,Y轴向上为正,Z轴向外为正
val vertex = floatArrayOf(
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, // 右上
1.0f, -1.0f, 0.0f, // 右下
)
val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertex)
.position(NO_OFFSET)
// 准备纹理坐标,分配直接内存
// 纹理坐标系:原点在左下角,X轴向右为正,Y轴向上为正
val textureCoords = floatArrayOf(
0.0f, 1.0f, // 左上
0.0f, 0.0f, // 左下
1.0f, 1.0f, // 右上
1.0f, 0.0f, // 右下
)
val textureBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureCoords)
.position(NO_OFFSET)
// 索引坐标,分配直接内存
val index = shortArrayOf(
0, 1, 2, // 第一个三角形
1, 3, 2, // 第二个三角形
)
val indexBuffer = ByteBuffer.allocateDirect(index.size * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(index)
.position(NO_OFFSET)
// 初始化着色器程序
fun initShader() {
val vertexShaderCode = """#version 300 es
uniform mat4 uMVPMatrix; // 变换矩阵
in vec4 aPosition; // 顶点坐标
in vec2 aTexCoord; // 纹理坐标
out vec2 vTexCoord;
void main() {
// 输出顶点坐标和纹理坐标到片段着色器
gl_Position = uMVPMatrix * aPosition;
vTexCoord = aTexCoord;
}""".trimIndent()
val fragmentShaderCode = """#version 300 es
precision mediump float;
uniform sampler2D uTexture_0;
uniform sampler2D uTexture_1;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture_0, vTexCoord) + texture(uTexture_1, vTexCoord);
}""".trimIndent()
// 加载顶点着色器和片段着色器, 并创建着色器程序
val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
mProgram = GLES30.glCreateProgram()
GLES30.glAttachShader(mProgram, vertexShader)
GLES30.glAttachShader(mProgram, fragmentShader)
GLES30.glLinkProgram(mProgram)
GLES30.glUseProgram(mProgram)
// 删除着色器对象
GLES30.glDeleteShader(vertexShader)
GLES30.glDeleteShader(fragmentShader)
}
// 创建VAO, VBO, IBO
fun initVertexBuffer() {
// 绑定VAO
GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
// 绑定VBO
GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
// 绑定顶点缓冲区数据到VBO[0]
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
// 解析顶点缓冲区数据到VBO[0]
val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(
positionHandle,
VERTEX_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 解绑顶点缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
// 绑定纹理缓冲区数据到VBO[1]
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
textureCoords.size * 4,
textureBuffer,
GLES30.GL_STATIC_DRAW
)
// 解析纹理缓冲区数据到VBO[1]
val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
GLES30.glEnableVertexAttribArray(textureHandle)
GLES30.glVertexAttribPointer(
textureHandle,
TEXTURE_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 解绑纹理缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
// 绑定IBO
GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
// 绑定索引缓冲区数据到IBO[0]
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
index.size * 2,
indexBuffer,
GLES30.GL_STATIC_DRAW
)
// 解绑VAO
GLES30.glBindVertexArray(0)
// 解绑IBO
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
}
// 计算GLSurfaceView变换矩阵
fun computeMVPMatrix(width: Int, height: Int) {
// 正交投影矩阵
takeIf { width > height }?.let {
mViewPortRatio = (width * 1f) / height
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-mViewPortRatio, // 近平面的坐标系左边界
mViewPortRatio, // 近平面的坐标系右边界
-1f, // 近平面的坐标系的下边界
1f, // 近平面坐标系的上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
} ?: run {
mViewPortRatio = (height * 1f) / width
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-1f, // 近平面坐标系左边界
1f, // 近平面坐标系右边界
-mViewPortRatio, // 近平面坐标系下边界
mViewPortRatio, // 近平面坐标系上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
}
// 设置相机矩阵
// 相机位置(0f, 0f, 1f)
// 物体位置(0f, 0f, 0f)
// 相机方向(0f, 1f, 0f)
Matrix.setLookAtM(
mViewMatrix, // 相机矩阵
NO_OFFSET, // 偏移量
0f, // 相机位置x
0f, // 相机位置y
1f, // 相机位置z
0f, // 物体位置x
0f, // 物体位置y
0f, // 物体位置z
0f, // 相机上方向x
1f, // 相机上方向y
0f // 相机上方向z
)
// 最终变化矩阵
Matrix.multiplyMM(
mMVPMatrix, // 最终变化矩阵
NO_OFFSET, // 偏移量
mProjectionMatrix, // 投影矩阵
NO_OFFSET, // 投影矩阵偏移量
mViewMatrix, // 相机矩阵
NO_OFFSET // 相机矩阵偏移量
)
// 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
// 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
Matrix.scaleM(
mMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
// 初始化帧缓冲
fun initFrameBuffer(){
// 创建FBO
GLES30.glGenFramebuffers(mFBO.size, mFBO, NO_OFFSET)
// 绑定FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
// 创建空纹理
GLES30.glGenTextures(mFBOTextureID.size, mFBOTextureID, NO_OFFSET)
// 绑定空纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFBOTextureID[0])
// 设置纹理参数
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER,
GLES30.GL_LINEAR
) // 纹理缩小时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER,
GLES30.GL_LINEAR
) // 纹理放大时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexImage2D(
GLES30.GL_TEXTURE_2D, // 纹理类型
NO_OFFSET, // 偏移量
GLES30.GL_RGBA, // 颜色通道
mFrameBufferWidth, // 纹理宽度
mFrameBufferHeight, // 纹理高度
NO_OFFSET, // 偏移量
GLES30.GL_RGBA, // 颜色通道
GLES30.GL_UNSIGNED_BYTE, // 颜色数据类型
null // 不传入颜色数据
)
// 绑定空纹理到FBO,用于绘制到FBO
GLES30.glFramebufferTexture2D(
GLES30.GL_FRAMEBUFFER, // FBO
GLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区
GLES30.GL_TEXTURE_2D, // 纹理类型
mFBOTextureID[0], // 纹理ID
0
)
if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) {
Log.e("yang", "initFrameBuffer: FBO初始化失败")
}
// 解绑FBO纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
// 解绑FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
}
// 离屏渲染
fun drawOffScreen() {
takeIf { mFrameBufferBitmap == null }?.let {
// 帧缓冲绘制纹理需要修改视口大小,这里需要进行视口的保存和恢复
val viewport = IntArray(4)
GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, NO_OFFSET)
// 绑定FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
// 设置视口为FBO尺寸
GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
// 计算帧缓冲纹理宽高的变换矩阵
computeFrameBufferMVPMatrix()
// 清除FBO
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
// 绘制图形到FBO, FBO上的内容不会显示
drawFrameBuffer()
// 缓存FBO内容到Bitmap
getBitmapFromFrameBuffer()
// 解绑FBO,恢复默认帧缓冲
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
// 恢复原始视口
GLES30.glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
}
}
// 使用着色器程序绘制图形
fun drawSomething() {
// 解析变换矩阵
val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)
// 绑定VAO
GLES30.glBindVertexArray(mVAO[0])
// 绘制图形
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
// 解绑VAO
GLES30.glBindVertexArray(0)
}
fun drawFrameBuffer(){
// 解析帧缓冲变换矩阵
val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mFrameBufferMVPMatrix, NO_OFFSET)
// 绑定VAO
GLES30.glBindVertexArray(mVAO[0])
// 绘制图形
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
// 解绑VAO
GLES30.glBindVertexArray(0)
}
fun enableTexture() {
// 激活纹理编号0
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])
val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture_0")
GLES30.glUniform1i(textureSampleHandle, 0)
// 激活纹理编号1
GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[1])
val textureSampleHandle1 = GLES30.glGetUniformLocation(mProgram, "uTexture_1")
GLES30.glUniform1i(textureSampleHandle1, 1)
}
fun disableTexture() {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
}
fun initTexture0(context: Context, resourceId: Int){
mTextureID[0] = loadTexture(context, resourceId)
}
fun initTexture1(context: Context, resourceId: Int){
mTextureID[1] = loadTexture(context, resourceId)
}
// 加载纹理
fun loadTexture(context: Context, resourceId: Int): Int {
val textureId = IntArray(1)
// 生成纹理
GLES30.glGenTextures(1, textureId, 0)
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
// 设置纹理参数
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER,
GLES30.GL_LINEAR
) // 纹理缩小时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER,
GLES30.GL_LINEAR
) // 纹理放大时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
// 加载图片
val options = BitmapFactory.Options().apply {
inScaled = false // 不进行缩放
}
val bitmap = BitmapFactory.decodeResource(context.resources, resourceId, options)
// 将图片数据加载到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
// 释放资源
bitmap.recycle()
// 解绑纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
mFrameBufferWidth = max(mFrameBufferWidth, bitmap.width)
mFrameBufferHeight = max(mFrameBufferHeight, bitmap.height)
Log.e("yang", "loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}")
return textureId[0]
}
fun getBitmapFromFrameBuffer(){
// 分配缓冲区来存储像素数据
val pixelBuffer = ByteBuffer.allocateDirect(mFrameBufferWidth * mFrameBufferHeight * 4)
.order(ByteOrder.LITTLE_ENDIAN)
// 读取像素数据
GLES30.glReadPixels(
0, 0, mFrameBufferWidth, mFrameBufferHeight,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
pixelBuffer
)
// 将ByteBuffer转换为Bitmap
val bitmap = Bitmap.createBitmap(mFrameBufferWidth, mFrameBufferHeight, Bitmap.Config.ARGB_8888)
pixelBuffer.rewind()
bitmap.copyPixelsFromBuffer(pixelBuffer)
// OpenGL和Android的Y轴方向相反,需要围绕中心垂直翻转
val matrix = android.graphics.Matrix().apply {
setScale(1f, -1f, mFrameBufferWidth / 2f, mFrameBufferHeight / 2f)
}
mFrameBufferBitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height,
matrix, true
)
bitmap.recycle()
}
fun getScreenBitmap() : Bitmap?{
return mFrameBufferBitmap
}
fun computeFrameBufferMVPMatrix() {
// 正交投影矩阵
takeIf { mFrameBufferWidth > mFrameBufferHeight }?.let {
mViewPortRatio = (mFrameBufferWidth * 1f) / mFrameBufferHeight
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-mViewPortRatio, // 近平面的坐标系左边界
mViewPortRatio, // 近平面的坐标系右边界
-1f, // 近平面的坐标系的下边界
1f, // 近平面坐标系的上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
} ?: run {
mViewPortRatio = (mFrameBufferHeight * 1f) / mFrameBufferWidth
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-1f, // 近平面坐标系左边界
1f, // 近平面坐标系右边界
-mViewPortRatio, // 近平面坐标系下边界
mViewPortRatio, // 近平面坐标系上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
}
// 设置相机矩阵
// 相机位置(0f, 0f, 1f)
// 物体位置(0f, 0f, 0f)
// 相机方向(0f, 1f, 0f)
Matrix.setLookAtM(
mViewMatrix, // 相机矩阵
NO_OFFSET, // 偏移量
0f, // 相机位置x
0f, // 相机位置y
1f, // 相机位置z
0f, // 物体位置x
0f, // 物体位置y
0f, // 物体位置z
0f, // 相机上方向x
1f, // 相机上方向y
0f // 相机上方向z
)
// 最终变化矩阵
Matrix.multiplyMM(
mFrameBufferMVPMatrix, // 最终变化矩阵
NO_OFFSET, // 偏移量
mProjectionMatrix, // 投影矩阵
NO_OFFSET, // 投影矩阵偏移量
mViewMatrix, // 相机矩阵
NO_OFFSET // 相机矩阵偏移量
)
// 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
// 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
Matrix.scaleM(
mFrameBufferMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
object LoadShaderUtil {
// 创建着色器对象
fun loadShader(type: Int, source: String): Int {
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
return shader
}
}
}