OpenGL ES ->乒乓缓冲,计算只用两个帧缓冲对象(Frame Buffer Object)+叠加多个滤镜作用后的Bitmap
乒乓缓冲核心思想
- 不使用乒乓缓冲,如果要每个滤镜作用下的绘制内容,也就是这个滤镜作用下的帧缓冲,需要创建一个
Frame Buffer Object加上对应的Frame Buffer Object Texture - 使用乒乓缓冲,只用两个
Frame Buffer Object加上对应的Frame Buffer Object Texture,Open GL渲染管线不允许读取一个帧缓冲的同时,对这个帧缓冲的纹理进行写入,这样会导致读写冲突绘制内容出错,所以用到乒乓缓冲,绑定一个帧缓冲的同时,使用另外一个帧缓冲的纹理,交替使用
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"><!-- OpenGL渲染区域 --><com.example.myapplication.MyGLSurfaceViewandroid:id="@+id/gl_surface_view"android:layout_width="match_parent"android:layout_height="match_parent" /><ImageViewandroid:id="@+id/image_view_1"android:layout_width="180dp"android:layout_height="180dp"android:layout_alignParentStart="true"android:layout_alignParentTop="true"android:layout_margin="10dp"android:background="#33000000" /><ImageViewandroid:id="@+id/image_view_2"android:layout_width="180dp"android:layout_height="180dp"android:layout_alignParentEnd="true"android:layout_alignParentTop="true"android:layout_margin="10dp"android:background="#33000000" /><ImageViewandroid:id="@+id/image_view_3"android:layout_width="180dp"android:layout_height="180dp"android:layout_alignParentStart="true"android:layout_alignParentBottom="true"android:layout_margin="10dp"android:background="#33000000" /><ImageViewandroid:id="@+id/image_view_4"android:layout_width="180dp"android:layout_height="180dp"android:layout_alignParentEnd="true"android:layout_alignParentBottom="true"android:layout_margin="10dp"android:background="#33000000" /><Buttonandroid: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="10dp"android:text="滤镜渲染"android:padding="12dp" /></RelativeLayout>
Activity代码
class MainActivity : AppCompatActivity() {private lateinit var glSurfaceView: MyGLSurfaceViewprivate lateinit var imageView1: ImageViewprivate lateinit var imageView2: ImageViewprivate lateinit var imageView3: ImageViewprivate lateinit var imageView4: ImageViewprivate lateinit var captureButton: Button@SuppressLint("MissingInflatedId")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)glSurfaceView = findViewById(R.id.gl_surface_view)imageView1 = findViewById(R.id.image_view_1)imageView2 = findViewById(R.id.image_view_2)imageView3 = findViewById(R.id.image_view_3)imageView4 = findViewById(R.id.image_view_4)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 -> {glSurfaceView?.getDrawData()?.getEdgeFilterBitmap()?.let {imageView1.setImageBitmap(it)}glSurfaceView?.getDrawData()?.getPixelFilterBitmap()?.let {imageView2.setImageBitmap(it)}glSurfaceView?.getDrawData()?.getColorFilterBitmap()?.let {imageView3.setImageBitmap(it)}glSurfaceView?.getDrawData()?.getOriginBitmap()?.let {imageView4.setImageBitmap(it)}}MotionEvent.ACTION_UP -> {imageView1.setImageBitmap(null)imageView2.setImageBitmap(null)imageView3.setImageBitmap(null)imageView4.setImageBitmap(null)}}return true}})}
}
自定义GLSurfaceView代码
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {private var mRenderer = MyGLRenderer(context)init {// 设置 OpenGL ES 3.0 版本setEGLContextClientVersion(3)setRenderer(mRenderer)// 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源renderMode = RENDERMODE_WHEN_DIRTY}fun getDrawData(): DrawData? {return mRenderer?.getDrawData()}
}
自定义GLSurfaceView.Renderer代码
class MyGLRenderer(private val mContext: Context) : GLSurfaceView.Renderer {private var mDrawData: DrawData? = nulloverride fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {// 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为青蓝色 (Red=0, Green=0.5, Blue=0.5, Alpha=1)GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)mDrawData = DrawData().apply {initTexture0(mContext, R.drawable.picture)initShader()initVertexBuffer()initFrameBuffer()initEdgeFilterShader()initPixelFilterShader()initColorFilterShader()initPingFrameBuffer()initPongFrameBuffer()}}override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {// 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小GLES30.glViewport(0, 0, width, height)mDrawData?.computeMVPMatrix(width, height)}override fun onDrawFrame(gl: GL10?) {GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)// 绘制到FBO(离屏渲染)mDrawData?.drawOriginFrameBuffer()// 缓存像素着色器mDrawData?.drawPixelFilterBitmap()// 缓存彩色旋涡mDrawData?.drawColorFilterBitmap()// 缓存边缘滤镜mDrawData?.drawEdgeFilterBitmap()// 在GLSurfaceView绘制FBO内容mDrawData?.drawGLSurfaceView()}fun getDrawData(): DrawData? {return mDrawData}
}
GLSurfaceView.Renderer需要的绘制数据
class DrawData {private var NO_OFFSET = 0private val VERTEX_POS_DATA_SIZE = 3private val TEXTURE_POS_DATA_SIZE = 2private var mProgram: Int = -1private var mEdgeProgram : Int = -1 // 边缘检测private var mPixelProgram : Int = -1 // 像素着色private var mColorProgram : Int = -1 // 彩色旋涡// FBO(Frame Buffer Object), 帧缓冲对象,用于存储渲染后的图像private var mFBO = IntArray(1)private var mEdgeFBO = IntArray(1)private var mPixelFBO = IntArray(1)private var mColorFBO = IntArray(1)private var mPingFBO = IntArray(1)private var mPongFBO = IntArray(1)// VAO(Vertex Array Object), 顶点数组对象, 用于存储VBOprivate var mVAO = IntArray(1)// VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据private var mVBO = IntArray(2)// IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据private var mIBO = IntArray(1)// 纹理IDprivate var mTextureID = IntArray(1)// FBO中的纹理IDprivate var mFBOTextureID = IntArray(1)private var mEdgeFBOTextureID = IntArray(1)private var mPixelFBOTextureID = IntArray(1)private var mColorFBOTextureID = IntArray(1)private var mPingTextureID = IntArray(1)private var mPongTextureID = IntArray(1)// 有上下翻转的变换矩阵private var mMVPMatrix = FloatArray(16)// 投影矩阵private val mProjectionMatrix = FloatArray(16)// 相机矩阵private val mViewMatrix = FloatArray(16)// 视口比例private var mViewPortRatio = 1f// 帧缓冲宽高private var mFrameBufferWidth = 0private 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)private var mOriginBitmap : Bitmap ?= nullprivate var mEdgeFilterBitmap : Bitmap ?= nullprivate var mPixelFilterBitmap : Bitmap ?= nullprivate var mColorFilterBitmap : Bitmap ?= null// 初始化着色器程序fun initShader() {val vertexShaderCode = """#version 300 esuniform mat4 uMVPMatrix; // 变换矩阵in vec4 aPosition; // 顶点坐标in vec2 aTexCoord; // 纹理坐标 out vec2 vTexCoord; void main() {// 输出顶点坐标和纹理坐标到片段着色器gl_Position = uMVPMatrix * aPosition; vTexCoord = aTexCoord;}""".trimIndent()val fragmentShaderCode = """#version 300 esprecision 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.glUseProgram(mProgram)// 删除着色器对象GLES30.glDeleteShader(vertexShader)GLES30.glDeleteShader(fragmentShader)}// 创建VAO, VBO, IBOfun initVertexBuffer() {// 绑定VAOGLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)GLES30.glBindVertexArray(mVAO[0])// 绑定VBOGLES30.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)// 绑定IBOGLES30.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)// 解绑VAOGLES30.glBindVertexArray(0)// 解绑IBOGLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)}// 初始化帧缓冲fun initFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mFBO.size, mFBO, NO_OFFSET)// 绑定FBOGLES30.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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mFBOTextureID[0], // 纹理ID0)if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) {Log.e("yang", "initFrameBuffer: FBO初始化失败")}// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}// FrameBuffer内容绘制(离屏渲染)fun drawOriginFrameBuffer() {val previousProgram = IntArray(1)val previousFrameBuffer = IntArray(1)val viewPort = IntArray(4)try {GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)enableTexture0(mProgram, mTextureID[0])// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0])GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)computeFrameBufferMVPMatrix()drawSomething(mProgram, mFrameBufferMVPMatrix)mOriginBitmap = savePixelBufferBitmap()// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)disableTexture0()}finally {GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])GLES30.glUseProgram(previousProgram[0])}}// GLSurfaceView内容绘制fun drawGLSurfaceView() {val previousProgram = IntArray(1)val previousFrameBuffer = IntArray(1)try {GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)enableTexture0(mProgram, mPingTextureID[0])drawSomething(mProgram, mMVPMatrix)disableTexture0()} finally {GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])GLES30.glUseProgram(previousProgram[0])}}fun drawEdgeFilterBitmap() {takeIf { mEdgeFilterBitmap == null }?.let {val previousProgram = IntArray(1)val previousFrameBuffer = IntArray(1)val viewPort = IntArray(4)try {GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)GLES30.glUseProgram(mEdgeProgram)GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)enableTexture2(mEdgeProgram, mPongTextureID[0])// 纹理大小val textureSizeHandle = GLES30.glGetUniformLocation(mEdgeProgram, "uTextureSize")GLES30.glUniform2f(textureSizeHandle, mFrameBufferWidth.toFloat(), mFrameBufferHeight.toFloat())drawSomething(mEdgeProgram, mFrameBufferMVPMatrix)mEdgeFilterBitmap = savePixelBufferBitmap()disableTexture2()GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)} finally {GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])GLES30.glUseProgram(previousProgram[0])}}}fun drawPixelFilterBitmap() {takeIf { mPixelFilterBitmap == null }?.let {val previousProgram = IntArray(1)val previousFrameBuffer = IntArray(1)val viewPort = IntArray(4)try {GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)GLES30.glUseProgram(mPixelProgram)GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)enableTexture3(mPixelProgram, mFBOTextureID[0])// 纹理大小val textureSizeHandle = GLES30.glGetUniformLocation(mPixelProgram, "uTextureSize")GLES30.glUniform2f(textureSizeHandle, mFrameBufferWidth.toFloat(), mFrameBufferWidth.toFloat())// 像素尺寸val pixelSizeHandle = GLES30.glGetUniformLocation(mPixelProgram, "uPixelSize")GLES30.glUniform1f(pixelSizeHandle, 15.0f) // 可调节drawSomething(mPixelProgram, mFrameBufferMVPMatrix)mPixelFilterBitmap = savePixelBufferBitmap()disableTexture3()GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)} finally {GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])GLES30.glUseProgram(previousProgram[0])}}}fun drawColorFilterBitmap() {takeIf { mColorFilterBitmap == null }?.let {val previousProgram = IntArray(1)val previousFrameBuffer = IntArray(1)val viewPort = IntArray(4)try {GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewPort, NO_OFFSET)GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight)GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, previousProgram, 0)GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, previousFrameBuffer, 0)GLES30.glUseProgram(mColorProgram)GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPongFBO[0])GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)enableTexture4(mColorProgram, mPingTextureID[0])// 流动时间val timeHandle = GLES30.glGetUniformLocation(mColorProgram, "uTime")if (timeHandle != -1) { // 检查是否存在该参数GLES30.glUniform1f(timeHandle, (System.currentTimeMillis() % 10000) / 10000.0f)}// 设置旋涡强度val twistIntensityHandle = GLES30.glGetUniformLocation(mColorProgram, "uTwistIntensity")GLES30.glUniform1f(twistIntensityHandle, 0.15f)drawSomething(mColorProgram, mFrameBufferMVPMatrix)mColorFilterBitmap = savePixelBufferBitmap()disableTexture4()GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)} finally {GLES30.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3])GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, previousFrameBuffer[0])GLES30.glUseProgram(previousProgram[0])}}}// 使用着色器程序绘制图形fun drawSomething(program: Int, mvpMatrix: FloatArray) {// 解析变换矩阵val matrixHandle = GLES30.glGetUniformLocation(program, "uMVPMatrix")GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mvpMatrix, NO_OFFSET)// 绑定VAOGLES30.glBindVertexArray(mVAO[0])// 绘制图形GLES30.glDrawElements(GLES30.GL_TRIANGLES,index.size,GLES30.GL_UNSIGNED_SHORT,NO_OFFSET)// 解绑VAOGLES30.glBindVertexArray(0)}// 边缘检测着色器fun initEdgeFilterShader() {val vertexShaderCode = """#version 300 esuniform mat4 uMVPMatrix;in vec4 aPosition;in vec2 aTexCoord;out vec2 vTexCoord;void main() {gl_Position = uMVPMatrix * aPosition;vTexCoord = aTexCoord;}""".trimIndent()val fragmentShaderCode = """#version 300 esprecision mediump float;uniform sampler2D uTexture_2;uniform vec2 uTextureSize;in vec2 vTexCoord;out vec4 fragColor;void main() {float dx = 1.0 / uTextureSize.x;float dy = 1.0 / uTextureSize.y;// 获取周围像素vec4 center = texture(uTexture_2, vTexCoord);vec4 left = texture(uTexture_2, vTexCoord - vec2(dx, 0.0));vec4 right = texture(uTexture_2, vTexCoord + vec2(dx, 0.0));vec4 top = texture(uTexture_2, vTexCoord - vec2(0.0, dy));vec4 bottom = texture(uTexture_2, vTexCoord + vec2(0.0, dy));// 计算边缘vec4 horizontal = abs(right - left);vec4 vertical = abs(bottom - top);float edge = (horizontal.r + horizontal.g + horizontal.b + vertical.r + vertical.g + vertical.b) / 6.0;// 边缘增强fragColor = vec4(vec3(1.0 - edge * 3.0), 1.0);}""".trimIndent()// 加载着色器并创建程序val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)val fragmentShader =LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)mEdgeProgram = GLES30.glCreateProgram()GLES30.glAttachShader(mEdgeProgram, vertexShader)GLES30.glAttachShader(mEdgeProgram, fragmentShader)GLES30.glLinkProgram(mEdgeProgram)// 删除着色器对象GLES30.glDeleteShader(vertexShader)GLES30.glDeleteShader(fragmentShader)}// 像素着色器fun initPixelFilterShader() {val vertexShaderCode = """#version 300 esuniform mat4 uMVPMatrix;in vec4 aPosition;in vec2 aTexCoord;out vec2 vTexCoord;void main() {gl_Position = uMVPMatrix * aPosition;vTexCoord = aTexCoord;}""".trimIndent()val fragmentShaderCode = """#version 300 esprecision mediump float;uniform sampler2D uTexture_3;uniform vec2 uTextureSize;uniform float uPixelSize; // 像素块大小 (10.0-30.0)in vec2 vTexCoord;out vec4 fragColor;void main() {float dx = uPixelSize / uTextureSize.x;float dy = uPixelSize / uTextureSize.y;// 计算像素块的中心坐标vec2 pixelatedCoord;pixelatedCoord.x = dx * floor(vTexCoord.x / dx) + dx * 0.5;pixelatedCoord.y = dy * floor(vTexCoord.y / dy) + dy * 0.5;// 采样像素块中心的颜色vec4 pixelColor = texture(uTexture_3, pixelatedCoord);// 减少颜色深度,增强复古效果const float colorLevels = 5.0;pixelColor = floor(pixelColor * colorLevels) / colorLevels;// 添加微小的像素边框vec2 pixelPos = fract(vTexCoord / vec2(dx, dy));float borderFactor = step(0.95, max(pixelPos.x, pixelPos.y));pixelColor.rgb *= mix(1.0, 0.8, borderFactor);fragColor = pixelColor;}""".trimIndent()// 加载着色器并创建程序val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)val fragmentShader =LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)mPixelProgram = GLES30.glCreateProgram()GLES30.glAttachShader(mPixelProgram, vertexShader)GLES30.glAttachShader(mPixelProgram, fragmentShader)GLES30.glLinkProgram(mPixelProgram)// 删除着色器对象GLES30.glDeleteShader(vertexShader)GLES30.glDeleteShader(fragmentShader)}// 彩色旋涡滤镜fun initColorFilterShader() {val vertexShaderCode = """#version 300 esuniform mat4 uMVPMatrix;in vec4 aPosition;in vec2 aTexCoord;out vec2 vTexCoord;void main() {gl_Position = uMVPMatrix * aPosition;vTexCoord = aTexCoord;}""".trimIndent()val fragmentShaderCode = """#version 300 esprecision mediump float;uniform sampler2D uTexture_4;uniform float uTime; // 动画时间 (如果需要)uniform float uTwistIntensity; // 旋涡强度 (0.1-0.3)in vec2 vTexCoord;out vec4 fragColor;// 更高效的RGB转HSV函数vec3 rgb2hsv(vec3 c) {vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));float d = q.x - min(q.w, q.y);float e = 1.0e-10;return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);}// 更高效的HSV转RGB函数vec3 hsv2rgb(vec3 c) {vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);}void main() {// 将纹理坐标转换为中心为原点的坐标vec2 center = vec2(0.5, 0.5);vec2 texCoordFromCenter = vTexCoord - center;// 计算距离和角度float distance = length(texCoordFromCenter);float angle = atan(texCoordFromCenter.y, texCoordFromCenter.x);// 应用旋涡扭曲angle += uTwistIntensity * (1.0 - distance);// 将极坐标转换回纹理坐标vec2 newCoord;newCoord.x = center.x + distance * cos(angle);newCoord.y = center.y + distance * sin(angle);// 采样扭曲后的颜色vec4 color = texture(uTexture_4, newCoord);// 增强颜色对比度和饱和度vec3 hsv = rgb2hsv(color.rgb);hsv.y = hsv.y * 1.4; // 增加饱和度hsv.z = hsv.z * 0.9 + 0.1; // 增加亮度和对比度// 添加彩虹色效果hsv.x = hsv.x + distance * 0.5;fragColor = vec4(hsv2rgb(hsv), color.a);}""".trimIndent()// 加载着色器并创建程序val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)val fragmentShader =LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)mColorProgram = GLES30.glCreateProgram()GLES30.glAttachShader(mColorProgram, vertexShader)GLES30.glAttachShader(mColorProgram, fragmentShader)GLES30.glLinkProgram(mColorProgram)// 删除着色器对象GLES30.glDeleteShader(vertexShader)GLES30.glDeleteShader(fragmentShader)}// 边缘滤镜的帧缓冲fun initEdgeFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mEdgeFBO.size, mEdgeFBO, NO_OFFSET)// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mEdgeFBO[0])// 创建空纹理GLES30.glGenTextures(mEdgeFBOTextureID.size, mEdgeFBOTextureID, NO_OFFSET)// 绑定空纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mEdgeFBOTextureID[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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mEdgeFBOTextureID[0], // 纹理ID0)// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}// 像素滤镜的帧缓冲fun initPixelFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mPixelFBO.size, mPixelFBO, NO_OFFSET)// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPixelFBO[0])// 创建空纹理GLES30.glGenTextures(mPixelFBOTextureID.size, mPixelFBOTextureID, NO_OFFSET)// 绑定空纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPixelFBOTextureID[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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mPixelFBOTextureID[0], // 纹理ID0)// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}// 彩色旋涡的帧缓冲fun initColorFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mColorFBO.size, mColorFBO, NO_OFFSET)// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mColorFBO[0])// 创建空纹理GLES30.glGenTextures(mColorFBOTextureID.size, mColorFBOTextureID, NO_OFFSET)// 绑定空纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorFBOTextureID[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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mColorFBOTextureID[0], // 纹理ID0)// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}fun initPingFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mPingFBO.size, mPingFBO, NO_OFFSET)// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPingFBO[0])// 创建空纹理GLES30.glGenTextures(mPingTextureID.size, mPingTextureID, NO_OFFSET)// 绑定空纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPingTextureID[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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mPingTextureID[0], // 纹理ID0)// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}fun initPongFrameBuffer() {// 创建FBOGLES30.glGenFramebuffers(mPongFBO.size, mPongFBO, NO_OFFSET)// 绑定FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mPongFBO[0])// 创建空纹理GLES30.glGenTextures(mPongTextureID.size, mPongTextureID, NO_OFFSET)// 绑定空纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mPongTextureID[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,用于绘制到FBOGLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, // FBOGLES30.GL_COLOR_ATTACHMENT0, // 颜色缓冲区GLES30.GL_TEXTURE_2D, // 纹理类型mPongTextureID[0], // 纹理ID0)// 解绑FBO纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)// 解绑FBOGLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)}fun savePixelBufferBitmap(): Bitmap? {// 分配缓冲区来存储像素数据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转换为Bitmapval bitmap = Bitmap.createBitmap(mFrameBufferWidth,mFrameBufferHeight,Bitmap.Config.ARGB_8888)pixelBuffer.rewind()bitmap.copyPixelsFromBuffer(pixelBuffer)return bitmap}// 计算GLSurfaceView变换矩阵fun computeMVPMatrix(width: Int, height: Int) {// 正交投影矩阵takeIf { width > height }?.let {mViewPortRatio = (width * 1f) / heightMatrix.orthoM(mProjectionMatrix, // 正交投影矩阵NO_OFFSET, // 偏移量-mViewPortRatio, // 近平面的坐标系左边界mViewPortRatio, // 近平面的坐标系右边界-1f, // 近平面的坐标系的下边界1f, // 近平面坐标系的上边界0f, // 近平面距离相机距离1f // 远平面距离相机距离)} ?: run {mViewPortRatio = (height * 1f) / widthMatrix.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, // 相机位置x0f, // 相机位置y1f, // 相机位置z0f, // 物体位置x0f, // 物体位置y0f, // 物体位置z0f, // 相机上方向x1f, // 相机上方向y0f // 相机上方向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 computeFrameBufferMVPMatrix() {// 正交投影矩阵takeIf { mFrameBufferWidth > mFrameBufferHeight }?.let {mViewPortRatio = (mFrameBufferWidth * 1f) / mFrameBufferHeightMatrix.orthoM(mProjectionMatrix, // 正交投影矩阵NO_OFFSET, // 偏移量-mViewPortRatio, // 近平面的坐标系左边界mViewPortRatio, // 近平面的坐标系右边界-1f, // 近平面的坐标系的下边界1f, // 近平面坐标系的上边界0f, // 近平面距离相机距离1f // 远平面距离相机距离)} ?: run {mViewPortRatio = (mFrameBufferHeight * 1f) / mFrameBufferWidthMatrix.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, // 相机位置x0f, // 相机位置y1f, // 相机位置z0f, // 物体位置x0f, // 物体位置y0f, // 物体位置z0f, // 相机上方向x1f, // 相机上方向y0f // 相机上方向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,
// )}// 加载纹理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]}// 激活纹理编号0fun enableTexture0(program: Int, id: Int) {GLES30.glActiveTexture(GLES30.GL_TEXTURE0)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)val textureSampleHandle = GLES30.glGetUniformLocation(program, "uTexture_0")GLES30.glUniform1i(textureSampleHandle, 0)}// 激活纹理编号2fun enableTexture2(program: Int, id: Int) {GLES30.glActiveTexture(GLES30.GL_TEXTURE2)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)val textureSampleHandle2 = GLES30.glGetUniformLocation(program, "uTexture_2")GLES30.glUniform1i(textureSampleHandle2, 2)}// 激活纹理编号3fun enableTexture3(program: Int, id: Int) {GLES30.glActiveTexture(GLES30.GL_TEXTURE3)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)val textureSampleHandle3 = GLES30.glGetUniformLocation(program, "uTexture_3")GLES30.glUniform1i(textureSampleHandle3, 3)}// 激活纹理编号4fun enableTexture4(program: Int, id: Int) {GLES30.glActiveTexture(GLES30.GL_TEXTURE4)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)val textureSampleHandle4 = GLES30.glGetUniformLocation(program, "uTexture_4")GLES30.glUniform1i(textureSampleHandle4, 4)}fun disableTexture0() {GLES30.glActiveTexture(GLES30.GL_TEXTURE0)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)}fun disableTexture2() {GLES30.glActiveTexture(GLES30.GL_TEXTURE2)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 2)}fun disableTexture3() {GLES30.glActiveTexture(GLES30.GL_TEXTURE3)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 3)}fun disableTexture4(){GLES30.glActiveTexture(GLES30.GL_TEXTURE4)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 4)}fun initTexture0(context: Context, resourceId: Int) {mTextureID[0] = loadTexture(context, resourceId)}fun getEdgeFilterBitmap(): Bitmap? {return mEdgeFilterBitmap}fun getPixelFilterBitmap(): Bitmap? {return mPixelFilterBitmap}fun getColorFilterBitmap(): Bitmap? {return mColorFilterBitmap}fun getOriginBitmap(): Bitmap? {return mOriginBitmap}object LoadShaderUtil {// 创建着色器对象fun loadShader(type: Int, source: String): Int {val shader = GLES30.glCreateShader(type)GLES30.glShaderSource(shader, source)GLES30.glCompileShader(shader)return shader}}
}
效果图

相关文章:
OpenGL ES ->乒乓缓冲,计算只用两个帧缓冲对象(Frame Buffer Object)+叠加多个滤镜作用后的Bitmap
乒乓缓冲核心思想 不使用乒乓缓冲,如果要每个滤镜作用下的绘制内容,也就是这个滤镜作用下的帧缓冲,需要创建一个Frame Buffer Object加上对应的Frame Buffer Object Texture使用乒乓缓冲,只用两个Frame Buffer Object加上对应的F…...
数据库练习2
目录 1.向heros表中新增一列信息,添加一些约束,并尝试查询一些信息 2.课堂代码练习 3.题目如下 一、单表查询 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号,不显示重复的部门号。 3、求出所有职工的人数。 4…...
macOS Sequoia 15.3 一直弹出“xx正在访问你的屏幕”
🙅 问题描述 macOS 系统升级后(15.2或者15.3均出现过此问题),不管是截图还是开腾讯会议,只要跟捕捉屏幕有关,都一直弹出这个选项,而且所有软件我都允许访问屏幕了,这个不是询问是否…...
OpenCV图像拼接(1)自动校准之校准旋转相机的函数calibrateRotatingCamera()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::calibrateRotatingCamera 是OpenCV中用于校准旋转相机的函数。它特别适用于那种相机相对于一个固定的场景进行纯旋转运动的情况&…...
Conda常用命令汇总(持续更新中)
原文章:安装和使用Miniconda来管理Python环境-CSDN博客 一、Miniconda的使用 Miniconda没有GUI界面,只能通过conda命令对Python环境和软件包进行管理,所以这里主要介绍一下conda的常用命令。 1. Conda相关 (1)查询conda版本 conda --vers…...
C# 调用 VITS,推理模型 将文字转wav音频调试 -数字人分支
Microsoft.ML.OnnxRuntime.OnnxRuntimeException: [ErrorCode:InvalidArgument] Input name: input_name is not in the metadata在 Microsoft.ML.OnnxRuntime.InferenceSession.LookupInputMetadata(String nodeName) 位置 D:\a\_work\1\s\csharp\src\Microsoft.ML.OnnxRuntim…...
Java锁等待唤醒机制
在 Java 并发编程中,锁的等待和唤醒机制至关重要,通常使用 wait()、notify() 和 notifyAll() 来实现线程间的协调。本文将详细介绍这些方法的用法,并通过示例代码加以说明。 1. wait()、notify() 与 notifyAll() 在 Java 中,Obj…...
Docker Compose 常用命令详解
Docker Compose 常用命令详解 Docker Compose 是 Docker 官方提供的一个用于管理多个容器的工具,可以使用 docker-compose.yml 文件定义和运行多容器应用。本篇博客将详细介绍 Docker Compose 的常用命令,帮助你更高效地管理容器。 1. docker compose u…...
C站算法技能题-题解(javascript)
切面条 const 切面条 (n10)>{return 2 ** n 1; } 切面条(0) 2 切面条(1) 3 切面条(2) 5 切面条(10) 1025大衍数列 const 大衍数列 (n100) > {let ans []for(let i1;i<n;i){if(i%2 0){ans.push((i ** 2 ) / 2)}else{ans.push((i ** 2 - 1) / 2)}}return ans…...
【Docker系列一】Docker 简介
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
C++进阶——封装红黑树实现map和set
目录 1、源码及框架分析 2、模拟实现map和set 2.1 复用的红黑树框架及Insert 2.2 iterator的实现 2.2.1 iterator的核心源码 2.2.2 iterator的实现思路 2.3 map支持[ ] 2.4 map和set的代码实现 2.4.1 MyMap.h 2.4.2 MySet.h 2.4.3 RBTree.h 2.4.4 Test.cpp 1、源码及…...
python基础-02-列表+序列数据类型
文章目录 【README】【4】python列表【4.1】列表数据类型【4.1.1】用索引取得列表中的单个值【4.1.2】负数索引【4.1.3】利用切片获取子列表【4.1.4】用索引改变列表中的值【4.1.5】列表连接与复制【4.1.6】del语句删除列表中的元素 【4.2】使用列表【4.2.1】列表用于循环【补充…...
‘闭包‘, ‘装饰器‘及其应用场景
‘闭包’, 装饰器’及其应用场景 一, 闭包及其应用场景 图解 闭包的定义 概述: 内部函数 使用了 外部函数 的变量, 这种写法就称之为闭包. 格式: def 外部函数名(形参列表):外部函数的(局部)变量def 内部函数名(形参列表):内部函数的(局部)变量return 内部函数名前提条件: …...
IDEA 快捷键ctrl+shift+f 无法全局搜索内容的问题及解决办法
本篇文章主要讲解IDEA、phpStrom、webStrom、pyCharm等jetbrains系列编辑器无法进行全局搜索内容问题的主要原因及解决办法。 日期:2025年3月22日 作者:任聪聪 现象描述: 1.按下ctrlshiftf 输入法转为了繁体。 2.快捷键ctrlshiftr 可以全局检…...
Java——Random库
一、作用 Random库——生成随机数 二、实现步骤 1.导包:import java.util.Random; #快捷键:“Random”回车键 2.取得随机数:Random 变量1 new Random(); 3.调用随机数:类型 变量2 变量1.nextInt(n); (代表变量…...
【通过Groovy去热修复线上逻辑】1.执行线上数据修复 2.写工具
1.执行groovy // 实际执行的话, 我们是通过vue提交的 http://localhost:8080/groovy/execute?scriptimport com.example.groovytest.controller.LoginController; LoginController.num251222 还有个技巧: 而执行执行的,则是: 写的工具什么的,想直接使…...
Powershell WSL导出导入ubuntu22.04.5子系统
导出Linux子系统 导出位置在C盘下,根据自己的实际情况更改即可Write-Host "export ubuntu22.04.5" -ForegroundColor Green wsl --export Ubuntu-22.04 c:\Ubuntu-22.04.tar 导入Linux子系统 好处是目录可用在任意磁盘路径,便于迁移不同的设备之间Write-Host &quo…...
【005安卓开发方案调研】之Flutter+Dart技术开发安卓
基于2025年国内移动开发环境现状,结合多份行业分析报告和技术文档,对FlutterDart开发安卓应用的技术成熟度和生态适配性分析如下: 一、技术成熟度评估 1. 跨平台能力达到生产级标准 Flutter的Skia自渲染引擎和Dart的AOT/JIT双编译模式&…...
论文笔记(七十三)Gemini Robotics: Bringing AI into the Physical World
Gemini Robotics: Bringing AI into the Physical World 文章概括1. 引言2. Gemini 2.0的具身推理2.1. 具身推理问答(ERQA)基准测试2.2. Gemini 2.0的具身推理能力2.3. Gemini 2.0支持零样本和少样本机器人控制 3. 使用 Gemini Robotics 执行机器人动作3…...
AI + 医疗 Qwq大模型离线本地应用
通义千问Qwq-32b-FP16可用于社区医院、乡镇卫生院、诊所等小型医疗机构,替代专业合理用药系统,作为药品知识库,实现以下功能: 药品信息智能查询:检索药品的详细说明书、适应症、禁忌症、不良反应及药物相互作用等关键信…...
Vue 3 项目实现国际化指南 i18n
引言 在开发现代 Web 应用时,国际化(Internationalization,简称 i18n)已经成为一个不可或缺的功能。无论是面向全球用户的商业网站,还是需要支持多语言的企业应用,良好的国际化支持都能显著提升用户体验。本…...
元音辅音及其字母组合发音
文章目录 单元音长元音/ɑː//ɔ://u://i://ɜː/// 短元音/ʌ//ɒ//ʊ//ɪ//ə//e/ 双元音/eɪ//aɪ//ɔɪ//ɪə//eə//ʊə//əʊ//aʊ/ 辅音3个鼻辅音m n ŋ 5个独立浊辅音w j r l h 20个清浊相对的辅音s zʃ ʒf vθ p bt dk gts dztʃ dʒtr dr 以下是列举的部分字母组合…...
【Vitis AIE】FPGA图像处理 11 双线性插值 Bilinear Interpolation
双线性插值 https://github.com/Xilinx/Vitis-Tutorials/tree/2024.2/AI_Engine_Development/AIE/Design_Tutorials/11-Bilinear_Interpolation 简介 双线性插值是一种使用重复线性插值来插值两个变量函数的方法。它通常用于以下应用: 图像处理和计算机视觉&…...
Linux | 安装 Samba将ubuntu 的存储空间指定为windows 上的一个磁盘
01 安装 samba 文件来实现。比如把我们 ubuntu 的存储空间指定为我们 windows 上的一个磁盘,然后我们在这个磁盘里面创建 .c 文件,进行我们代码的修改和编写,可以安装 samba 文件来实现。 samba 是一种网络共享服务,可以通过网络访问我们指定的文件夹 02 第一步:下…...
一文说清预训练与微调:AI的双重训练法则
什么是预训练? 预训练是大型语言模型训练的第一步。它在资金和计算能力的支持下,通过深入分析大量的文本数据,使模型建立起语言的基本构架。在这一阶段,模型通过学习海量的书籍、文章和网页,识别出语言的语法、句法和…...
solana增加流动性和删除流动性
在 Solana 区块链上增加和删除流动性通常通过去中心化交易所(DEX)实现,例如 Raydium 或 Orca。以下是详细的操作流程和注意事项: 一、增加流动性 步骤: 1. 连接钱包 使用支持 Solana 的钱包(如 Phantom、…...
996引擎-接口测试:音效测试NPC
996引擎-接口测试:音效测试NPC 参考资料local offset = 1 -- 默认偏移量function main(player, newOffset)offset = newOffset or offset -- 更新偏移量local buttonWidth =...
javabean类,测试类,工具类都是什么?
JavaBean类 用来描述一类事物的类。比如Student、Teacher、Dog、Cat 例如下面的这个就是JavaBean类 package com.hong.static01demo;public class Student {//姓名,年龄,性别private String name;private int age;private String gender;public stati…...
基于C8051F020单片机的液晶显示,LCD1602并口驱动,单片机并口驱动LCD1602
一、前言 LCD1602是一种广泛使用的字符型液晶显示模块,有8根数据线和3根控制线E,RS和R/W,8根数据线与单片机P6连接,3根控制线与使用P1口的P1.4、P1.5、P1.6连接,VO连接了P1.7,通过给P1.7赋值0或1ÿ…...
miniconda安装保姆级教程|win11|深度学习环境配置
一、官网安装miniconda miniconda官网:Miniconda - Anaconda 点击Download按钮 在红框位置输入邮箱并点击submit,下载链接将会发到邮箱中 邮箱中将会收到如图所示邮件,点击下载 选择windows对应的miniconda安装包 miniconda安装包安装完成如…...
