16. 颠倒、灰度、漩涡、马赛克滤镜
本次马赛克滤镜项目框架跟15.分屏滤镜的项目一致。
图片显示原理: CPU:计算视图frame,图片解码,需要绘制纹理图片通过数据总线交给GPU GPU:纹理混合,顶点变换与计算,像素点的填充计算,渲染到帧缓冲区 时钟信号:垂直同步信号V-Sync/水平同步信号H-Sync iOS设备双缓冲机制:显示系统通常会引入两个帧缓冲区,双缓冲机制 图片显示到屏幕上是CPU与GPU协作完成的
正常显示
顶点着色器
attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;
void main()
{
gl_Position = i_position;
o_textureCoord = i_textureCoord;
}
片元着色器
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
gl_FragColor = texture2D(u_sampler, o_textureCoord);
}
颠倒滤镜
纹理坐标翻转即可,推荐顶点着色器去翻转,减少计算量
attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;
void main()
{
gl_Position = i_position;
o_textureCoord = vec2(i_textureCoord.x, 1.0 - i_textureCoord.y);
}
灰度滤镜
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(GLSL里没有位移运算)
4.平均值法:Gray = (R + G + B)/3
5.仅取绿色:Gray = G
其中1、2、3采用的是权重值方式,其效果比较逼真(权重因子的和要等于1);
precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
/// 权重因子
const vec3 w = vec3(0.3, 0.59, 0.11);
void main()
{
vec4 color = texture2D(u_sampler, o_textureCoord);
float average = dot(color.rgb, w);
gl_FragColor = vec4(vec3(average), 1.0);
}
4为平均值法,其效果比较柔和;
precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
vec4 color = texture2D(u_sampler, o_textureCoord);
float average = (color.r + color.g + color.b)/3.0;
gl_FragColor = vec4(vec3(average), 1.0);
}
5只取绿色,仅仅是使用方便不需要进行计算,效果并不好
precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
vec4 color = texture2D(u_sampler, o_textureCoord);
gl_FragColor = vec4(vec3(color.g), 1.0);
}
漩涡滤镜
图像漩涡主要是在某个半径范围里,把当前采样点旋转一定角度,旋转以后当前点的颜色就被旋转后的点的颜色代替,因此整个半径范围里会有旋转的效果。如果旋转的时候旋转角度随着当前点距中心点的距离递减,整个图像就会出现漩涡效果。这里使用抛物线递减因子:(1.0 - (r/Radius)*(r/Radius))
precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
/// 漩涡旋转角度,角度越大漩涡变化越密集
const float angle = 180.0;
/// 漩涡的半径
const float radius = 0.4;
///漩涡的中心点
const vec2 center = vec2(0.5, 0.6);
void main()
{
/// 把传入的[0,1]之间的纹理坐标转化为以漩涡中心点为原点的坐标
vec2 xy = o_textureCoord - center;
/// 获取当前点到中心点距离
float r = length(xy);
/// 判断距离是否大于漩涡半径,如果大于则在漩涡之外
if (r > radius) {
/// 漩涡外的像素不需要调整
gl_FragColor = texture2D(u_sampler, o_textureCoord);
}else {
/// 实现漩涡效果,需要旋转角度随着当前点理半径的距离递减,这个是一个递减的因子
float w = 1.0 - pow(r/radius, 2.0);
/// 根据当前角度和旋转角度,获得旋转后的角度
float new_angle = atan(xy.y, xy.x) + radians(angle) * w;
/// 通过旋转后的角度和距离,得到要取色的纹理坐标
vec2 new_xy = r * vec2(cos(new_angle), sin(new_angle)) + center;
gl_FragColor = texture2D(u_sampler, new_xy);
}
}
正方形马赛克滤镜
正方形马赛克效果就是把图片的一个相当大小的区域用同一个点的颜色来表示。可以认为是大规模的降低图像的分辨率,而让图像的一些细节隐藏起来。
precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
/// 纹理图片的宽高比
uniform float u_aspectRatio;
/// 每块马赛克的宽高
const vec2 mosaicSize = vec2(0.02, 0.02);
void main()
{
/// 判断当前点属于哪块马赛克,然后取当前马赛克中心点的颜色来赋值,mosaicSize.y*u_aspectRatio为真正的y方向马赛克高度
vec2 newPoint = vec2((floor(o_textureCoord.x/mosaicSize.x) + 0.5) * mosaicSize.x, (floor((o_textureCoord.y)/(mosaicSize.y*u_aspectRatio)) + 0.5) * (mosaicSize.y*u_aspectRatio));
gl_FragColor = texture2D(u_sampler, newPoint);
}
六边形马赛克滤镜
就是把图片划分成很多六边形,然后让每个六边形内部的颜色统一(可以直接去六边形中心点像素颜色).
把图片用正六边形划分,然后通过正六边形中心点连线分割,可见分为一个个格子。
把每个格子转化到坐标系中,那么格子分为4类:
格子类型 | 可能的中心点位置 |
---|---|
偶行偶列 | 左上、右下 |
偶行奇列 | 左下、右上 |
奇行偶列 | 左下、右上 |
奇行奇列 | 左上、右下 |
通过上面的分析,正六边形马赛克滤镜的实现就是先找到纹理坐标所处的格子,确认格子的类型之后,再比较当前纹理坐标到格子的2个可能中心点的距离,然后把纹理坐标就替换为距离近的中心点的坐标。
precision highp float;
precision highp int;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
/// 纹理宽高比
uniform float u_aspectRatio;
/// 正六边形边长
const float sideLength = 0.02;
void main()
{
/// 每个格子的实际宽度
float cellWidth = 1.5*sideLength;
/// 每个格子的实际高度
float cellHeight = sin(radians(60.0))*sideLength*u_aspectRatio;
/// 分别以格子宽高为单位,转化当前纹理坐标到格子坐标系
float cellX = o_textureCoord.x/cellWidth;
float cellY = o_textureCoord.y/cellHeight;
/// 格子坐标点向下取整
int pX = int(floor(cellX));
int pY = int(floor(cellY));
// c1、c2为对应格子上的正六边形中心点对应的纹理坐标
vec2 c1, c2, c;
/// 奇偶数判断 int型/2*2,如果是奇数不会等于自己的
if (pX/2*2 == pX) {
if (pY/2*2 == pY) {
c1 = vec2(float(pX) * cellWidth, (float(pY) + 1.0) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY) * cellHeight);
}else {
c1 = vec2(float(pX) * cellWidth, float(pY) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY + 1) * cellHeight);
}
}else {
if (pY/2*2 == pY) {
c1 = vec2(float(pX) * cellWidth, float(pY) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY + 1) * cellHeight);
}else {
c1 = vec2(float(pX) * cellWidth, float(pY + 1) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY) * cellHeight);
}
}
/// 计算当前点纹理坐标 与正六边形中心处纹理坐标的距离,一定要把纹理宽高比计算进去,要保证x、y轴上的单位长度是一致的,这样计算出来的距离才是正确的
float s1 = sqrt(pow(c1.x - o_textureCoord.x, 2.0) + pow((c1.y - o_textureCoord.y)/u_aspectRatio, 2.0));
float s2 = sqrt(pow(c2.x - o_textureCoord.x, 2.0) + pow((c2.y - o_textureCoord.y)/u_aspectRatio, 2.0));
if (s1 > s2) {
c = c2;
}else {
c = c1;
}
/// 当前纹理坐标处颜色,使用距离最近的正六边形中心处纹理坐标的颜色
gl_FragColor = texture2D(u_sampler, c);
}
三角形马赛克滤镜
本次三角形马赛克滤镜是在正六边形滤镜的基础上再处理得到的。我们在上面正六边形滤镜中能够获取到当前点和六边形中心点,所以我们可以得到当前点相对中心点的角度,通过角度把正六边形分为6个三角形,判断当前点在哪个三角形中,然后把三角形中中心点颜色赋给当前点就可以实现三角形滤镜了。
precision highp float;
precision highp int;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
/// 纹理宽高比
uniform float u_aspectRatio;
/// 正六边形边长
const float sideLength = 0.03;
void main()
{
/// 每个格子的实际宽度
float cellWidth = 1.5*sideLength;
/// 每个格子的实际高度
float cellHeight = sin(radians(60.0))*sideLength*u_aspectRatio;
/// 分别以格子宽高为单位,转化当前纹理坐标到格子坐标系
float cellX = o_textureCoord.x/cellWidth;
float cellY = o_textureCoord.y/cellHeight;
int pX = int(floor(cellX));
int pY = int(floor(cellY));
// c1、c2为对应格子上的正六边形中心点对应的纹理坐标
vec2 c1, c2, c;
/// 奇偶数判断 int型/2*2,如果是奇数不会等于自己的
if (pX/2*2 == pX) {
if (pY/2*2 == pY) {
c1 = vec2(float(pX) * cellWidth, (float(pY) + 1.0) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY) * cellHeight);
}else {
c1 = vec2(float(pX) * cellWidth, float(pY) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY + 1) * cellHeight);
}
}else {
if (pY/2*2 == pY) {
c1 = vec2(float(pX) * cellWidth, float(pY) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY + 1) * cellHeight);
}else {
c1 = vec2(float(pX) * cellWidth, float(pY + 1) * cellHeight);
c2 = vec2(float(pX + 1) * cellWidth, float(pY) * cellHeight);
}
}
/// 计算当前点纹理坐标 与正六边形中心处纹理坐标的距离,一定要把纹理宽高比计算进去,要保证x、y轴上的单位长度是一致的,可以理解为基于实际像素值
float s1 = sqrt(pow(c1.x - o_textureCoord.x, 2.0) + pow((c1.y - o_textureCoord.y)/u_aspectRatio, 2.0));
float s2 = sqrt(pow(c2.x - o_textureCoord.x, 2.0) + pow((c2.y - o_textureCoord.y)/u_aspectRatio, 2.0));
if (s1 > s2) {
c = c2;
}else {
c = c1;
}
/// 获得当前点相对中心点角度(基于实际像素值)
float angle = degrees(atan((o_textureCoord.y - c.y)/u_aspectRatio, o_textureCoord.x - c.x));
/// 得到相应三角形中心到c的角度(基于实际像素值)
if (angle >= 0.0 && angle < 60.0) {
angle = 30.0;
}else if (angle >= 60.0 && angle < 120.0) {
angle = 90.0;
}else if (angle >= 120.0 && angle <= 180.0) {
angle = 150.0;
}else if (angle < 0.0 && angle >= -60.0) {
angle = -30.0;
}else if (angle < -60.0 && angle >= -120.0) {
angle = -90.0;
}else if (angle < -120.0 && angle > -180.0) {
angle = -150.0;
}
/// c到三角形中心的距离
float t_r = (sideLength/2.0)/cos(radians(30.0));
/// 三角形中心点([0,1]坐标系下)
vec2 point = vec2(c.x + t_r*cos(radians(angle)), c.y + t_r*sin(radians(angle))*u_aspectRatio);
gl_FragColor = texture2D(u_sampler, point);
}
代码示例见:Github