#### 什么是OpenGL?

openGL是一个图形绘制专业编程接口，功能比较强大，可以绘制二维，三维，它与硬件没有关系，也可以在不同的平台上使用，

进行良好的移植，使用较为广泛

> 安装 glsl 插件

> 分析OpenGL坐标系和android坐标系

![img](20181107182411382.jpg)

>
> android坐标系
>

![img](20181107182411382.jpg)

android的坐标点要用opengl的方式来显示就要换算成opengl的方式，就像是2长图片的重合坐标上的修改，下面直接撸代码




始之前，我们需要建立2个文件，

> 一个是顶点着色器，
>
> 一个是片源着色器

​		顶点着色器是处理顶点、法线等数据，片元着色器是处理光、阴影、遮挡、环境等等对物体表面的影响，最终生成一副图像

在res下面建立一个文件，camera.vert顶点 camera_planyuan.frag片元

下面介绍一下数据类型：float    浮点型

    vec2   含两个浮点型数据的向量
    
    vec4  含四个浮点型数据的向量(xyzw,rgba,stpq)
    
    sampler2D  2D纹理采样器(代表一层纹理)
    
    内置函数exture2D (采样器,坐标)  采样指定位置的纹理
    
    内置变量 顶点   gl_Position      vec4 顶点位置
    
    片元  gl_FragColor   vec4 颜色

需求

使用OpenGL显示摄像头，分析一下需求，其实也就是两步，第一步采集摄像头数据，第二步将摄像头数据显示到屏幕上

采集摄像头数据

使用Android的Camera就可以实现采集

将摄像头数据显示到屏幕上

这里显示到屏幕上，其实有很多方法，比如ANative_window等等，但是这个是使用OpenGL实现，使用OpenGL怎么实现呢，在上一篇博客中说到了OpenGL的绘制流程，基本就是按照那个流程进行实现的。

下面是项目结构中一个不太规范的类图，下面根据这种图来实现具体的代码



![img](https:////upload-images.jianshu.io/upload_images/14188537-90a7456ff92e96e1.png?imageMogr2/auto-orient/strip|imageView2/2/w/1004/format/webp)

简单点说，我们都知道SurfaceView实质是将底层显存Surface显示到界面上，而GLSurfaceView实际就是在这个基础之上增加了OpenGL环境，DouyinView实际就相当于一块画布，而ScreenFilter中是封装了如何使用画笔去画当前摄像头采集到的内容，而DouyinRender渲染器，就是将ScreenFilter中的画渲染到画布上。

创建工程

创建一个JNI工程，就是在一开始创建项目的时候，勾选上 Include C++ support,这样就创建好了



![img](https:////upload-images.jianshu.io/upload_images/14188537-4ee7c8e460dad73c.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)

配置文件 AndroidManifest.xml



![img](https:////upload-images.jianshu.io/upload_images/14188537-5be77f74565cf783.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)

OpenGL的使用还需要有设备制造商提供支持，以下是设备的支持情况，项目里都是用来2.0

OpenGL ES 1.0 和 1.1 ：Android 1.0和更高的版本支持这个API规范。

OpenGL ES 2.0 ：Android 2.2(API 8)和更高的版本支持这个API规范。

OpenGL ES 3.0 ：Android 4.3(API 18)和更高的版本支持这个API规范。

OpenGL ES 3.1 : Android 5.0(API 21)和更高的版本支持这个API规范。

DouyinView.java

![img](https:////upload-images.jianshu.io/upload_images/14188537-8667aa35d589772b.png?imageMogr2/auto-orient/strip|imageView2/2/w/1126/format/webp)

DouyinRender.java



![img](https:////upload-images.jianshu.io/upload_images/14188537-ad31f3815e950f85.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)

这里使用的Render,是实现了OpenGL配置好EGL的渲染器，后面会自己来配置，这里先使用这个，上面也有提到过，OpenGL的操作都是在GLThread线程中完成，所以这里的onSurfaceCreated，onSurfaceChanged，onDrawFrame都是在GLThread中实现的，下面单独看看每个方法的使用



![img](https:////upload-images.jianshu.io/upload_images/14188537-f3b90cbddd0961fe.png?imageMogr2/auto-orient/strip|imageView2/2/w/1180/format/webp)

CameraHelper是封装了对Camera的一系列操作



![img](https:////upload-images.jianshu.io/upload_images/14188537-ed967ff034c39025.png?imageMogr2/auto-orient/strip|imageView2/2/w/1072/format/webp)



![img](https:////upload-images.jianshu.io/upload_images/14188537-bac5a6f0131ec5c3.png?imageMogr2/auto-orient/strip|imageView2/2/w/1058/format/webp)

# **创建着色器** 

AS里面有个插件可以支持GLSL的高亮显示，在plugin里面搜索 GLSL Support

顶点着色器



![img](https:////upload-images.jianshu.io/upload_images/14188537-e600da8be9e3898d.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)

片元着色器



![img](https:////upload-images.jianshu.io/upload_images/14188537-59c1328cfbc7b3d1.png?imageMogr2/auto-orient/strip|imageView2/2/w/1108/format/webp)

这里我们需要画的是矩形，也就是两个三角形，给了4个点的坐标，这4个点，会执行4次顶点着色器，依次将顶点传递给gl_Position，当4个点都传递完成之后，会进行光栅化，将一个矩形变换成一个个的片元，然后再去使用gl_FragColor去着色

因为SurfaceTexture是Android中的，并不是OpenGL的，所以需要使用额外扩展的采样器samplerExternalOES，而不是普通的sample2D采样器，在使用samplerExternalOES时候，需要再添加一句：#extension GL_OES_EGL_image_external : require

 



### 3    滤镜

滤镜实际上就是在片元着色器对特定的像素点进行处理。 **灰度滤镜** 灰度滤镜原理：

> 1. 浮点算法：Gray = R * 0.3 + G * 0.59 + B * 0.11
> 2. 整数算法：Gray = (R*30 + G*59 + B*11) / 100
> 3. 移位算法：Gray = (R*76 + G*151 + B*28)>>8
> 4. 平均值法：Gray = (R+G+B)/3
> 5. 仅取绿色：Gray = G

**思路：**

> 1. 获取原始图片的纹素值
> 2. 选择合适的灰度算法
> 3. 将计算所得赋给gl_FragColor

**实例代码**

1. 顶点着色器（滤镜并不需要操作顶点着色器，所以不同滤镜顶点着色器代码一致）：

```
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords;
}
```

1. 片元着色器

```
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
}
```

权值法（各个颜色的权值参考了GPUImage ，也可以用上面的权值）

```
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    float luminance = dot(mask.rgb, W);
    gl_FragColor = vec4(vec3(luminance), 1.0);
}
```

平均值法：

```
1.   float color = (mask.r + mask.g + mask.b) / 3.0;
2.   vec4 tempColor = vec4(color,color,color,1);
3.   gl_FragColor = vec4(vec3(tempColor), 1.0)
```



​	**一般由于人眼对不同颜色的敏感度不一样，所以三种颜色值的权重不一样，一般来说绿色最高，红色其次，蓝色最低，最合理的取值分别为Wr ＝ 30％，Wg ＝ 59％，Wb ＝ 11％，所以权值法相对效果更好一点。**

**暖色滤镜** 思路：

1. 获取原始图片的纹素值
2. 将rgb通道的颜色添加相应的红/绿色值，再作为rgb通道的值，
3. 将计算所得赋给gl_FragColor

```
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = mask + vec4(0.3,0.3,0.0,0.0);
```

**冷色滤镜** 思路：

1. 获取原始图片的纹素值
2. 将rgb通道的颜色添加相应的蓝色值，再作为rgb通道的值，
3. 将计算所得赋给gl_FragColor

```
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = mask + vec4(0.0,0.0,0.3,0.0);
```









**颠倒滤镜** 思路：

1. 获取原始图片的纹素值
2. 将纹理坐标做映射 （y -> 1 - y）
3. 将计算所得赋给gl_FragColor

```
vec4 color = texture2D(Texture, vec2(TextureCoordsVarying.x, 1.0 - TextureCoordsVarying.y));
```









用GL ES画一个三角形从程序的角度需要以下几个步骤。
\1. 写一个fragment Shader和vertex shader
\2. 创建一个glProgram，链接两个shader。
\3. 设置viewport
\4. 设置framebuffer和renderbuffer用来显示三角形
\5. 传递顶点坐标
\6. 绘制

如果把所有的都详细介绍一遍的话，估计没有几万字是说不清楚的。这里我们只做5，6步。前面4步我已经写好了放在github上。

**第一个问题, 如何向GPU传递顶点信息**

顶点信息是什么?
简单来说，为了绘制三角形，我们必须告诉GL ES三角形的三个点具体是什么。在GL ES中，我们使用(x, y, z)这种形式的数据来表示一个顶点的xyz坐标。

那么放在程序里，我们使用的是

```text
const GLfloat vertices[] = {
        -1, -1, 0,   //左下
        1,  -1, 0,   //右下
        -1, 1,  0};   //左上 
```

这种数据格式来传递顶点信息。典型的C语言的一维数组。每个顶点由3个数值确定坐标。那么3个顶点，刚好是含有9个GLFloat数值的数组了。

那么怎么向GPU传递数据呢？

看一下代码。

```text
glEnableVertexAttribArray(_positionSlot);
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
```

在学习一般计算机语言的过程中，我们已经习惯了赋值就是简单的x = 10这种形式。
但是OpenGL ES不一样的地方在于，OpenGL ES内部是一个巨大的状态机。
至于为什么OpenGL被设置成状态机模式的原因很简单，因为GPU就是这么工作的。
再给大家举一个形象的例子。
清晨起床的你需要去用咖啡机做一杯咖啡。
首先，你需要

> 1. 放入咖啡豆
> 2. 将旋钮转动，选择制作咖啡的杯数
> 3. 灌入水
> 4. 在咖啡机的出水口放置咖啡壶。
>
> 5. 点击制作按钮

也就是说，任何一步出错都会导致你制作咖啡失败。但是，如果你把前4步做的没问题，那么只要按按钮，就肯定可以做出咖啡。

OpenGL可以看做是一个拥有好几百个步骤的咖啡机。但是，这个咖啡机不需要你调配基本的设置就可以做出咖啡，需要你做的仅仅是，配置其中几个步骤，做出不同风味的咖啡。比如，不加奶就是美式，加少量奶就是拿铁，多奶就是卡布奇诺。

那么如何利用OpenGL来绘制呢？

![img](v2-ad65b99a9ebbe4aa4c0b1402b53467b9_720w.jpg)



上面这张图就是OpenGL绘制的基本过程。

这里不详细阐述每个过程的意义了。
需要注意的是，我们需要控制的步骤只有2个。

> 1. 传顶点数据
> 2. 写vertex shader 和fragment shader

我们先看一下vertex shader里面到底是什么东西.

```text
attribute vec4 a_Position;

void main(void) {
    gl_Position = a_Position;

}
```

首先，第一行是一个变量的声明。这里声明的变量的值是从外部传入的。
shader中一共有三种变量，attribute， uniform， varying。

至于3种变量的区别，大家可以看这篇文章。[三种变量的区别](https://link.zhihu.com/?target=http%3A//www.jianshu.com/p/989cad48e01a)

下面是一个形如C语言的main函数。但是不同的地方在于，这个main函数里面有一个莫名其妙的赋值，是把我们的外部变量a_Position赋值给一个叫做gl_Position的东西。

其实，就像之前所说的，OpenGL是一个状态机，那么gl_Position可以理解为OpenGL的其中一个状态，这里你就是把OpenGL的一个状态确定了。

既然vertex shader又叫做顶点着色器，所以很理所当然的，这里的gl_Position就是代表我们需要处理的顶点。也就是说，这里gl_Position接受的数据就是我们之前传入的vertices数组。

几个问题。
1.vec4是什么东西？
vec4你可以类比为swift中的元组。他是一组包含了4个参数的结构。比如，一个RGBA的颜色用4个0-1的float类型表示一个颜色。刚好用vec4就可以表示一个RGBA颜色。比如vec4(1.0, 0.0, 0.0, 1.0)就表示一个透明度为1.0的红色。

2.我们的最终绘制结果是一个三角形，当然，三角形只需要3个点就可以绘制，但是，当这个三角形放到屏幕上的时候，这个三角形区域就包含了无数个像素，这个vertex shader到底是处理三个点，还是区域内的所有点？

那么在这里解释一下。**vertex shader，只对传入的顶点操作。比例如，你绘一个三角形，只传入三个点顶点数据。vertex shader只会对这个三点进行操作，而且是并行计算的
至于点所包围的区域。这个是vertex shader之后有个格栅化(就是上图中的Rasterization)的操作。就是计算你在缓冲区占了多少个像素点。然后在frament shader里利用插值计算每个像素的颜色。**

再来看看frament shader。

```text
precision mediump float;

void main(void) {
   gl_FragColor = vec4(1.0, 0.0, 0.0 , 1.0);

}
```

第一句话是指明float类型的精度。
大家可能会很奇怪。在普通的语言中，float类型有一个固定的取值范围。
但是在glsl中，shader将float类型分为三种精度，lowp, mediump, highp.分别表示着，低精度，中精度，高精度。越高的精度耗费的资源和时间越多。我们需要在shader中指明我们需要的精度。这里使用mediump就可以了。

下面也是个类似于vertex shader中的main函数。
区别在于，这里返回的是gl_FragColor而不是gl_Position.因为顶点位置的处理我们已经在veretx shader中做完了。所以这里只需要处理三角形区域内的每个像素的颜色是多少。这里我们赋值vec4(1.0, 0.0, 0.0, 1.0);代表每个像素点的颜色都是红色。