Android使用OpenGL实现相机滤镜

目录

效果展示

滤镜1

滤镜2

滤镜3

滤镜4

实现步骤

1.继承GLSurfaceView

继承GLSurfaceView用于展示渲染的画面,并实现GLSurfaceView.Renderer接口

public class CameraView extends GLSurfaceView implements GLSurfaceView.Renderer {
    public CameraView(Context context) {
        super(context);
    }

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
     @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        
    }

    @Override
    public void onDrawFrame(final GL10 gl) {
        
    }
}
2.获取相机数据

这里我用的是CameraX
版本是早期的版本,可以只获取相机数据

implementation "androidx.camera:camera-core:1.0.0-alpha05"
implementation "androidx.camera:camera-camera2:1.0.0-alpha05"
 private void initCameraX() {
        PreviewConfig config = new PreviewConfig.Builder()
//                .setTargetResolution(new Size(640,480))
                .setLensFacing(CameraX.LensFacing.BACK)
                .build();
        Preview preview = new Preview(config);
        CameraX.bindToLifecycle((LifecycleOwner) getContext(),preview);
        preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
            @Override
            public void onUpdated(Preview.PreviewOutput output) {
                //拿到输出画布
                surfaceTexture = output.getSurfaceTexture();
            }
        });
    }
3.监听相机数据回调

在onSurfaceCreated方法中通过setOnFrameAvailableListener方法监听相机数据的回调,相机数据回调时,调用requestRender()方法,触发onDrawFrame方法

@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //拿到摄像头在GPU地址
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        surfaceTexture.attachToGLContext(textures);
        surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                //摄像头回调
                //触发 onDrawFrame
                requestRender();
            }
        });
        cameraDrawer = new CameraDrawer();
    }
4.渲染画面

在onDrawFrame函数中对画面进行渲染,渲染的逻辑在CameraDrawer中,基本都是些固定代码

class CameraDrawer {

    /**
     * 顶点着色器代码
     */
    private var vertexShaderCode = ""
    /**
     * 片段着色器代码
     */
    private var fragmentShaderCode = ""

    /**
     * 着色器程序ID引用
     */
    private var mProgram = 0


    // 四边形顶点的坐标
    private var squareCoords = floatArrayOf(
        -1f, 1f, 0.0f,      // top left
        -1f, -1f, 0.0f,      // bottom left
        1f, -1f, 0.0f,      // bottom right
        1f, 1f, 0.0f       // top right
    )

    // 顶点所对应的纹理坐标
    private var textureVertices = floatArrayOf(
        0f, 1f,      // top left
        1f, 1f,      // bottom left
        1f, 0f,       // bottom right
        0f, 0f     // top right
    )

    // 四个顶点的缓冲数组
    private val vertexBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(squareCoords.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply {
                put(squareCoords)
                position(0)
            }

    // 四个顶点的绘制顺序数组
    private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)

    // 四个顶点绘制顺序数组的缓冲数组
    private val drawListBuffer: ShortBuffer =
        ByteBuffer.allocateDirect(drawOrder.size * 2).order(ByteOrder.nativeOrder())
            .asShortBuffer().apply {
                put(drawOrder)
                position(0)
            }

    // 四个顶点的纹理坐标缓冲数组
    private val textureVerticesBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(textureVertices.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply {
                put(textureVertices)
                position(0)
            }

    // 每个顶点的坐标数
    private val COORDS_PER_VERTEX = 3

    // 每个纹理顶点的坐标数
    private val COORDS_PER_TEXTURE_VERTEX = 2
    private val vertexStride: Int = COORDS_PER_VERTEX * 4
    private val textVertexStride: Int = COORDS_PER_TEXTURE_VERTEX * 4

    init {
        fragmentShaderCode = ResourceUtils.readRaw2String(R.raw.camera_frag4)
        vertexShaderCode = ResourceUtils.readRaw2String(R.raw.camera_vert)
        // 编译顶点着色器和片段着色器
        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
        // glCreateProgram函数创建一个着色器程序,并返回新创建程序对象的ID引用
        mProgram = GLES20.glCreateProgram().also {
            // 把顶点着色器添加到程序对象
            GLES20.glAttachShader(it, vertexShader)
            // 把片段着色器添加到程序对象
            GLES20.glAttachShader(it, fragmentShader)
            // 连接并创建一个可执行的OpenGL ES程序对象
            GLES20.glLinkProgram(it)
        }
    }

    fun draw(textureID:Int) {
        // 激活着色器程序 Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram)
        // 获取顶点着色器中的vPosition变量(因为之前已经编译过着色器代码,所以可以从着色器程序中获取);用唯一ID表示
        val position = GLES20.glGetAttribLocation(mProgram, "vPosition")
        // 允许操作顶点对象position
        GLES20.glEnableVertexAttribArray(position)
        // 将顶点数据传递给position指向的vPosition变量;将顶点属性与顶点缓冲对象关联
        GLES20.glVertexAttribPointer(
            position, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
            false, vertexStride, vertexBuffer
        )
        // 激活textureID对应的纹理单元
        GLES20.glActiveTexture(textureID)
        // 绑定纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureID)
        // 获取顶点着色器中的inputTextureCoordinate变量(纹理坐标);用唯一ID表示
        val textureCoordinate = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate")
        // 允许操作纹理坐标inputTextureCoordinate变量
        GLES20.glEnableVertexAttribArray(textureCoordinate)
        // 将纹理坐标数据传递给inputTextureCoordinate变量
        GLES20.glVertexAttribPointer(
            textureCoordinate, COORDS_PER_TEXTURE_VERTEX, GLES20.GL_FLOAT,
            false, textVertexStride, textureVerticesBuffer
        )
        // 按drawListBuffer中指定的顺序绘制四边形
        GLES20.glDrawElements(
            GLES20.GL_TRIANGLE_STRIP, drawOrder.size,
            GLES20.GL_UNSIGNED_SHORT, drawListBuffer
        )
        // 操作完后,取消允许操作顶点对象position
        GLES20.glDisableVertexAttribArray(position)
        GLES20.glDisableVertexAttribArray(textureCoordinate)
    }

    private fun loadShader(type: Int, shaderCode: String): Int {
        // glCreateShader函数创建一个顶点着色器或者片段着色器,并返回新创建着色器的ID引用
        val shader = GLES20.glCreateShader(type)
        // 把着色器和代码关联,然后编译着色器
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
        return shader
    }
}

而渲染的glsl代码我放在了,raw文件夹下


案例源码

https://gitee.com/itfitness/open-gl-demo

你可能感兴趣的:(Android使用OpenGL实现相机滤镜)