WebGL交互式背景:Bayer抖动快速指南
用户体验依赖于那些融入整体设计而不压倒用户的小而周到的细节。这种平衡可能很棘手,尤其是使用像WebGL这样的技术。虽然它们可以创建惊人的视觉效果,但如果处理不当,也可能变得过于复杂和分散注意力。
Bayer抖动模式是一种微妙但有效的技术。例如,JetBrains最近的Junie活动页面使用这种方法来营造一种沉浸式和吸引人的氛围,同时保持视觉平衡和可访问性。
在本教程中,我将向您介绍Bayer抖动模式。我将解释它是什么,它是如何工作的,以及如何将其应用到您自己的Web项目中,以增强视觉深度而不压倒用户体验。
Bayer抖动
Bayer模式是一种有序抖动类型,它允许您使用固定矩阵模拟渐变和深度。
如果我们适当地缩放这个矩阵,我们可以定位特定值并创建基本模式。
这是一个简单的例子:
1
2
3
4
5
6
7
8
9
|
// 2×2 Bayer矩阵模式:返回[0, 1)范围内的值
float Bayer2(vec2 a)
{
a = floor(a); // 使用整数单元格坐标
return fract(a.x / 2.0 + a.y * a.y * 0.75);
// 等效查找表:
// (0,0) → 0.0, (1,0) → 0.5
// (0,1) → 0.75, (1,1) → 0.25
}
|
让我们来看一个如何使用这个函数的例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
// 1. 基础遮罩:左半部分是黑到白渐变
float mask = uv.y;
// 2. 右半部分:应用有序抖动
if (uv.x > 0.5) {
float dither = Bayer2(fragCoord);
mask += dither - 0.5;
mask = step(0.5, mask); // 二进制阈值
}
// 3. 输出结果
fragColor = vec4(vec3(mask), 1.0);
|
因此,仅使用一个小矩阵,我们就得到了四个不同的抖动值——基本上是免费的。
创建背景效果
这仍然相当基础——在用户体验方面还没有什么太令人兴奋的。让我们通过在UV地图上创建一个网格来进一步推进。我们将定义一个"像素"的大小和确定每个"像素"是否打开或关闭的矩阵大小,使用Bayer排序。
1
2
3
4
5
6
7
8
9
10
11
12
|
const float PIXEL_SIZE = 10.0; // Bayer矩阵中每个像素的大小
const float CELL_PIXEL_SIZE = 5.0 * PIXEL_SIZE; // 5x5矩阵
float aspectRatio = uResolution.x / uResolution.y;
vec2 pixelId = floor(fragCoord / PIXEL_SIZE);
vec2 cellId = floor(fragCoord / CELL_PIXEL_SIZE);
vec2 cellCoord = cellId * CELL_PIXEL_SIZE;
vec2 uv = cellCoord/uResolution * vec2(aspectRatio, 1.0);
vec3 baseColor = vec3(uv, 0.0);
|
您将看到一个渲染的UV网格,蓝色点表示像素,白色(以及后续相同大小的块)表示Bayer矩阵。
递归Bayer矩阵
Bayer的天才之处在于递归生成的遮罩,它保持噪声高频且代码低复杂度。现在让我们尝试一下,并应用更大的抖动矩阵:
1
2
3
4
5
6
7
8
9
10
11
|
float Bayer2(vec2 a) { a = floor(a); return fract(a.x / 2. + a.y * a.y * .75); }
#define Bayer4(a) (Bayer2(0.5 * (a)) * 0.25 + Bayer2(a))
#define Bayer8(a) (Bayer4(0.5 * (a)) * 0.25 + Bayer2(a))
#define Bayer16(a) (Bayer8(0.5 * (a)) * 0.25 + Bayer2(a))
...
if(uv.x > .2) dither = Bayer2 (pixelId);
if(uv.x > .4) dither = Bayer4 (pixelId);
if(uv.x > .6) dither = Bayer8 (pixelId);
if(uv.x > .8) dither = Bayer16(pixelId);
...
|
这给我们提供了一个很好的视觉过渡,从基本的UV网格到越来越复杂的Bayer矩阵(2×2、4×4、8×8、16×16)。
如您所见,8×8和16×16模式非常相似——超过8×8,感知增益变得最小。因此,我们将在下一步中使用Bayer8。
现在,我们将Bayer8应用于由fbm噪声调制的UV地图,使结果感觉更加有机——正如我们承诺的那样。
添加交互性
这是事情变得令人兴奋的地方:实时交互性,这是背景视频无法复制的。让我们使用抖动模式在点击点周围运行涟漪效果。我们将迭代所有活动点击并计算一个波:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
for (int i = 0; i < MAX_CLICKS; ++i) {
// 将此点击转换为平方单位UV
vec2 pos = uClickPos[i];
if(pos.x < 0.0 && pos.y < 0.0) continue; // 跳过空点击
vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution) )) * vec2(aspectRatio, 1.0);
float t = max(uTime - uClickTimes[i], 0.0);
float r = distance(uv, cuv);
float waveR = speed * t;
float ring = exp(-pow((r - waveR) / thickness, 2.0));
float atten = exp(-dampT * t) * exp(-dampR * r);
feed = max(feed, ring * atten); // 最亮者获胜
}
|
尝试点击下面的CodePen:
最终思考
由于整个Bayer抖动背景是在单个GPU通道中生成的,即使在4K分辨率下,它也能在0.2毫秒内渲染完成,大小约为3 KB(在这种情况下加上Three.js),并且在加载后消耗零网络带宽。一旦您有数千个节点,SVG就无法与之相比,而自动播放视频在带宽、CPU和电池消耗上要重两个数量级。简而言之:这可能是当今开放Web上可以构建的最轻量级的完全交互式背景效果之一。