Android使用OpenGL实现相机滤镜
2022-08-05 本文已影响0人
itfitness
目录
效果展示
滤镜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文件夹下