效果示例, 工程源码地址见文末

特效: 上下分屏、3分屏、4分屏、9分屏、缩放效果、抖动效果、毛刺 、马赛克、六边形马赛克、三角形马赛克、灵魂出窍效果、灰度效果、泛白效果、幻觉效果

加载原图:

- 顶点着色器
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main (void) {
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
- 片元着色器
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main (void) {
vec4 mask = texture2D(Texture, TextureCoordsVarying);
gl_FragColor = vec4(mask.rgb, 1.0);
}
分屏
分屏的原理基本相同,就是将原始坐标(x, y),根据映射关系通过运算生成新的坐标(newX, newY)
上下分屏
我们需要的效果
图解分析:

宽度仍是原图的宽度,但是上下两部分显示的内容高度为原图0.25 - 0.75部分
- 上半部 [0.0 - 0.5] 对应图片的[0.25, 0.75], 即 x = x + 0.25
- 下半部 [0.5 - 1.0] 对应图片的 [0.25, 0.75], 即 x = x - 0.25
所以设原图纹理坐标为(x, y), 则
- 在上半部时, 新的坐标为(x + 0.25, y)
- 在下半部时, 新的坐标为(x - 0.25, y)
顶点着色器代码没有变化
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main (void) {
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
片元着色器代码:
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
float y;
if (uv.y >= 0.0 && uv.y <= 0.5) {
// 上半部
y = uv.y + 0.25;
} else {
// 下半部
y = uv.y - 0.25;
}
gl_FragColor = texture2D(Texture, vec2(uv.x, y));
}
三分屏

原理同上下二分屏.只是坐标值有所变化.

- 顶点着色器代码没有变化
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main (void) {
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
- 片元着色器代码
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if (uv.y < 1.0/3.0) {
uv.y = uv.y + 1.0/3.0;
} else if (uv.y > 2.0/3.0){
uv.y = uv.y - 1.0/3.0;
}
gl_FragColor = texture2D(Texture, uv);
}
四分屏

x方向分两部分左[0.0, 0.5] 和右 [0.5, 1.0], 映射图片内容坐标 [0.0, 1.0]
y方向分两部分上[0.0, 0.5] 和下 [0.5, 1.0], 映射图片内容坐标 [0.0, 1.0]
由此可知:
处在左部分时, newX = x * 2, 右部分时: newX = (x - 0.5) * 2
处在上部分时, newY = y * 2, 下部分时: newY = (y - 0.5) * 2
顶点着色器代码没有变化不再赘述,片元着色器代码如下:
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if(uv.x <= 0.5){
uv.x = uv.x * 2.0;
}else{
uv.x = (uv.x - 0.5) * 2.0;
}
if (uv.y<= 0.5) {
uv.y = uv.y * 2.0;
}else{
uv.y = (uv.y - 0.5) * 2.0;
}
gl_FragColor = texture2D(Texture, uv);
}
六分屏

片元着色器代码
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if(uv.x <= 1.0 / 3.0){
uv.x = uv.x + 1.0/3.0;
}else if(uv.x >= 2.0/3.0){
uv.x = uv.x - 1.0/3.0;
}
if(uv.y <= 0.5){
uv.y = uv.y + 0.25;
}else {
uv.y = uv.y - 0.25;
}
gl_FragColor = texture2D(Texture, uv);
}
效果如下:

九分屏

片元着色器代码
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if (uv.x < 1.0 / 3.0) {
uv.x = uv.x * 3.0;
} else if (uv.x < 2.0 / 3.0) {
uv.x = (uv.x - 1.0 / 3.0) * 3.0;
} else {
uv.x = (uv.x - 2.0 / 3.0) * 3.0;
}
if (uv.y <= 1.0 / 3.0) {
uv.y = uv.y * 3.0;
} else if (uv.y < 2.0 / 3.0) {
uv.y = (uv.y - 1.0 / 3.0) * 3.0;
} else {
uv.y = (uv.y - 2.0 / 3.0) * 3.0;
}
gl_FragColor = texture2D(Texture, uv);
}

灰度特效

灰度滤镜算法
名称 | 公式 |
---|---|
浮点算法 | Gray = R* 0.3 + G * 0.59 + B * 0.11 |
整数方法 | Gray=( R * 30 + G * 59 + B * 11 ) / 100 |
移位方法 | Gray = ( R * 76 + G * 151 + B * 28 ) >> 8 |
平均值法 | Gray = ( R + G + B ) / 3 |
取绿色 | Gray = G |
这里采用浮点算法, 片元着色器代码如下:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
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);
}
图片颠倒

翻转纹理的方案有很多, 具体可看:纹理翻转方案
片元着色器代码:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main (void) {
vec4 color = texture2D(Texture, vec2(TextureCoordsVarying.x, 1.0 - TextureCoordsVarying.y));
gl_FragColor = color;
}
马赛克

原理: 马赛克效果就是把图片的一个相当⼤小的区域用同一个点的颜色来表示.可以认为是⼤规模的降低图像的分辨率,而让图像的一些细节隐藏起来。
因此, 我们只要在片元着色器中修改在一定区域内现实的是同一个点的像素就能产生效果.
片元着色器代码:
precision mediump float;
varying vec2 TextureCoordsVarying;
uniform sampler2D Texture;
const vec2 TexSize = vec2(400.0, 400.0);
const vec2 mosaicSize = vec2(4.0, 4.0);
void main()
{
vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x, XYMosaic.y/TexSize.y);
vec4 color = texture2D(Texture, UVMosaic);
gl_FragColor = color;
}
-
const vec2 mosaicSize = vec2(4.0, 4.0);
定义一个马赛克点的大小. -
const vec2 TexSize = vec2(400.0, 400.0);
,x,y的值范围是 0 - 1, 为了方便计算, 先将其扩大一定倍数. -
vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
当前扩大x,y的值. -
vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
通过运算控制当前点展示的颜色范围. - 还原xy的范围到 0 - 1
- 比如当前 ( x , y ) = ( 0.101 , 0.1 )
- 扩大400之后为: ( 40.4 , 40 )
- 除以马赛克大小4, 并向下取整为: ( 10 , 10 )
- 缩小400还原: ( 0.1, 0.1 )
- 也就是说 (0.101, 0.1) 这个点显示的颜色值和(0.1, 0.1)是一样的.
- 以此类推就形成马赛克效果
六边形马赛克

原理基本相同,只是计算区域的逻辑算法有所改变

如上图把图片看成一个一个的六边形组成,当前坐标点落在哪个六边形上,则填充的颜色为该六边形中心点的颜色。如下图,再把每个六边形拆分,可以看成四种矩形拼接而成。

- 首先需要判断当前坐标(x,y)坐落在这四个矩形里的哪一个。
- 算出距离六边形中心点最近的那个六边形
- 修改颜色
假定我们设定的矩阵比例为 3LEN : √3LEN ,(LEN为常数)那么屏幕上的任意点(x, y)所对应的矩阵坐标为(int(x/(3LEN)), int(y/ (√3LEN)))。
换算公式:
//wx,wy -> 表示纹理坐标在所对应的矩阵坐标为
int wx = int(x /( TB * length));
int wy = int(y /(TR * length));
根据wx和wy算出矩形的四个顶点:
// 左上
vec2(length * 1.5 * float(wx), length * TR * float(wy));
// 右上
vec2(length * 1.5 * float(wx + 1), length * TR * float(wy))
// 左下
vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
// 右下
vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
综上所述,得出片元着色器代码如下:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
const float mosaicSize = 0.015;
void main (void)
{
float length = mosaicSize;
float TR = 0.866025;
float TB = 1.5;
float x = TextureCoordsVarying.x;
float y = TextureCoordsVarying.y;
int wx = int(x / TB / length);
int wy = int(y / TR / length);
vec2 v1, v2, vn;
if (wx/2 * 2 == wx) {
if (wy/2 * 2 == wy) {
//(0,0),(1,1)
v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
} else {
//(0,1),(1,0)
v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
}
}else {
if (wy/2 * 2 == wy) {
//(0,1),(1,0)
v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
} else {
//(0,0),(1,1)
v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
}
}
float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
if (s1 < s2) {
vn = v1;
} else {
vn = v2;
}
vec4 color = texture2D(Texture, vn);
gl_FragColor = color;
}
三角形马赛克

三角形效果是基于六边形实现的

在找到六边形中心点后,算出当前(x, y)处在哪个三角形区域即可
片元着色器代码如下
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
float mosaicSize = 0.02;
void main (void){
const float TR = 0.866025;
const float PI6 = 0.523599;
float x = TextureCoordsVarying.x;
float y = TextureCoordsVarying.y;
int wx = int(x/(1.5 * mosaicSize));
int wy = int(y/(TR * mosaicSize));
vec2 v1, v2, vn;
if (wx / 2 * 2 == wx) {
if (wy/2 * 2 == wy) {
v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy + 1));
} else {
v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy));
}
} else {
if (wy/2 * 2 == wy) {
v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
v2 = vec2(mosaicSize * 1.5 * float(wx+1), mosaicSize * TR * float(wy));
} else {
v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy+1));
}
}
float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
if (s1 < s2) {
vn = v1;
} else {
vn = v2;
}
vec4 mid = texture2D(Texture, vn);
float a = atan((x - vn.x)/(y - vn.y));
vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0);
vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0);
vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
if (a >= PI6 && a < PI6 * 3.0) {
vn = area1;
} else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {
vn = area2;
} else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0)|| (a<-PI6 * 5.0 && a>-PI6*6.0)) {
vn = area3;
} else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {
vn = area4;
} else if(a <= -PI6 && a> -PI6 * 3.0) {
vn = area5;
} else if (a > -PI6 && a < PI6)
{
vn = area6;
}
vec4 color = texture2D(Texture, vn);
gl_FragColor = color;
}
上面的效果都是静态的,下面开始的特效都是动态的。所以需要先开启一个定时器,不停的去绘制屏幕, 并将当前时间进度传递到着色器代码中。
// 开始一个滤镜动画
- (void)startFilerAnimation {
//1.判断displayLink 是否为空
//CADisplayLink 定时器
if (self.displayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
}
//2. 设置displayLink 的方法
self.startTimeInterval = 0;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)];
//3.将displayLink 添加到runloop 运行循环
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
//2. 动画
- (void)timeAction {
//DisplayLink 的当前时间撮
if (self.startTimeInterval == 0) {
self.startTimeInterval = self.displayLink.timestamp;
}
//使用program
glUseProgram(self.program);
//绑定buffer
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
// 传入时间
CGFloat currentTime = self.displayLink.timestamp - self.startTimeInterval;
GLuint time = glGetUniformLocation(self.program, "Time");
glUniform1f(time, currentTime);
// 清除画布
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
// 重绘
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//渲染到屏幕上
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
缩放特效

原理比较简单,修改纹理顶点的映射关系就好了。这里我们采用正弦函数去实现变化幅度。
片元着色器代码如下:
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
uniform float Time;
const float PI = 3.1415926;
void main (void) {
float duration = 0.6;
float maxAmplitude = 0.3;
float time = mod(Time, duration);
float amplitude = 1.0 + maxAmplitude * abs(sin(time * (PI / duration)));
gl_Position = vec4(Position.x * amplitude, Position.y * amplitude, Position.zw);
TextureCoordsVarying = TextureCoords;
}
灵魂出窍特效

两个图层的叠加,一个显示原图,一个放大透明度渐变的动画图层。
片元着色器代码:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
uniform float Time;
void main (void) {
float duration = 0.7; // 动画时长
float maxAlpha = 0.4; // 放大效果透明度峰值
float maxScale = 1.8; // 放大倍数峰值
float progress = mod(Time, duration) / duration; // 0~1
float alpha = maxAlpha * (1.0 - progress);
float scale = 1.0 + (maxScale - 1.0) * progress;
float weakX = 0.5 + (TextureCoordsVarying.x - 0.5) / scale;
float weakY = 0.5 + (TextureCoordsVarying.y - 0.5) / scale;
vec2 weakTextureCoords = vec2(weakX, weakY);
vec4 weakMask = texture2D(Texture, weakTextureCoords);
vec4 mask = texture2D(Texture, TextureCoordsVarying);
// 叠加
gl_FragColor = mask * (1.0 - alpha) + weakMask * alpha;
}
抖动特效

原理:颜色偏移 + 微弱的放大效果
片元着色器代码:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
uniform float Time;
void main (void) {
float duration = 0.7;
float maxScale = 1.1;
float offset = 0.02;
float progress = mod(Time, duration) / duration; // 0~1
vec2 offsetCoords = vec2(offset, offset) * progress;
float scale = 1.0 + (maxScale - 1.0) * progress;
vec2 ScaleTextureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale;
vec4 maskR = texture2D(Texture, ScaleTextureCoords + offsetCoords);
vec4 maskB = texture2D(Texture, ScaleTextureCoords - offsetCoords);
vec4 mask = texture2D(Texture, ScaleTextureCoords);
gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a);
}
闪白

闪白特效比较简单,就是叠加一层白色图层
片元着色器代码:
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
uniform float Time;
const float PI = 3.1415926;
void main (void) {
float duration = 0.6;
float time = mod(Time, duration);
vec4 whiteMask = vec4(1.0, 1.0, 1.0, 1.0);
float amplitude = abs(sin(time * (PI / duration)));
vec4 mask = texture2D(Texture, TextureCoordsVarying);
gl_FragColor = mask * (1.0 - amplitude) + whiteMask * amplitude;
}
毛刺特效

具体的思路是,我们让每一⾏像素随机偏移 -1 ~ 1 的距离(这⾥的 -1 ~ 1 是对于纹理坐标来说的),但是如果整个画⾯都偏移⽐较⼤的值,那我们可能都看不不出原来图像的样子。所以我们的逻辑是,设定⼀个阈值,⼩于这个阈值才进⾏行偏 移,超过这个阈值则乘上⼀个缩小系数。
则最终呈现的效果是:绝大部分的行都会进⾏微小的偏移,只有少量的行会进⾏较大偏移
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
uniform float Time;
const float PI = 3.1415926;
float rand(float n) {
return fract(sin(n) * 43758.5453123);
}
void main (void) {
float maxJitter = 0.06;
float duration = 0.3;
float colorROffset = 0.01;
float colorBOffset = -0.025;
float time = mod(Time, duration * 2.0);
float amplitude = max(sin(time * (PI / duration)), 0.0);
float jitter = rand(TextureCoordsVarying.y) * 2.0 - 1.0; // -1~1
bool needOffset = abs(jitter) < maxJitter * amplitude;
float textureX = TextureCoordsVarying.x + (needOffset ? jitter : (jitter * amplitude * 0.006));
vec2 textureCoords = vec2(textureX, TextureCoordsVarying.y);
vec4 mask = texture2D(Texture, textureCoords);
vec4 maskR = texture2D(Texture, textureCoords + vec2(colorROffset * amplitude, 0.0));
vec4 maskB = texture2D(Texture, textureCoords + vec2(colorBOffset * amplitude, 0.0));
gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a);
}
幻影特效

残影的效果: 是在移动的过程中,每经过⼀段时间间隔,根据当前的位置去创建一个新层,并且新层的不透明度随着时间逐渐减弱。于是在一个移动周期内,可以看到很多透明度不同的层叠加在一起,从⽽形成残影的效果。残影,让图⽚片随着时间做圆周运动
颜偏移: 物体移动的过程是蓝⾊色在前面,红⾊在后面。所以整个过程可以理解成:在移动的过程中,每间隔⼀段时间,遗 失了一部分红色通道的值在原来的位置,并且这部分红色通道的值,随着时间偏移,会逐渐恢复.
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
uniform float Time;
const float PI = 3.1415926;
const float duration = 2.0;
vec4 getMask(float time, vec2 textureCoords, float padding) {
vec2 translation = vec2(sin(time * (PI * 2.0 / duration)),
cos(time * (PI * 2.0 / duration)));
vec2 translationTextureCoords = textureCoords + padding * translation;
vec4 mask = texture2D(Texture, translationTextureCoords);
return mask;
}
float maskAlphaProgress(float currentTime, float hideTime, float startTime) {
float time = mod(duration + currentTime - startTime, duration);
return min(time, hideTime);
}
void main (void) {
float time = mod(Time, duration);
float scale = 1.2;
float padding = 0.5 * (1.0 - 1.0 / scale);
vec2 textureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale;
float hideTime = 0.9;
float timeGap = 0.2;
float maxAlphaR = 0.5; // max R
float maxAlphaG = 0.05; // max G
float maxAlphaB = 0.05; // max B
vec4 mask = getMask(time, textureCoords, padding);
float alphaR = 1.0; // R
float alphaG = 1.0; // G
float alphaB = 1.0; // B
vec4 resultMask = vec4(0, 0, 0, 0);
for (float f = 0.0; f < duration; f += timeGap) {
float tmpTime = f;
vec4 tmpMask = getMask(tmpTime, textureCoords, padding);
//
float tmpAlphaR = maxAlphaR - maxAlphaR * maskAlphaProgress(time, hideTime, tmpTime) / hideTime;
float tmpAlphaG = maxAlphaG - maxAlphaG * maskAlphaProgress(time, hideTime, tmpTime) / hideTime;
float tmpAlphaB = maxAlphaB - maxAlphaB * maskAlphaProgress(time, hideTime, tmpTime) / hideTime;
resultMask += vec4(tmpMask.r * tmpAlphaR,
tmpMask.g * tmpAlphaG,
tmpMask.b * tmpAlphaB,
1.0);
alphaR -= tmpAlphaR;
alphaG -= tmpAlphaG;
alphaB -= tmpAlphaB;
}
resultMask += vec4(mask.r * alphaR, mask.g * alphaG, mask.b * alphaB, 1.0);
gl_FragColor = resultMask;
}
- 建议用真机运行,因为模拟器是用CPU模拟的GPU功能,所以用模拟器运行导致CPU使用率会很高而造成卡顿的问题。
网友评论