opengl
中是通过三个点确定一个面进行着色,总体思路为确定顶点着色器
》形成矢量图形
》进行栅格化
==》最后再通过片元着色器
进行上色的过程
1,先用一个view
继承自GLSurfaceView
也就是mCameraTexure
2, 在构造方法中用setEGLContextClientVersion(2)
; 选用2版本,2版本稳定一些
3, opengl的渲染方式,手动和自动两种,自动的话直接在构造方法中调用requestRender()
;
4, 接着初始化CameraX
(此处省略)
5,回调接口onUpdate
,渲染在哪里
6,我们要通过实现Preview
的OnPreviewOutputUpdateListener
接口,从onUpdate
的PreviewOutput
参数中拿到CameraX
的预览图像,我们从output
中通过output.getSurfaceTexture()
方法拿到surfaceTexure
7, 我们还要通过实现GLSurfaceView.Render
接口,并在构造方法中设置setRender
监听,一旦设置监听后,他就会回调onSurfaceCreated
方法
8,此时需要设置一个int
值的texture
,可以理解为在GPU
所在的地方
9,接着在onSurfaceCreated
方法中进行绑定,需用这个mCameraTexture
的attachToGLContext(texture)
将这个int值传进去,这时就绑定了数据
10,这时候我们给这个mCameraTexture
设置setFrameAvailableListener(this)
进行渲染,我们需要先实现SurfaceTexture
的onFrameAvailableListener
这个接口
11,摄像头如果有数据,就会回调onFrameAvailable
这个方法
12,这个数据就会在GPU
没有在CPU
13, 然后我们在onFrameAvailable
中调用requestRender
方法进行手动渲染,等于摄像头一有数据,它一回调这个方法我们就会手动渲染一次
14,然后就会回调我们写的这个自定义控件中的onDraw
方法,也就是onDrawFrame
方法
15,到现在我们的Camera
获取的数据还在Texture
,还没有到我们的CameraView
上,接下来我们需要绑定GPU
,确定形状,颜色
16,我们需要通过mCameraTexture
的updateTextureImage()
方法获取最新的数据,到GPU
的内存缓冲区。
17,我们需要进行一层转化,将世界坐标系转换为安卓坐标系,因为在opengl
中是三个点确定一个面,确定一个矩形就需要四个点的一个矩阵,前面三个是一个面,后面三个是一个面,两个三角形拼成一个矩形,这个就是VERTEX
形状
18,这时候需要进行opengl
程序了,我们需要先新建一个文件夹raw
,写opengl
程序,新建一个Camera_vert.glsl
文件,这是一个程序,无论片元程序还是顶点程序都有main
函数,这个是片元程序
19,再新建一个Camera_vert.glsl
这个是顶点程序,在opengl
运行着两个程序,一个是片元程序,一个是顶点程序,顶点的作用是确定形状,这个形状已经在CPU
中定义好了,接下来我们需要将CPU
中定义好的形状传值过来,所以需要写一个接收变量
20,首先我们要声明一个GPU
变量,
attribute vec4 vPosition;
4代表有4个坐标
21,在main
方法中对
gl_position = vPosition;
进行赋值,一旦赋值后形状便确认了
22,在这里接收坐标系的是世界坐标系,但是我们需要把它绘制到我们的控件,也就是纹理坐标系中
23,这时我们需要定义纹理坐标系,也是
attribute vec4 vCoord;
24, 接着我们需要定义采样器,将世界坐标系的值传递给纹理坐标系
varying vec2 aCoord;
aCoord = vCoord.xy;
25, 一旦定义varing
,它会把我们的顶点坐标系丢到片元坐标系,所以我们在Camera_frag.glsl
片元程序中需要声明一个一样的名字,
varying vec2 aCoord;
名字一定要一模一样,它就会传递,不一样的话就不传递了
26,这时我们需要加载这个opengl
程序,在Java
方法中
String vertexShader = readRawTextFile(context, R.raw.camera_vert);
//返回的String vertexShader是它的路径
27,接下来我们要创建一个程序
//顶点程序
GLES20.glCreatShader(GLES20.GL_VERTEX_SHADER);
//片元程序
GLES20.glCreatShader(GLES20.GL_FRAGMENT_SHADER);
顶点程序和片元程序创建大体上是一样的,只有这里参数上有些差别
28, 创建完顶点程序会返回一个int
值,这个int
值vShader
是程序的地址
29,我们需要关联对应的代码
GLES20.glShaderSource(vShader, vertexShader);
30,接下来编译
//调用这个方法这里GPU会帮你去编译
GLES20.glShaderSource(vShader);
31, 如果编译成功我们需要用一个int[]数组去获取状态
//入参出参对象
int[] status = new int[1];
//获取参数
GLES20.glGetShader(vShader, GLES20.GLCOMPILE_STATUS, status, 0);
//查看配置,是否成功
接下来我们通过status[0]
是否等于GLES20.GL_TRUE
来判断是否成功
32,然后我们加载片元程序,片元程序和顶点程序流程一样,唯一不同就是两个地址和参数有些不一样
33,两个都执行完成我们需要创建一个总程序
int program = GLES20.glCreatProgram();
它会返回一个int
值program
,这个int
值对于CPU
来说没有意义,但是对GPU
却有意义
34,总程序添加两个程序
//加载顶点程序
GLES20.glAttachShader(program, vShader);
//加载片元程序
GLES20.glAttachShader(program, fShader);
这个总程序就相当于一个exe
功能
35,最后我们把整个程序激活
GLES20.glLinkProgram(program);
36, 这个时候我们的自定义控件CameraView
和我们的Camera
的Texture
还是没有关系,我们需要将CPU
的世界坐标系矩阵,纹理坐标系矩阵传递给GPU
37,需要在Java
层用ByteBuffer.allocateDirect
,这个方法是通过allocateDirect
创建CPU
和GPU
之间的一个通道
Buffer vertexBuffer = ByteBuffer.allocateDirect(4*2*4).order(Byteorder.nativeOrder()).asFloatBuffer();
这时会返回一个Buffer对象,vertexBuffer,先把他清空
vertexBuffer.clear();
再把坐标点放进去
vertexBuffer.put(VERTEX);
38, 这时我们写一个onDraw
方法,传入宽高,每次有数据我们就渲染一次,因此在onDrawFrame
中去调用onDraw
39, 在onDraw
方法中,需要告诉GPU
范围,调用GLES20.glViewport()
并传入宽高
40,调用program
程序,
GLES20.glUSEProgram(program);
41,定位到GPU
的地址
int vPostion = GLES20.glGetAttribLocation(program, "vPostion");
它会返回一个int
值vPosition
,地址值
42,将CPU
的数据传递给GPU
`GLES20.glVertxAttribPointer()`
要传入6个参数,主要要传入的参数是这个vPositon
的地址值,和刚才通道的哪个返回值Buffer
也就是vetexBuffer
,这样就是把vetexBuffer
传递到GPU
里去了,这个GPU
里的vPositon
就有值了
43.接下来调用
GLES20.glEnableVertexAttibArray(vPosition);
代表传完了,告诉GPU现在启动这个通道
44,接着传递纹理坐标系,与顶点坐标系一致,开启vCoord变量
GLES20.glEnableVertexAttibArray(vCoord);
45, 这样在GPU
中我们两个值都传递了,一个是vPostion
,一个是vCoord
46,我们还要在onDraw
方法中每次渲染都需要将vertexBuffer
与textureBuffer
放在0位
47,在onSurfaceCreat
中,将CameraView
绑定到Texture
mCameraTexture.attachToGLContext(texture);
这样我们的自定义CameraView
就与texture
绑定了
48, 这时我们需要激活一个图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
一共有32个图层,我们随便使用某一个都可以
49,
//绑定一个采样器和摄像头的内容
GLES20.glBindTexture(GLES20.TEXTURE0, texture);
50, 我们需要在片元程序中定义一个采样器
uniform samplerExternalOES vTexture;
51, 在java
中先定义一个int
值vTexture
,接着定位到片元变量
vTexture = GLES20.glGetUniformLocation(program, "vTexture");
52, 接下来我们将vTexture
传进去
//告诉摄像机在第几个图层,摄像头和第几个图层进行绑定
GLES20.glUniform1f(vTexure, GL_TEXTURE0);
这个vTexture
就拿到片元中的vTexture
53, 在片元程序的main
方法中texture2D()
//自带的采样器,图层采样对应的像素值
vec4 rgba = texture2D(vTexture, aCoord);
//我们得到的采样像素就是rgba
54, 我们给gl_FragColor = rgba
进行赋值,就可以得到我们摄像头捕获的图像
55,最后在java
层通知GPU
进行渲染
GLES20.glDrawArrays(GLES20.GL_TRIANGLE, 0, 4);
//第一个参数代表三个顶点
//第二个0
//第三个代表4个坐标值
56,如果想加滤镜,就在gl_FragColor = rgba
赋值的时候分别对rgba的r g b a值乘以一定的系数进行转换,形成一定的滤镜效果
比方说相加灰色滤镜
float color = (rgba.r + rgba.g + rgba.b) / 3
gl_FragColor = vec4(color, color, color, rgba.a);
57,想要增加分屏效果,就是通过控制采样点坐标达到分屏的效果
texture2D(vTexture, vec2(aCoord.x, aCoord.y));
59, 如果我们得到CameraX
的预览图像时横着的,我们可以在onDrawFrame
方法中
float[] mtx = new float[16];
mCameraTexture.getTransformMatrix(mtx);//得到一个纠正的矩阵
//通过摄像头捕获这个偏移值传进来
GLES20.glUniformMatix4fv(vMatrix, 1, false, mtx);
在顶点程序中来一个矩阵
uniform mat4 vMatrix;
aCoord = (vMaxtrix * vCoord.xy);
这样就可以得到一个纠正的图像了
1,其实就是高斯模糊,就是取每个点的平均值放在周围
2,一般取20个点,上,下 ,左,右各4个,然后里三环外三环,三环的图案20个点累加除以20得到平均值,最后将取到的值赋值到这个坐标点,就得到一个高斯模糊的图像
3,再用高反差,就是清晰图减去一张模糊的图片,保留边界的细节
4,调优。分别对r,g,b三种颜色的图层的高反差图乘以一定的调优系数
5,线性叠加,将蓝图的值取出来,保留边界的细节
6,设置一个磨皮的系数,一般是0~1f,再大会模糊
7,然后两个图原图保留的细节,与一个高斯模糊的图相加
6,再对gl_Fragcolor
进行赋值
gl_Fragcolor = vec4(r, 1.0);