2
头图

伴随着赛博朋克2077的火爆(各种意义上的),这种电子故障类的shader似乎成为了一种时尚,因为你真不知道这是bug还是有意为之。今天我们就来了了几款故障类shader的原理及实现吧!本期将介绍2种shader(颜色偏移与扫描偏移)下期我们再说两种(抖动和摇晃),合理使用这四种shader你能够实现下面短片中的效果:

image

颜色偏移

最终效果

我们都知道,图片是由RGB三个通道的颜色叠加起来而成的。R+G得到黄,R+B得到洋红,G+B是青。

颜色偏移的原理就是把其中一个通道偏移一个距离在叠加起来。比如移动绿色通道:

void main () {
 // Color drift
 float drift = sin ( u_time) * u_colorDrift * .04;
 // Offset
 vec2 offset = vec2(drift,.0);
 vec4 color1 = texture2D (u_image0, uv);
 vec4 color2 = texture2D (u_image0, uv + offset);
 gl_FragColor = vec4 (color1.r, color2.g, color1.b, 1.0);
}

其中u_time是当前时间,保证持续抖动,u_colorDrift是横向偏移的距离:

是不是you点抖音的感觉?那么为什么同种出现的颜色是洋红和绿??那是因为我们移动了绿通道,而洋红就是剩下的蓝通道和红通道叠加的结果。这里我们可以实验一下,如果移动红通道。得到的抖动颜色就会是红色和青色:

gl_FragColor =vec4(color2.r, color1.g, color1.b,1.0);

扫描射线

这种效果会在产生逐条的偏移,类似小时候电视机调台时那种感觉(暴露年龄了)

这里我们需要一个随机函数对不同y进行不同的偏移(图中可以看出,对y方向相同的点偏移是一致的)。这里我们需要一个hash函数:

float hash21 (float x, float y) {
 return fract (sin (dot (vec2 (x, y), vec2 (12.9898, 78.233))) * 43758.5453);
}

核心思想是通过fract(sin(x)*a)当a是一个很大的数时构造出出一种随机:

这种理念在需要生成躁点的shader中十分常见,再配合smoothstep,step,u_time可以写出很多难以置信的效果,以后我们会经常遇到。

此时在main函数中我们已经可以完成代码逻辑了:

void main(){
    float y = uv.y;
    float jitter = hash21 (y, .0) * 2. - 1.;
    vec2 offset = fract (vec2 (uv.x + jitter, .0));
    vec4 color = texture2D (u_image0, offset);
    gl_FragColor = color;
}

offset加上fract是保证偏能够连续:

图中上为没有加fract的效果,下为加了fract的效果,我们使用扫描偏移时,由于不至于偏移到半个屏幕,所以不加fract效果也未必很明显,但后面说的两种属性(抖动和摇晃)是很容易移动半个屏幕的,所以必须加上fract。

最后由于我们需要通过参数控制扫描射线效果,所以我们还需要加上一个阀门函数。

jitter *= step (u_scan,abs (jitter)) ;

其中step函数,用于尖锐的过度,y = step(a,x)当x大于a时y=1;否则等于0:

a等于0.5时

调换x与a的顺序相反:

我们来分析一下step (u_scan, abs (jitter));因为 abs (jitter)是一个值域介于0-1,当u_scan趋近于0时,step (u_scan, abs (jitter))就更大概率等于1;当u_scan趋近于1时,step (u_scan, abs (jitter))就更大概率等于0;

所以通过控制u_scan的值就能控制,jitter *= step (u_scan,abs (jitter)) 输出随机数的稠密程度:

u_scan分别取0,0.5,0.9时函数的稠密程度(取1就完全归零了,有兴趣的小伙伴可以试试)。但是我们实习完u_scan在0的时候最小,1的时候最大。这个效果正好与我们所期望的相反。所以我们还需要再添加一个函数

float scan = clamp (1.0 - u_scan * 1.2, 0., 1.);
jitter *= step (scan, abs (jitter));

其实这里用float scan = 1.0 - u_scan也是可以的。clamp函数只是保证输出在[0,1]之间。

最后我们把上面两个效果合并:

precision mediump float;
/* 
 变量申明
*/
varying vec2 uv;
uniform sampler2D u_image0;
uniform float u_scan;
uniform float u_colorDrift;
uniform float u_time;
/* 
 工具函数
*/
float hash21 (float x, float y) {
 return fract (sin (dot (vec2 (x, y), vec2 (12.9898, 78.233))) * 43758.5453);
}
void main () {
 float x = uv.x;
 float y = uv.y;
 // Scan line jitter
 float jitter = hash21 (y, u_time) * 2. - 1.;
 float scan = clamp (1.0 - u_scan * 1.2, 0., 1.);
  jitter *= step (u_scanY, abs (jitter)) ;
 // Color drift
 float drift = sin (u_time * 666. + jump) * u_colorDrift * .04;
 // Offset
 vec2 offset1 = fract (vec2 (uv.x + jitter , 0.));
 vec2 offset2 = fract (vec2 (uv.x + jitter + drift , 0.));
 vec4 color1 = texture2D (u_image0, offset1);
 vec4 color2 = texture2D (u_image0, offset2);
 gl_FragColor = vec4 (color1.r, color2.g, color1.b, 1.0);
}

下期我们来说说横向和纵向的抖动和摇晃吧。
相关文章:
热成像
像素风

如果你对shader感兴趣,也可以看看下面的文章:


这是上帝的杰作
2.2k 声望164 粉丝

//loading...