Android Opengl(九)FBO帧缓冲示例
FBO介绍
用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer)。
-
OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。
-
默认情况下,OpenGL渲染的目标是屏幕,但如果你不想直接渲染到屏幕上,还需要对渲染结果做某些后期处理、渲染到纹理、阴影映射等操作,便可以使用帧缓冲对象,实现离屏渲染。
-
通过使用帧缓冲对象,可以实现离屏渲染、多重渲染目标(MRT)等高级渲染技术,而不必直接渲染到屏幕。
-
FBO本身不能用于渲染,只有添加了纹理或者渲染缓冲区之后,才能作为渲染目标。
FBO使用流程
- 生成FBO,glGenFramebuffers()
- 绑定FBO,glBindFramebuffer()
- 生成空的颜色纹理,
GLES30.glGenTextures(1, colorTexureId, 0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, colorTexureId[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)
//生成2D纹理
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA,width, height,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null)
- FBO与空纹理绑定
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D,
frameBufferTextureIds[0],
0)
- FBO解绑
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
FBO实战示例
示例渲染过程:原图进行lut滤镜灰度后再进行转场,最终渲染到屏幕
Activity代码
class SimpleFBOMultiHandleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_common_sample)
Util.context = this
val glSurfaceView = findViewById<GLSurfaceView>(R.id.glsurfaceview)
glSurfaceView.setEGLContextClientVersion(3)
glSurfaceView.setRenderer(SamplerFBOMultiRenderer(BitmapFactory.decodeResource(resources, R.drawable.flower)))
glSurfaceView?.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.opengl.GLSurfaceView
android:id="@+id/glsurfaceview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
核心操作
SamplerFBOMultiRenderer
import android.graphics.Bitmap
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class SamplerFBOMultiRenderer(val bitmap:Bitmap) : GLSurfaceView.Renderer {
private val imageRender by lazy { GLImageRender(bitmap) }
private val imageFilterRender by lazy { GLImageFilterRender() }
private val imageTransitionRender by lazy { GLImageTransitionRender() }
private val fboRender by lazy { GLFBOMultiRender() }
private val fboRender2 by lazy { GLFBOMultiRender() }
private val fboRender3 by lazy { GLFBOMultiRender() }
override fun onDrawFrame(gl: GL10?) {
// 绑定第一个FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboRender.getFrameBufferId())
// 绘制图片进行绘制
imageRender.draw()
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE)
// 绑定第二个FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboRender2.getFrameBufferId())
// 将第一个FBO的绘制结果,作为滤镜渲染的输入纹理
imageFilterRender.updateTexure(fboRender.getTexureId())
imageFilterRender.draw() // 进行滤镜处理
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE)
// 绑定第三个FBO
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboRender3.getFrameBufferId())
// 将第2个FBO的绘制结果(灰度过的),作为转场渲染的输入纹理
imageTransitionRender.updateTexure(fboRender2.getTexureId())
imageTransitionRender.draw()
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE)
// 上屏绘制
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 6)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
imageRender.onSurfaceChanged(gl,width,height)
imageFilterRender.onSurfaceChanged(gl,width,height)
fboRender.onSurfaceChanged(gl,width,height)
fboRender2.onSurfaceChanged(gl,width,height)
fboRender3.onSurfaceChanged(gl,width,height)
imageTransitionRender.onSurfaceChanged(gl,width,height)
val imgRatio = bitmap.width.toFloat().div(bitmap.height)
val windowRatio = width.toFloat().div(height)
GLES30.glViewport(0, 0, width, height)
// 根据比例调整大小
if (imgRatio > windowRatio) {
Matrix.orthoM(imageTransitionRender.getTranslateMatrix(), 0, -1f, 1f, -imgRatio * 2f, imgRatio * 2f, 0f, 10f);
} else {
Matrix.orthoM(imageTransitionRender.getTranslateMatrix(), 0, -(1 / imgRatio) * 2f, (1 / imgRatio) * 2f, -1f, 1f, 0f, 10f);
}
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
imageRender.onSurfaceCreated(gl,config)
imageFilterRender.onSurfaceCreated(gl,config)
fboRender.onSurfaceCreated(gl,config)
imageTransitionRender.onSurfaceCreated(gl,config)
fboRender2.onSurfaceCreated(gl,config)
fboRender3.onSurfaceCreated(gl,config)
}
}
GLImageRender
import android.graphics.Bitmap
import android.opengl.GLES30
import android.opengl.Matrix
import com.df.openglstudydemo.util.ShaderUtil
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class GLImageRender(val bitmap: Bitmap) {/*
* 顶点位置程序
*/
private val vertexShaderCode =
"uniform mat4 uTMatrix;" + "attribute vec4 aPosition;" + "attribute vec2 aTexCoord;" + "varying vec2 vTexCoord;" + "void main() { " + " gl_Position = uTMatrix * aPosition;" + " vTexCoord = aTexCoord;" + "}"
private val fragmentShaderCode0_2 =
"precision mediump float;" + "uniform sampler2D uSampler;" + "varying vec2 vTexCoord;" + "void main() { " + " gl_FragColor = texture2D(uSampler,vTexCoord);" + "}"
private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private lateinit var vertexDataBuffer: FloatBuffer
private val textureCoordinateData0 = floatArrayOf(0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
private lateinit var textureCoordinateDataBuffer0: FloatBuffer
private var programId0 = 0
private var imageTexture = 0
private var glSurfaceViewWidth = 0
private var glSurfaceViewHeight = 0
private var samplerHandle: Int = 0
private var uMatrixHandle: Int = 0
private val translateMatrix = FloatArray(16)
fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化坐标、图片数据
// Init the coordinates and image texture
initData()
// 创建2个GL Program,第一个将图片的蓝色通道全部设为0.5,第二做水平方向模糊
// Create two GL programs, and one is used for set the blue channel to 0.5, while the other is used for horizontal blur
programId0 = ShaderUtil.createGlProgram(vertexShaderCode, fragmentShaderCode0_2)
samplerHandle = GLES30.glGetUniformLocation(programId0, "uSampler")
uMatrixHandle = GLES30.glGetUniformLocation(programId0, "uTMatrix")
Matrix.setIdentityM(translateMatrix, 0)
}
private fun initData() {
vertexDataBuffer =
ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8).order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
textureCoordinateDataBuffer0 = ByteBuffer.allocateDirect(textureCoordinateData0.size * java.lang.Float.SIZE / 8)
.order(ByteOrder.nativeOrder()).asFloatBuffer()
textureCoordinateDataBuffer0.put(textureCoordinateData0)
textureCoordinateDataBuffer0.position(0)
// 创建图片纹理
// Create texture
val textures = IntArray(1)
ShaderUtil.loadTexture(bitmap, textures)
imageTexture = textures[0]
}
fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
glSurfaceViewWidth = width
glSurfaceViewHeight = height
// 计算并设置窗口大小
val imgRatio = bitmap.width.toFloat().div(bitmap.height)
val windowRatio = width.toFloat().div(height)
GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
if (imgRatio > windowRatio) {
Matrix.orthoM(translateMatrix, 0, -1f, 1f, -imgRatio * 2f, imgRatio * 2f, 0f, 10f);
} else {
Matrix.orthoM(translateMatrix, 0, -(1 / imgRatio) * 2f, (1 / imgRatio) * 2f, -1f, 1f, 0f, 10f);
}
}
fun draw() {
realDraw(programId0, imageTexture, textureCoordinateDataBuffer0)
}
private fun realDraw(programId: Int, texture: Int, textureCoordinateDataBuffer: FloatBuffer) {
// 应用GL程序
GLES30.glUseProgram(programId)
// 获取字段a_position在shader中的位置
val aPositionLocation = GLES30.glGetAttribLocation(programId, "aPosition")
// 启动对应位置的参数
GLES30.glEnableVertexAttribArray(aPositionLocation)
// 指定a_position所使用的顶点数据
GLES30.glVertexAttribPointer(aPositionLocation, 2, GLES30.GL_FLOAT, false, 0, vertexDataBuffer)
// 获取字段a_textureCoordinate在shader中的位置
val aTextureCoordinateLocation = GLES30.glGetAttribLocation(programId, "aTexCoord")
// 启动对应位置的参数
GLES30.glEnableVertexAttribArray(aTextureCoordinateLocation)
// 指定a_textureCoordinate所使用的顶点数据
GLES30.glVertexAttribPointer(aTextureCoordinateLocation,
2,
GLES30.GL_FLOAT,
false,
0,
textureCoordinateDataBuffer)
GLES30.glUniformMatrix4fv(uMatrixHandle, 1, false, translateMatrix, 0)
// 绑定纹理并设置u_texture参数
// Bind the texture and set the u_texture parameter
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture)
val uTextureLocation = GLES30.glGetAttribLocation(programId, "vTexCoord")
GLES30.glUniform1i(uTextureLocation, 0)
GLES30.glUniform1i(samplerHandle, 0)
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 6)
}
}
GLImageFilterRender
import android.opengl.GLES30
import android.opengl.Matrix
import com.df.openglstudydemo.util.ShaderUtil
import com.df.openglstudydemo.util.Util
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class GLImageFilterRender() {
/*
* 顶点位置程序
*/
private val vertexShaderCode =
"uniform mat4 uTMatrix;" +
"attribute vec4 aPosition;" +
"attribute vec2 aTexCoord;" +
"varying vec2 vTexCoord;" +
"void main() { " +
" gl_Position = uTMatrix * aPosition;" +
" vTexCoord = aTexCoord;" +
"}"
/**
* 片元颜色程序
*/
private val fragmentShader_LUT =
"precision mediump float;" +
"uniform sampler2D uSampler;" +
"uniform sampler2D uLutSampler;" +
"varying vec2 vTexCoord;" +
"void main() { " +
" vec4 texColor = texture2D(uSampler,vTexCoord);" +
" float blueColor = texColor.b * 63.0;" +
" vec2 quad1,quad2;" +
" quad1.y = floor(floor(blueColor) /8.0);" +
" quad1.x = floor(blueColor)- (quad1.y * 8.0);" +
" quad2.y = floor(ceil(blueColor) /8.0);" +
" quad2.x = ceil(blueColor)- (quad2.y * 8.0);" +
" vec2 texPos1,texPos2;" +
" texPos1.x = (quad1.x * 0.125) + 0.5/512.0 +(0.125-1.0/512.0) * texColor.r;" +
" texPos1.y = (quad1.y * 0.125) + 0.5/512.0 +(0.125-1.0/512.0) * texColor.g;" +
" texPos2.x = (quad2.x * 0.125) + 0.5/512.0 +(0.125-1.0/512.0) * texColor.r;" +
" texPos2.y = (quad2.y * 0.125) + 0.5/512.0 +(0.125-1.0/512.0) * texColor.g;" +
" vec4 newColor1 = texture2D(uLutSampler,texPos1);" +
" vec4 newColor2 = texture2D(uLutSampler,texPos2);" +
" vec4 newColor = mix(newColor1,newColor2,fract(blueColor));" +
" gl_FragColor = newColor;" +
"}"
private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private lateinit var vertexDataBuffer: FloatBuffer
private val textureCoordinateData0 = floatArrayOf(0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
private lateinit var textureCoordinateDataBuffer0: FloatBuffer
private var programId0 = 0
private var inputTexureId = 0
private var lutTextureId= 0
private var glSurfaceViewWidth = 0
private var glSurfaceViewHeight = 0
private var translateMatrix = FloatArray(16)
fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化坐标、图片数据
// Init the coordinates and image texture
initData()
// 创建2个GL Program,第一个将图片的蓝色通道全部设为0.5,第二做水平方向模糊
// Create two GL programs, and one is used for set the blue channel to 0.5, while the other is used for horizontal blur
programId0 = ShaderUtil.createGlProgram(vertexShaderCode, fragmentShader_LUT)
Matrix.setIdentityM(translateMatrix, 0)
}
private fun initData() {
vertexDataBuffer =
ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8).order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
textureCoordinateDataBuffer0 = ByteBuffer.allocateDirect(textureCoordinateData0.size * java.lang.Float.SIZE / 8)
.order(ByteOrder.nativeOrder()).asFloatBuffer()
textureCoordinateDataBuffer0.put(textureCoordinateData0)
textureCoordinateDataBuffer0.position(0)
// 加载默认的滤镜图片
val textures = IntArray(1)
ShaderUtil.loadTexture(Util.decodeBitmapFromAssets("lut_d.png"), textures)
lutTextureId = textures[0]
}
fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
glSurfaceViewWidth = width
glSurfaceViewHeight = height
GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
}
fun updateTexure(inputTexure: Int) {
this.inputTexureId = inputTexure
}
fun draw() {
realDraw(programId0, inputTexureId, textureCoordinateDataBuffer0)
}
private fun realDraw(programId: Int, texture: Int, textureCoordinateDataBuffer: FloatBuffer) {
// 应用GL程序
// Use the GL program
GLES30.glUseProgram(programId)
val aPositionLocation = GLES30.glGetAttribLocation(programId, "aPosition")
GLES30.glEnableVertexAttribArray(aPositionLocation)
GLES30.glVertexAttribPointer(aPositionLocation, 2, GLES30.GL_FLOAT, false, 0, vertexDataBuffer)
val aTextureCoordinateLocation = GLES30.glGetAttribLocation(programId, "aTexCoord")
GLES30.glEnableVertexAttribArray(aTextureCoordinateLocation)
GLES30.glVertexAttribPointer(aTextureCoordinateLocation,
2, GLES30.GL_FLOAT, false, 0, textureCoordinateDataBuffer)
val uMatrixHandle = GLES30.glGetUniformLocation(programId, "uTMatrix")
GLES30.glUniformMatrix4fv(uMatrixHandle, 1, false, translateMatrix, 0)
// 绑定纹理并设置u_texture参数
val uSimplerHandle = GLES30.glGetUniformLocation(programId, "uSampler")
val uLutSimplerHandle = GLES30.glGetUniformLocation(programId, "uLutSampler")
// 激活纹理单元
GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
// 绑定纹理单元
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture)
// 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE0,因此传递0
GLES30.glUniform1i(uSimplerHandle, 1)
// 激活纹理单元
GLES30.glActiveTexture(GLES30.GL_TEXTURE2)
// 绑定纹理单元
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, lutTextureId)
// 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE1,因此传递1
GLES30.glUniform1i(uLutSimplerHandle, 2)
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0,6)
}
}
GLImageTransitionRender(转场render,转场的顶点shader和fragment参考:
https://gl-transitions.com/gallery](https://gl-transitions.com/gallery)
import android.graphics.BitmapFactory
import android.opengl.GLES30
import android.opengl.Matrix
import com.df.openglstudydemo.R
import com.df.openglstudydemo.util.ShaderUtil
import com.df.openglstudydemo.util.Util
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class GLImageTransitionRender() {
private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private val VERTEX_COMPONENT_COUNT = 2
private lateinit var vertexDataBuffer: FloatBuffer
private val textureCoordinateData0 = floatArrayOf(0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
private lateinit var textureCoordinateDataBuffer0: FloatBuffer
private var programId0 = 0
private var inputTexureId = 0
private var inputTexureIds= IntArray(1)
private var dstTextureIds = IntArray(1)
private var displacementMapIds = IntArray(1)
private var glSurfaceViewWidth = 0
private var glSurfaceViewHeight = 0
private var translateMatrix = FloatArray(16)
private var positionHandle: Int = -1
private var texCoordHandle: Int = -1
private var samplerHandle: Int = -1
private var dstSamplerHandle: Int = -1
private var displacementMapSamplerHandle: Int = -1
private var progressHandle: Int = -1
private var uMatrixHandle: Int = -1
private val FRAME_SIZE = 240
private var frameIndex = 0
fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化坐标、图片数据
// Init the coordinates and image texture
initData()
val vertexShader = ShaderUtil.readFileFromAssets("vertex.glsl", Util.context)
val fragmentShader = ShaderUtil.readFileFromAssets("fragment_circle.glsl", Util.context)
programId0 = ShaderUtil.createGlProgram(vertexShader, fragmentShader)
Matrix.setIdentityM(translateMatrix, 0)
// 将数据传递shader
positionHandle = GLES30.glGetAttribLocation(programId0, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
texCoordHandle = GLES30.glGetAttribLocation(programId0, "aTexCoord")
GLES30.glEnableVertexAttribArray(texCoordHandle)
uMatrixHandle = GLES30.glGetUniformLocation(programId0, "uTMatrix")
samplerHandle = GLES30.glGetUniformLocation(programId0, "uSampler")
dstSamplerHandle = GLES30.glGetUniformLocation(programId0, "uDstSampler")
displacementMapSamplerHandle = GLES30.glGetUniformLocation(programId0, "displacementMap")
progressHandle = GLES30.glGetUniformLocation(programId0, "progress")
ShaderUtil.loadTexture(BitmapFactory.decodeResource(Util.context.resources,R.drawable.maomi), dstTextureIds)
ShaderUtil.loadTexture(BitmapFactory.decodeResource(Util.context.resources, R.drawable.displacementmap),
displacementMapIds)
}
private fun initData() {
vertexDataBuffer =
ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8).order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
textureCoordinateDataBuffer0 = ByteBuffer.allocateDirect(textureCoordinateData0.size * java.lang.Float.SIZE / 8)
.order(ByteOrder.nativeOrder()).asFloatBuffer()
textureCoordinateDataBuffer0.put(textureCoordinateData0)
textureCoordinateDataBuffer0.position(0)
}
fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
glSurfaceViewWidth = width
glSurfaceViewHeight = height
GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
}
fun updateTexure(inputTexure: Int) {
this.inputTexureId = inputTexure
}
fun draw() {
bindGLProgram(programId0, inputTexureId, textureCoordinateDataBuffer0)
}
private fun bindGLProgram(programId: Int, texture: Int, textureCoordinateDataBuffer: FloatBuffer) {
GLES30.glUseProgram(programId)
programId?.let {
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(positionHandle, 2, GLES30.GL_FLOAT, false, 0, vertexDataBuffer)
GLES30.glEnableVertexAttribArray(texCoordHandle)
GLES30.glVertexAttribPointer(texCoordHandle,
2, GLES30.GL_FLOAT, false, 0, textureCoordinateDataBuffer)
}
GLES30.glUniformMatrix4fv(uMatrixHandle, 1, false, translateMatrix, 0)
// 激活纹理单元
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
// 绑定纹理单元
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture)
// 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE0,因此传递0
GLES30.glUniform1i(samplerHandle, 0)
if (dstSamplerHandle > 0) {
// 激活纹理单元
GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
// 绑定纹理单元
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, dstTextureIds[0])
// 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE1,因此传递1
GLES30.glUniform1i(dstSamplerHandle, 1)
}
// 激活纹理单元
GLES30.glActiveTexture(GLES30.GL_TEXTURE2)
// 绑定纹理单元
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, displacementMapIds[0])
// 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE1,因此传递1
GLES30.glUniform1i(displacementMapSamplerHandle, 2)
val progress = (frameIndex++ % FRAME_SIZE) * 1.0 / FRAME_SIZE
if (progressHandle > 0) {
GLES30.glUniform1f(progressHandle, progress.toFloat())
}
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 6)
}
fun getTranslateMatrix() = translateMatrix
}
vertex.glsl
uniform mat4 uTMatrix;
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = uTMatrix * aPosition;
vTexCoord = aTexCoord;
}
fragment_circle.glsl
uniform sampler2D uSmapler;
uniform sampler2D uDstSampler;
varying vec2 vTexCoord;
uniform float progress;
vec4 getFromColor(vec2 uv){
return texture2D(uSmapler, uv);
}
vec4 getDstColor(vec2 uv){
return texture2D(uDstSampler, uv);
}
uniform sampler2D displacementMap;
vec4 transition (vec2 uv) {
float displacement = texture2D(displacementMap, uv).r * 0.5;
vec2 uvFrom = vec2(uv.x + progress * displacement, uv.y);
vec2 uvTo = vec2(uv.x - (1.0 - progress) * displacement, uv.y);
return mix(
getFromColor(uvFrom),
getDstColor(uvTo),
progress
);
}
void main(){
gl_FragColor=transition(vTexCoord);
}
GLFBOMultiRender
import android.opengl.GLES30
import android.opengl.Matrix
import com.df.openglstudydemo.util.ShaderUtil
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class GLFBOMultiRender() {
/*
* 顶点位置程序
*/
private val vertexShaderCode =
"attribute vec4 aPosition;" +
"attribute vec2 aTexCoord;" +
"varying vec2 vTexCoord;" +
"void main() { " +
" gl_Position = aPosition;" +
" vTexCoord = aTexCoord;" +
"}"
private val fragmentShaderCode0_2 =
"precision mediump float;" +
"uniform sampler2D uSampler;" +
"varying vec2 vTexCoord;" +
"void main() { " +
" gl_FragColor = texture2D(uSampler,vTexCoord);" +
"}"
private var programId1 = 0
private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private val VERTEX_COMPONENT_COUNT = 2
private lateinit var vertexDataBuffer : FloatBuffer
private val textureCoordinateData1 = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 0f, 0f, 1f, 1f, 1f, 0f)
private lateinit var textureCoordinateDataBuffer1 : FloatBuffer
private var glSurfaceViewWidth = 0
private var glSurfaceViewHeight = 0
// 帧缓存
// frame buffer
private var frameBuffer = 0
private var frameBufferId = IntArray(1)
// 帧缓绑定的texture
// the texture bind on the frame buffer
private var frameBufferTexture = 0
private var frameBufferTextureIds = IntArray(1)
private val translateMatrix = FloatArray(16)
fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化坐标、图片数据
initData()
programId1 = ShaderUtil.createGlProgram(vertexShaderCode,fragmentShaderCode0_2)
Matrix.setIdentityM(translateMatrix, 0)
}
private fun initData() {
// 将三角形顶点数据放入buffer中
vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
textureCoordinateDataBuffer1 = ByteBuffer.allocateDirect(textureCoordinateData1.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
textureCoordinateDataBuffer1.put(textureCoordinateData1)
textureCoordinateDataBuffer1.position(0)
}
fun getFrameBufferId() = frameBufferId[0]
fun getTexureId():Int {
return frameBufferTextureIds[0]
}
fun onSurfaceChanged(p0: GL10?, width: Int, height: Int){
glSurfaceViewWidth = width
glSurfaceViewHeight = height
GLES30.glGenFramebuffers(frameBufferId.size, frameBufferId, 0)
ShaderUtil.createColorTexture(frameBufferTextureIds,glSurfaceViewWidth,glSurfaceViewWidth)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0])
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D,
frameBufferTextureIds[0],
0)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
}
}
工具类
ShaderUtil
import android.content.Context
import android.graphics.Bitmap
import android.opengl.GLES30
import android.opengl.GLUtils
import android.util.Log
class ShaderUtil {
companion object {
fun readFileFromAssets(fileName: String?, context: Context): String? {
fileName ?: return null
context ?: return null
val result = StringBuilder()
try {
val myIs = context.assets.open(fileName)
val buffer = ByteArray(1024)
var count = 0
while ((myIs.read(buffer).also { count = it })!= -1) {
result.append(String(buffer, 0, count))
}
myIs.close()
} catch (e: Exception) {
e.printStackTrace()
}
return result.toString()
}
/**
* 创建shader,加载shader程序
*/
private fun loadShader(type: Int, shaderCode: String): Int {
var shader = GLES30.glCreateShader(type)
if (shader != 0) {
GLES30.glShaderSource(shader, shaderCode)
GLES30.glCompileShader(shader)
val compiled = IntArray(1)
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == 0) {
Log.e("wdf", "编译失败=" + GLES30.glGetShaderInfoLog(shader))
GLES30.glDeleteShader(shader)
shader = 0
}
}
return shader
}
/**
* 加载纹理
*/
fun loadTexture(bitmap: Bitmap, textureIdArray: IntArray) {
GLES30.glGenTextures(1, textureIdArray, 0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIdArray[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)
// 加载bitmap到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0)
GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D)
bitmap.recycle()
// 取消绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
}
fun createGlProgram(vertexShader: String?, fragmentShader: String?): Int {
if (vertexShader == null || fragmentShader == null) {
return 0
}
val vertexShaderId = loadShader(GLES30.GL_VERTEX_SHADER, vertexShader)
val fragmentShaderId = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShader)
if (vertexShaderId == 0 || fragmentShaderId == 0) {
return 0
}
var program = GLES30.glCreateProgram()
if (program != 0) {
program.let {
// 将顶点着色器加入程序
GLES30.glAttachShader(it, vertexShaderId)
// 将片元着色器加入程序
GLES30.glAttachShader(it, fragmentShaderId)
// 链接到着色器程序
GLES30.glLinkProgram(it)
val linkStatus = IntArray(1)
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES30.GL_TRUE) {
val info = GLES30.glGetProgramInfoLog(it)
GLES30.glDeleteProgram(program)
// 打印链接程序日志
Log.e("wdf", "link失败==" + info)
program = 0
}
}
}
return program
}
fun createColorTexture(colorTexureId: IntArray,width:Int,height:Int) {
//绑定纹理缓存到纹理单元
GLES30.glGenTextures(1, colorTexureId, 0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, colorTexureId[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)
//生成2D纹理
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA,width, height,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null)
}
fun genBindFrameBuffer(frameBufferIds:IntArray){
GLES30.glGenFramebuffers(frameBufferIds.size, frameBufferIds, 0)
}
}
}
Util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.opengl.GLES20
import android.opengl.GLES30
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.util.UUID
class Util {
companion object {
lateinit var context: Context
fun decodeBitmapFromAssets(filename : String) : Bitmap {
val options = BitmapFactory.Options()
options.inSampleSize = 1
val bitmap = BitmapFactory.decodeStream(context.assets.open(filename))
if (bitmap == null) {
Log.e("debug", "bitmap decode fail, path = $filename")
}
return bitmap
}
fun checkGLError() {
val error = GLES30.glGetError()
if (error != GLES30.GL_NO_ERROR) {
val hexErrorCode = Integer.toHexString(error)
Log.e("debug", "glError: $hexErrorCode")
throw RuntimeException("GLError")
}
}
fun getScreenWidth() : Int {
return 1440
}
fun getScreenHeight() : Int {
return 2560
}
fun getSnapshot(width: Int, height: Int): Bitmap? {
if (width == 0 || height == 0) {
return null
}
val pitch = width * 4
val byteArray = ByteArray(pitch * height)
val byteBuffer = ByteBuffer.allocateDirect(pitch * height)
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuffer)
byteBuffer.rewind()
val temp = ByteArray(pitch)
for (i in 0 until height) {
byteBuffer.get(temp)
System.arraycopy(temp, 0, byteArray, (height - i - 1) * pitch, pitch)
}
byteBuffer.clear()
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(byteArray))
return bitmap
}
fun saveBitmap(bitmap: Bitmap?) {
bitmap ?: return
try {
val parentFile: File = File((context.externalCacheDir).toString())
parentFile.mkdirs()
val file: File = File(parentFile.path, UUID.randomUUID().toString() + ".jpg")
Log.e("wdf", "path==" + file.path)
val out = FileOutputStream(file)
bitmap?.compress(Bitmap.CompressFormat.JPEG, 100, out)
out.flush()
out.close()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
}
}