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">
<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"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
android:background="#33000000"
android:contentDescription="FBO截图预览" />
<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="16dp"
android:text="捕获FBO图像"
android:padding="12dp" />
</RelativeLayout>
Activity
代码
class MainActivity : AppCompatActivity() {
private lateinit var glSurfaceView: MyGLSurfaceView
private lateinit var imageView: ImageView
private lateinit var captureButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
glSurfaceView = findViewById(R.id.gl_surface_view)
imageView = findViewById(R.id.image_view)
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 -> {
getFrameBufferBitmap()?.let {
imageView.setImageBitmap(it)
}
}
MotionEvent.ACTION_UP -> {
getFrameBufferBitmap()?.let {
imageView.setImageBitmap(null)
}
}
else -> {}
}
return true
}
})
}
private fun getFrameBufferBitmap() : Bitmap? {
return glSurfaceView?.getFrameBufferBitmap()
}
}
自定义GLSurfaceView
代码
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
private var mRenderer = MyGLRenderer(context)
init {
setEGLContextClientVersion(3)
setRenderer(mRenderer)
renderMode = RENDERMODE_WHEN_DIRTY
}
fun getFrameBufferBitmap(): Bitmap? {
return mRenderer?.getFrameBufferBitmap()
}
}
自定义GLSurfaceView.Renderer
代码
class MyGLRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
private var mDrawData: DrawData? = null
private var mWidth = 0
private var mHeight = 0
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
mDrawData = DrawData().apply {
initShader()
initVertexBuffer()
initTexture0(mContext, R.drawable.pic)
initTexture1(mContext, R.drawable.bitmap_shader)
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
mWidth = width
mHeight = height
mDrawData?.computeMVPMatrix(width, height)
mDrawData?.initFrameBuffer(width, height)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
mDrawData?.enableTexture()
mDrawData?.drawOffScreen()
mDrawData?.disableTexture()
}
fun getFrameBufferBitmap(): Bitmap? {
return mDrawData?.getScreenBitmap()
}
}
GLSurfaceView.Renderer
需要的绘制数据
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
private var mFBO = IntArray(1)
private var mVAO = IntArray(1)
private var mVBO = IntArray(2)
private var mIBO = IntArray(1)
private var mTextureID = IntArray(2)
private var mFBOTextureID = IntArray(1)
private val mMVPMatrix = FloatArray(16)
private val mProjectionMatrix = FloatArray(16)
private val mViewMatrix = FloatArray(16)
private var mViewPortRatio = 1f
private var mFrameBufferBitmap: Bitmap? = null
private var mFrameBufferWidth = 0
private var mFrameBufferHeight = 0
private val mFrameBufferMVPMatrix = FloatArray(16)
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)
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)
}
fun initVertexBuffer() {
GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
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)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
textureCoords.size * 4,
textureBuffer,
GLES30.GL_STATIC_DRAW
)
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)
GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
index.size * 2,
indexBuffer,
GLES30.GL_STATIC_DRAW
)
GLES30.glBindVertexArray(0)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
}
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
)
}
Matrix.setLookAtM(
mViewMatrix,
NO_OFFSET,
0f,
0f,
1f,
0f,
0f,
0f,
0f,
1f,
0f
)
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mProjectionMatrix,
NO_OFFSET,
mViewMatrix,
NO_OFFSET
)
Matrix.scaleM(
mMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
fun initFrameBuffer(){
GLES30.glGenFramebuffers(mFBO.size, mFBO, NO_OFFSET)
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
)
GLES30.glFramebufferTexture2D(
GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D,
mFBOTextureID[0],
0
)
if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) {
Log.e("yang", "initFrameBuffer: FBO初始化失败")
}
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
}
fun drawOffScreen() {
takeIf { mFrameBufferBitmap == null }?.let {
val viewport = IntArray(4)
GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, NO_OFFSET)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])
GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)
computeFrameBufferMVPMatrix()
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
drawFrameBuffer()
getBitmapFromFrameBuffer()
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)
GLES30.glBindVertexArray(mVAO[0])
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
GLES30.glBindVertexArray(0)
}
fun drawFrameBuffer(){
val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mFrameBufferMVPMatrix, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
GLES30.glBindVertexArray(0)
}
fun enableTexture() {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])
val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture_0")
GLES30.glUniform1i(textureSampleHandle, 0)
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
)
val bitmap = Bitmap.createBitmap(mFrameBufferWidth, mFrameBufferHeight, Bitmap.Config.ARGB_8888)
pixelBuffer.rewind()
bitmap.copyPixelsFromBuffer(pixelBuffer)
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
)
}
Matrix.setLookAtM(
mViewMatrix,
NO_OFFSET,
0f,
0f,
1f,
0f,
0f,
0f,
0f,
1f,
0f
)
Matrix.multiplyMM(
mFrameBufferMVPMatrix,
NO_OFFSET,
mProjectionMatrix,
NO_OFFSET,
mViewMatrix,
NO_OFFSET
)
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
}
}
}
效果图
