本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
经过阅览本文,你将取得以下收成:
1.经过Shader完成滤镜的基本原理
2.怎样完成静态滤镜
3.常见的静态滤镜实例剖析
上篇回忆
上一篇文章一看就懂的OpenGL ES教程——烘托宫崎骏动漫重拾幼年 现已详细阐述了怎样用OpenGL es将原始的YUV数据组成的视频烘托到屏幕上,想必有许多童鞋在阅览了它之后仍然觉得耐人寻味,学习的胃口也越来越大了,由于你们知道仅仅烘托视频是不行的,咱们要的是,能够在视频上面玩出花来,所以,在本系列现已渐入高潮的上一篇文章之后,我将在本文掀起更大的“浪花”,进一步满足列位看官的求知欲。本文将解说运用OpenGL es给视频添加各种放抖音滤镜特效
,这也是我在本系列第一篇文章中许诺过的。
说起滤镜,这是这个年头一个很火很奇特的东西,简直能够说能化腐朽为奇特(化野兽为美女?)都不过火。最初是在photoshop这类图像处理软件中逐渐为世人所知,后来被抖音这类视频软件玩出花来,为世人特别是花季少女所倾慕~
那滤镜的完成原理又是怎样的呢?
滤镜基本原理
其实咱们在本系列之前的文章现已有触摸过这方面的东西,还记得这张图么:
嘻嘻,你又怎样会不记得呢~~ 这图便是在一看就懂的OpenGL ES教程——临摹画手的浪漫之纹路映射(实践篇)终究做图层混合的示例图,还记得咱们怎样完成的么?
回忆一下其时的片段上色器:
#version 300 es
precision mediump float;
in vec2 TexCoord;
out vec4 FragColor;
//传入的纹路
uniform sampler2D ourTexture;
//新增纹路单元
uniform sampler2D ourTexture1;
void main() {
//对2个纹路进行混合
FragColor = mix(texture(ourTexture, TexCoord), texture(ourTexture1, TexCoord), 0.5);
};
经过2个纹路单元去采样2张图片
,对当时片段
采样到的色彩值按照0.5透明度的份额进行混合
,即色彩值1的0.5透明度+色彩值2的0.5透明度。
所以咱们能够得出结论,滤镜的完成,便是在片段上色器采样的时分,咱们不再老老实实将采样得到的色彩值直接赋给当时片段的终究色彩值,而是添加一些额定的处理,终究再赋给当时片段的终究色彩值。
至于什么样的处理,那就需求好好发挥咱们的想象力和算法功底了。
下面我将用几个代表性的初级视频滤镜
实例来解说,由于学习都是一个按部就班的过程,所以对滤镜完成的详细解说,我也将分为从易到难的等级逐渐解说。
值得注意的是,所用视频依旧是大家最喜爱的《龙猫》
哦~所以代码悉数基于上一篇的代码,改的主要是片段上色部分。
滤镜晋级打怪
青铜
首先是青铜等级,即最简略的等级,也便是对每个片段都是一样的处理方式
。
灰度滤镜
望文生义,便是将每一帧画面转化为灰度图。灰度便是没有色彩,关于RGB来说,即色彩重量悉数持平,关于YUV来说,即UV重量都为默认值128。
这一点从RGB和YUV的互相转化公式就能推出来,以下为BT601的Limited Range的转化公式:
关于RGB转YUV的公式来说,假设RGB三个重量都持平,那么能够算出此刻Y等于RGB中恣意一个重量的值,而U、V都是128。
而关于YUV转RGB的公式来说,当YUV的U、V都为128的时分,R、G、B都等于Y。
基于此,片段上色器代码就不难写了。
第一种是最简略的,便是直接将采样到的YUV中的UV置为默认值,即无色的值
。
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
//别离取yuv各个重量的采样纹路
yuv.x = texture(yTexture, vTextCoord).r;
//直接将uv置为0.0即可(0.5-0.5)
yuv.y = 0.0;
yuv.z = 0.0;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
FragColor = vec4(rgb, 1.0);
};
由于在OpenGL es内部现已做了归一化,所以采样到的YUV数值是从0.0-1.0的,由于UV每个通道的规模为0至255,因此此刻的无色对应的便是128
(更细致地来说是色度重量在偏置处理前的取值规模是-128至127,这时分的无色对应的是“0”值。经过偏置后色度重量取值变成了0至255,因此此刻的无色对应的便是128了),对应在shader中归一化的数值便是0.5,又由于在矩阵相乘之前先减0.5,所以这里就直接置为0.0即可
。
第二种,运用灰度转化公式
:
gray = R * 0.2125 + G * 0.7154 + B * 0.0721
直接代入,没啥好说的,不要问我公式怎样推导的。
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
//别离取yuv各个重量的采样纹路
yuv.x = texture(yTexture, vTextCoord).r;
//直接将uv置为0.0即可(0.5-0.5)
yuv.y = 0.0;
yuv.z = 0.0;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
float gray = rgb.r * 0.2125 + rgb.g * 0.7154 + rgb.b * 0.0721;
FragColor = vec4(gray,gray,gray, 1.0);
};
几行代码功夫,萌萌的动画片变得特别有时代感~
反色滤镜
所谓的反色,便是其RGB色彩值与其相加和为255的对应色彩值
,将一幅图置为反色,看起来就有种拍X光的作用。常见的反色如下表格所示,左右项互为反色(来源于百度百科):
白色(255,255,255) | 黑色(0,0,0) |
---|---|
灰色-25%(195,195,195) | 灰度-80%(60,60,60) |
褐色(185,122,87) | 深灰蓝绿色(70,133,168) |
玫瑰色(粉红)(255,174,201) | 深绿色(0,81,54) |
金色(255,201,14) | 蓝色(0,54,241) |
浅黄色(239,228,176) | 墨蓝色(16,27,79) |
酸橙色(181,230,29) | 亮蓝色(74,25,226) |
淡青绿色(153,217,234) | 深红褐色(102,38,21) |
蓝灰色(112,146,190) | 咖啡色(143,109,65) |
淡蓝紫色(200,191,231) | 深苔藓色(55,64,24) |
黑色(0,0,0) | 白色(255,255,255) |
---|---|
灰色-50%(127,127,127) | 灰色-50%(127,127,127)(反色便是它本身) |
深红色(136,0,21) | 浅蓝绿色(119,255,234) |
红色(237,28,36) | 蓝绿色(18,227,219) |
橙色(255,127,39) | 暗青蓝色(0,128,216) |
黄色(255,242,0) | 靛蓝色(0,13,255) |
绿色(34,177,76) | 暗玫红色(221,78,179) |
青绿(0,162,232) | 鲜橙色(255,93,23) |
靛青(63,72,204) | 棕黄色(192,183,51) |
紫色(163,73,164) | 草绿色(92,182,91) |
这样片段上色器就很简略了:
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
//别离取yuv各个重量的采样纹路
yuv.x = texture(yTexture, vTextCoord).r;
//直接将uv置为0.0即可(0.5-0.5)
yuv.y = 0.0;
yuv.z = 0.0;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
//取反色
FragColor = vec4(vec3(1.0 - rgb.r, 1.0 - rgb.g, 1.0 - rgb.b), 1.0);
};
只要终究赋值的一行改为以下即可:
FragColor = vec4(vec3(1.0 - rgb.r, 1.0 - rgb.g, 1.0 - rgb.b), 1.0);
一行代码功夫,一切都”反了“。
白银
白银等级难度当然有所提升,主要是不同区域的片段的处理方式不一样
了。
灰度反色穿插滤镜
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
//别离取yuv各个重量的采样纹路(r表示?)
yuv.x = texture(yTexture, vTextCoord).r;
yuv.y = texture(uTexture, vTextCoord).g - 0.5;
yuv.z = texture(vTexture, vTextCoord).b - 0.5;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
//根据不同的纹路坐标区域,赋值不同色彩值给当时当时片段色彩值
if (vTextCoord.x < 0.5 && vTextCoord.y < 0.5) {
//左上角区域,反色滤镜
FragColor = vec4(vec3(1.0 - rgb.r, 1.0 - rgb.g, 1.0 - rgb.b), 1.0);
} else if (vTextCoord.x > 0.5 && vTextCoord.y > 0.5) {
//右下角区域,灰度滤镜
float gray = rgb.r * 0.2125 + rgb.g * 0.7154 + rgb.b * 0.0721;
FragColor = vec4(gray, gray, gray, 1.0);
} else {
FragColor = vec4(rgb, 1.0);
}
};
代码一出来,其实也是so easy~关键点便是关于纹路坐标所在区域的判别
,如果处于左上角,即x<0.5,y<0.5,则运用反色作用。如果处于右下角,即x>0.5,y>0.5,则运用灰度作用。其他区域不做额定处理
。
又是几行代码的功夫,就戴上了”有色眼镜“~
黄金
黄金等级关于刚触摸的童鞋来说或许是一个小门槛,由于这里开始当时片段采样的纹素或许并非是片段本身对应的纹路坐标了,而是根据需求采样自己想要的纹路坐标方位的色彩值
。
二分屏
二分屏,望文生义,行将一个画面分为2个重复的画面在平均分的屏幕方位上烘托。这里的二分屏为了确保图像不变形,所以每个分屏都采样本来纹路图片的中心一半的区域。如下图所示,左边是被烘托的图元,右边是被采样的纹路:
该图显现的是针对片段在第一个分屏的情况,由于之前讲过纹路映射便是相当于将图元的顶点和纹路的顶点逐个对上。
从这个图咱们能够得出一个通用结论,假设当时片段坐标为(x,y),当y小于0.5的时分,则采样纹路图片对应方位为y+0.25的纹素
那么关于下方的分屏,就能够顺藤摸瓜推出以下结论:
当y大于0.5的时分,则采样纹路图片对应方位为y-0.25的纹素。
上片段上色器代码:
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
vec2 uv = vTextCoord.xy;
float y;
//关键点,对烘托图元不同方位的点采样纹路的不同方位
if (uv.y >= 0.0 && uv.y <= 0.5) {
//当烘托图元的点坐落上半部分的时分,采样比其纵坐标大于0.25部分
uv.y = uv.y + 0.25;
}else{
//当烘托图元的点坐落下半部分的时分,采样比其纵坐标小于0.25部分
uv.y = uv.y - 0.25;
}
//别离取yuv各个重量的采样纹路
yuv.x = texture(yTexture, uv).r;
yuv.y = texture(uTexture, uv).g - 0.5;
yuv.z = texture(vTexture, uv).b - 0.5;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
FragColor = vec4(rgb, 1.0);
};
四分屏
四分屏就更好玩了,可是原理和二分屏是一样的。
如下图,左边是被烘托的图元,右边是被采样的纹路,像之前所说的,能够将纹路和图元的顶点逐个对上,比如四分屏的第一个格子和纹路的对应联系如下图所示:
所以关于第一个分屏,假设此刻需求烘托的片段为(x,y),则能够推出采样的通用联系:
当x<0.5,y<0.5的时分,采样(x*2,y*2)的纹素。
那么以此类推,就能够推出:
当x>0.5,y<0.5的时分,采样((x-0.5)*2,y*2)的纹素。
当x<0.5,y>0.5的时分,采样(x*2,(y-0.5)*2)的纹素。
当x>0.5,y>0.5的时分,采样((x-0.5)*2,(y-0.5)*2)的纹素。
片段上色器代码:
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
//采样到的yuv向量数据
vec3 yuv;
//yuv转化得到的rgb向量数据
vec3 rgb;
vec2 uv = vTextCoord.xy;
if (uv.x <= 0.5) {
//当x小于0.5的时分,采样2倍x坐标的纹素色彩
uv.x = uv.x * 2.0;
}else{
//当x大于0.5的时分,采样2倍x坐标减0.5的纹素色彩
uv.x = (uv.x - 0.5) * 2.0;
}
if (uv.y <= 0.5) {
//当y小于0.5的时分,采样2倍y坐标的纹素色彩
uv.y = uv.y * 2.0;
}else{
//当y大于0.5的时分,采样2倍y坐标减0.5的纹素色彩
uv.y = (uv.y - 0.5) * 2.0;
}
//别离取yuv各个重量的采样纹路
yuv.x = texture(yTexture, uv).r;
yuv.y = texture(uTexture, uv).g - 0.5;
yuv.z = texture(vTexture, uv).b - 0.5;
rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.183, 1.816,
1.540, -0.459, 0.0
) * yuv;
FragColor = vec4(rgb, 1.0);
};
这么一看也没什么奇特的,你说呢?
总结
本文详细叙述了几种常见的滤镜作用完成原理,让广阔的程序员也有时机体验做一把画家艺术家的快感,当然段位仅仅进行到了黄金等级未免显得太菜鸡了吧,所以下一篇文章才是真实冲击王者宝座的时机。
欢迎点赞加关注,让咱们一起早日上王者段位~
项目代码
opengl-es-study-demo 不断更新中,欢迎各位来star~
参阅:
OpenGL ES 案例11:分屏滤镜
反色百科
系列文章目录
体系化学习系列博文,请看音视频体系学习的浪漫马车之总目录
实践项目: 介绍一个自己刚出炉的安卓音视频播放录制开源项目
相关专栏:
C/C++根底与进阶之路
音视频理论根底系列专栏
音视频开发实战系列专栏
轻松入门OpenGL系列
一看就懂的OpenGL ES教程——图形烘托管线的那些事
一看就懂的OpenGL ES教程——再谈OpenGL工作机制
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(一)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(二)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(三)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(四)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(五)
一看就懂的OpenGL ES教程——缓冲目标优化程序(一)
一看就懂的OpenGL ES教程——缓冲目标优化程序(二)
一看就懂的OpenGL ES教程——临摹画手的浪漫之纹路映射(理论篇)
一看就懂的OpenGL ES教程——临摹画手的浪漫之纹路映射(实践篇)
一看就懂的OpenGL ES教程——烘托宫崎骏动漫重拾幼年