Bootstrap

Android OpenGL(八)转场特效

转场

转场特效网址:https://gl-transitions.com/gallery

介绍

什么是转场特效:从一个镜头转到另一个镜头,让两个不相关的场景可以平滑过渡。
为什么使用转场特效:镜头转场对普通人难度太大,特效转场更容易实现。特效转场应用场景丰富。

原理

  • 每个转场特效就是一个很短的视频
  • 因此它是由多个视频帧组成的
  • 每秒钟播放的帧越多,转场效果越丝滑
  • Android手机一般60帧/s

关键帧

  • 转场特效很多视频帧组成
  • 但是关键帧数量并不多
  • 制作专场特效时,至少要有2个关键帧
  • 其它帧都是由关键帧生成的
    制作专场还有一个progress的概念:插帧的数量由progress决定,假设你想转场特效有120帧,则每次progress增加1/120,很多特效都需要以progress为参数,假设转场特效有120帧,帧率是60帧/s,则转场耗时2s。

视频帧与shader的关系

  • 每次调用onDrawFrame就绘制一帧
  • 绘制帧时,每个像素调用一次fragment shader

重要API

  • smoothstep(e0,e1,x):两个边界:上边界e0,下边界e1,当x小于等于上边界,就是0,大于等于下边界就是1,中间时为平滑值
  • mix(f,b,alhpa):根据alpha值确定,采用f还是b,如果alpha为0,就用f,alpha为1则用b,二者之间就混合到一起,计算一个值
  • step(e,x):与smoothstep类似,但是只有一个边界,如果x小于边界就为0,如果x大于边界就为1
  • distance(p1,p2):计算两点之间的距离

擦除特效

擦除特效算法

在这里插入图片描述

  • 通过progress控制进度
  • 如果当前坐标小于progress
  • 显示第二张图的像素颜色,否则显示第一张图的像素颜色

步骤

  • 加载图片到项目,并在Render中进行加载
  • 创建assets目录,用于存放glsl文件
  • 创建ShaderUtil,用于读取glsl文件
  • 在Render中使用ShaderUtil中的方法
  • 在.glsl中编写shader

ShaderUtil

  • readFileFromAssets,从assets中读glsl文件
  • loadShader ,加载并编译Shader程序
  • createGLProgream,将编译好的shader程序链接起来

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
        }
    }


}

擦除效果的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);
}

vec4 transition(vec2 uv){
    vec4 a=getFromColor(uv);
    vec4 b=getDstColor(uv);
    // x < progress 使用a,否则使用b颜色
    return mix(a, b, step(uv.x, progress));
}
void main(){
    gl_FragColor=transition(vTexCoord);
}

效果

在这里插入图片描述


import android.content.Context
import android.graphics.Bitmap
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.opengl.GLUtils
import android.opengl.Matrix
import android.util.Log
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 GLTransitionRender(val context: Context, val bitmap: Bitmap, val dstBitmap: Bitmap) : GLSurfaceView.Renderer {
    /**
     * 三角形顶点位置
     */
    private val coodData = floatArrayOf(
        // 顶点坐标        纹理坐标
        -1f, 1f, 0.0f, 0f, 0f,   // 左上角
        -1f, -1f, 0.0f, 0f, 1f,  //左下角
        1f, 1.0f, 0.0f, 1f, 0f,   //右上角
        1f, -1f, 0.0f, 1f, 1f  //右下角
    )

    private var translateMatrix = FloatArray(16)
    private var program: Int = 0
    private var positionHandle: Int = -1
    private var texCoordHandle: Int = -1
    private var samplerHandle: Int = -1
    private var dstSamplerHandle: Int = -1
    private var progressHandle: Int = -1
    private var uMatrixHandle: Int = -1


    // vbo
    private var vboId = IntArray(1)

    // 纹理id array
    private var textureIds = IntArray(1)
    private var lutTextureIds = IntArray(1)
    private val FRAME_SIZE = 120
    private var frameIndex = 0


    private lateinit var coordBuffer: FloatBuffer
    private lateinit var byteBuffer: ByteBuffer

    override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
        // 清理缓存
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        // 顶点坐标内存申请
        createFloatBuffer()
        // 创建定点着色程序
        val vertexShader = ShaderUtil.readFileFromAssets("vertex.glsl", context)
        val fragmentShader = ShaderUtil.readFileFromAssets("fragment_wipe.glsl", context)
        program = ShaderUtil.createGlProgram(vertexShader, fragmentShader)
        // 生成VBO
        GLES30.glGenBuffers(1, vboId, 0)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0])
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, byteBuffer.capacity(), byteBuffer, GLES30.GL_STATIC_DRAW)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

        //
        Matrix.setIdentityM(translateMatrix, 0)

        // 将数据传递shader
        positionHandle = GLES30.glGetAttribLocation(program, "aPosition")
        GLES30.glEnableVertexAttribArray(positionHandle)

        texCoordHandle = GLES30.glGetAttribLocation(program, "aTexCoord")
        GLES30.glEnableVertexAttribArray(texCoordHandle)

        uMatrixHandle = GLES30.glGetUniformLocation(program, "uTMatrix")
        samplerHandle = GLES30.glGetUniformLocation(program, "uSampler")
        dstSamplerHandle = GLES30.glGetUniformLocation(program, "uDstSampler")
        progressHandle = GLES30.glGetUniformLocation(program, "progress")


        ShaderUtil.loadTexture(bitmap, textureIds)
        ShaderUtil.loadTexture(dstBitmap, lutTextureIds)
    }

    private fun createFloatBuffer() {
        // 申请物理层空间
        byteBuffer = ByteBuffer.allocateDirect(coodData.size * 4).apply {
            this.order(ByteOrder.nativeOrder())
        }
        // 坐标数据转换
        coordBuffer = byteBuffer.asFloatBuffer()
        coordBuffer.put(coodData, 0, coodData.size)
        coordBuffer.position(0)
    }

    override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
        // 计算并设置窗口大小
        val imgRatio = bitmap.width.toFloat().div(bitmap.height)
        val windowRatio = width.toFloat().div(height)
        GLES30.glViewport(0, 0, width, height)
        // 1. 矩阵数组
        // 2. 结果矩阵起始的偏移量
        // 3. left:x的最小值
        // 4. right:x的最大值
        // 5. bottom:y的最小值
        // 6. top:y的最大值
        // 7. near:z的最小值
        // 8. far:z的最大值
        // 由于是正交矩阵,所以偏移量为0,near 和 far 也不起作用
        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);
        }
    }

    override fun onDrawFrame(p0: GL10?) {
        if (program <= 0) {
            return
        }
        GLES30.glUseProgram(program)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0])
        program?.let {
            GLES30.glVertexAttribPointer(positionHandle, 3, GLES30.GL_FLOAT, false, 5 * Float.SIZE_BYTES, 0)
            GLES30.glVertexAttribPointer(texCoordHandle,
                2,
                GLES30.GL_FLOAT,
                false,
                5 * Float.SIZE_BYTES,
                3 * Float.SIZE_BYTES)

            GLES30.glUniformMatrix4fv(uMatrixHandle, 1, false, translateMatrix, 0)

            // 激活纹理单元
            GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
            // 绑定纹理单元
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0])
            // 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE0,因此传递0
            GLES30.glUniform1i(samplerHandle, 0)
            if (dstSamplerHandle > 0) {
                // 激活纹理单元
                GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
                // 绑定纹理单元
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, lutTextureIds[0])
                // 第二个参数传递激活的纹理单元,因为激活的是GLES30.GL_TEXTURE1,因此传递1
                GLES30.glUniform1i(dstSamplerHandle, 1)
            }
            val progress = (frameIndex++ % FRAME_SIZE) * 1.0 / FRAME_SIZE
            if (progressHandle > 0) {
                GLES30.glUniform1f(progressHandle, progress.toFloat())
            }
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
            GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
        }

    }
}

activity


class OpenGlTransitionActivity : AppCompatActivity() {
    private var glSurfaceView: GLSurfaceView? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_opengl_img_demo)
        glSurfaceView = findViewById(R.id.glSurfaceView_img)
        glSurfaceView?.setEGLContextClientVersion(3)
        glSurfaceView?.setRenderer(GLTransitionRender(this,
            BitmapFactory.decodeResource(resources, R.drawable.flower),
            BitmapFactory.decodeResource(resources, R.drawable.maomi)))
        glSurfaceView?.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
    }
}

其他专场效果实践

https://gl-transitions.com/gallery](https://gl-transitions.com/gallery
根据转场效果,实践了下其他效果
在这里插入图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;