OpenGL ES ->乒乓緩沖,計算只用兩個幀緩沖對象(Frame Buffer Object)+疊加多個濾鏡作用后的Bitmap

乒乓緩沖核心思想

  • 不使用乒乓緩沖,如果要每個濾鏡作用下的繪制內容,也就是這個濾鏡作用下的幀緩沖,需要創建一個Frame Buffer Object加上對應的Frame Buffer Object Texture
  • 使用乒乓緩沖,只用兩個Frame Buffer Object加上對應的Frame Buffer Object TextureOpen 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}}
}

效果圖

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73053.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73053.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73053.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【HarmonyOS NEXT】關鍵資產存儲開發案例

在 iOS 開發中 Keychain 是一個非常安全的存儲系統&#xff0c;用于保存敏感信息&#xff0c;如密碼、證書、密鑰等。與文件系統不同&#xff0c;Keychain 提供了更高的安全性&#xff0c;因為它對數據進行了加密&#xff0c;并且只有經過授權的應用程序才能訪問存儲的數據。那…

ccfcsp1901線性分類器

//線性分類器 #include<iostream> using namespace std; int main(){int n,m;cin>>n>>m;int x[1000],y[1000];char z[1000];for(int i0;i<n;i){cin>>x[i]>>y[i];cin>>z[i];}int a[20],b[20],c[20];for(int i0;i<m;i){cin>>a[i…

Spring Boot 整合 OpenFeign 教程

精心整理了最新的面試資料和簡歷模板&#xff0c;有需要的可以自行獲取 點擊前往百度網盤獲取 點擊前往夸克網盤獲取 Spring Boot 整合 OpenFeign 教程 一、OpenFeign 簡介 OpenFeign 是 Netflix 開源的聲明式 HTTP 客戶端&#xff0c;通過接口和注解簡化服務間 HTTP 調用。…

APM 仿真遙控指南

地面站開發了一段時間了&#xff0c;由于沒有硬件&#xff0c;所以一直在 APM 模擬器中驗證。我們已經實現了 MAVLink 消息接收和解析&#xff0c;顯示無人機狀態&#xff0c;給無人機發送消息&#xff0c;實現一鍵起飛&#xff0c;飛往指定地點&#xff0c;降落&#xff0c;返…

C語言入門教程100講(4)輸入輸出

文章目錄 1. 什么是輸入輸出&#xff1f;2. 標準輸入輸出函數2.1 printf 函數2.2 scanf 函數 3. 格式化占位符4. 示例代碼代碼解析&#xff1a;輸出結果&#xff1a; 5. 常見問題問題 1&#xff1a;scanf 中的 & 是什么作用&#xff1f;問題 2&#xff1a;printf 和 scanf …

《信息系統安全》(第一次上機實驗報告)

實驗一 &#xff1a;網絡協議分析工具Wireshark 一 實驗目的 學習使用網絡協議分析工具Wireshark的方法&#xff0c;并用它來分析一些協議。 二實驗原理 TCP/IP協議族中網絡層、傳輸層、應用層相關重要協議原理。網絡協議分析工具Wireshark的工作原理和基本使用規則。 三 實…

城市街拍人像自拍電影風格Lr調色教程,手機濾鏡PS+Lightroom預設下載!

調色教程 城市街拍人像自拍的電影風格 Lr 調色&#xff0c;是利用 Adobe Lightroom 軟件&#xff0c;對在城市街景中拍攝的人像自拍照片進行后期處理&#xff0c;使其呈現出電影畫面般獨特的視覺質感與藝術氛圍。通過一系列調色操作&#xff0c;改變照片的色彩、明暗、對比等元…

自學Python創建強大AI:從入門到實現DeepSeek級別的AI

人工智能&#xff08;AI&#xff09;是當今科技領域最熱門的方向之一&#xff0c;而Python是AI開發的首選語言。無論是機器學習、深度學習還是自然語言處理&#xff0c;Python都提供了豐富的庫和工具。如果你夢想創建一個像DeepSeek這樣強大的AI系統&#xff0c;本文將為你提供…

Qt/C++項目積累:4.遠程升級工具 - 4.1 項目設想

背景&#xff1a; 桌面程序一般都支持遠程升級&#xff0c;也是比較常用的場景設計。如酷狗音樂的升級&#xff0c;會提供兩個選項&#xff0c;自動幫助安裝或是新版本提醒&#xff0c;由用戶來決定是否升級&#xff0c;都屬于遠程升級的應用及策略。 看看經過這塊的功能了解及…

(一)丶Windows安裝RabbitMQ可能會遇到的問題

一丶可能會忘了配置ERLang的環境變量 二丶執行命令時報錯 第一步 rabbitmq-plugins enable rabbitmq_management 第二部 rabbitmqctl status 三丶修改.erlang.cookie 文件 1.找到C盤目下的.erlang.cookie文件 C:\Users\admin\.erlang.cookie C:\Windows\System32\config\sys…

Amdahl 定律

Amdahl 定律是用來表示&#xff0c;當提高系統某部分性能時對整個系統的影響&#xff0c;其公式如下&#xff1a; a表示我們提升部分初始耗時比例&#xff0c;k是我們的提升倍率&#xff0c;通過這個公式我們可以輕松的得知對每一部分的提醒&#xff0c;對整個系統帶來的影響…

HW華為流程管理體系精髓提煉華為流程運營體系(124頁PPT)(文末有下載方式)

資料解讀&#xff1a;HW華為流程管理體系精髓提煉華為流程運營體系&#xff08;124頁PPT&#xff09; 詳細資料請看本解讀文章的最后內容。 華為作為全球領先的科技公司&#xff0c;其流程管理體系的構建與運營是其成功的關鍵之一。本文將從華為流程管理體系的核心理念、構建…

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…

【Attention】SKAttention

SKAttention選擇核注意力 標題&#xff1a;SKAttention 期刊&#xff1a;IEEE2019 代碼&#xff1a; https://github.com/implus/SKNet 簡介&#xff1a; 動機:增大感受野來提升性能、多尺度信息聚合方式解決的問題&#xff1a;自適應調整感受野大小創新性:提出選擇性內核…

解決Popwindow寬高的問題。

問題 在使用Popwindow進行自定義的過程中&#xff0c;需要設置popwindow的寬高。但是寬高很多時候容易出問題。比如下面的例子。 布局文件如下 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.andr…

MySQL數據庫精研之旅第二期:庫操作的深度探索

專欄&#xff1a;MySQL數據庫成長記 個人主頁&#xff1a;手握風云 目錄 一、查看數據庫 二、創建數據庫 2.1. 語法 2.2. 示例 三、字符集編碼和校驗(排序)規則 3.1. 查看數據庫支持的字符集編碼 3.2. 查看數據庫支持的排序規則 3.3. 不同的字串集與排序規則對數據庫的…

基于deepseek的智能語音客服【第四講】封裝milvus數據庫連接池封裝

通過工廠模式創建鏈接 static {// 創建連接池工廠BasePooledObjectFactory<MilvusServiceClient> factory new BasePooledObjectFactory<MilvusServiceClient>() {Overridepublic MilvusServiceClient create() throws Exception {return new MilvusServiceClient…

STM32基礎教程——定時器

前言 TIM定時器&#xff08;Timer&#xff09;:STM32的TIM定時器是一種功能強大的外設模塊&#xff0c;通過時基單元&#xff08;包含預分頻器、計數器和自動重載寄存器&#xff09;實現精準定時和計數功能。其核心原理是&#xff1a;內部時鐘&#xff08;CK_INT&#xff09;或…

OpenCV旋轉估計(4)生成一個字符串表示的匹配圖函數 matchesGraphAsString()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 matchesGraphAsString 函數是OpenCV庫中的一部分&#xff0c;位于 cv::detail 命名空間下。這個函數的主要作用是生成一個字符串表示的匹配圖&am…