借助自定义滤镜(以前称为 CSS 着色器),您可以将 WebGL 着色器的强大功能用于 DOM 内容。由于当前实现中使用的着色器与 WebGL 中的着色器几乎相同,因此您需要先了解一些 3D 术语和图形管道。
我附上了最近在 LondonJS 上演讲的录制版本。在该视频中,我将简要介绍您需要了解的 3D 术语、您会遇到的不同变量类型,以及如何立即开始使用自定义过滤条件。您还应下载幻灯片,以便自行试用演示。
着色器简介
我之前曾撰写过一篇着色器简介,其中详细介绍了着色器的含义以及如何从 WebGL 的角度使用着色器。如果您从未处理过着色器,则必须先阅读相关内容,然后才能继续深入学习,因为许多自定义滤镜概念和语言都依赖于现有的 WebGL 着色器术语。
话不多说,我们来启用自定义过滤器,继续前进!
启用自定义过滤器
自定义过滤器适用于 Chrome、Canary 版和 Chrome(Android 版)。只需前往 about:flags
并搜索“CSS 着色器”,然后启用它们并重启浏览器即可。现在,您可以开始操作了!
语法
自定义过滤器是对您已经可以对 DOM 元素应用的一组过滤器(例如 blur
或 sepia
)的扩展。Eric Bidelman 为此编写了一款出色的Playground 工具,您应该试用一下。
如需将自定义过滤器应用于 DOM 元素,请使用以下语法:
.customShader {
-webkit-filter:
custom(
url(vertexshader.vert)
mix(url(fragment.frag) normal source-atop),
/* Row, columns - the vertices are made automatically */
4 5,
/* We set uniforms; we can't set attributes */
time 0)
}
从中可以看出,我们声明了顶点着色器和片段着色器、我们希望 DOM 元素分解为的行数和列数,以及我们要传递的任何 uniform。
最后需要指出的是,我们在片段着色器周围使用了 mix()
函数,并采用了混合模式 (normal
) 和复合模式 (source-atop
)。我们来看看片段着色器本身,了解为什么我们需要 mix()
函数。
像素推送
如果您熟悉 WebGL 的着色器,就会发现自定义滤镜中的用法略有不同。首先,我们没有创建 fragment 着色器用于填充像素的纹理。而是会自动将应用了过滤器的 DOM 内容映射到纹理,这意味着两点:
- 出于安全考虑,我们无法查询 DOM 纹理的各个像素颜色值
- 我们不会(至少在当前实现中)自行设置最终像素颜色,即
gl_FragColor
不受限制。而是假定您希望渲染 DOM 内容,而您需要做的是通过css_ColorMatrix
和css_MixColor
间接操控其像素。
也就是说,我们的片段着色器 Hello World 更像这样:
void main() {
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
css_MixColor = vec4(0.0, 0.0, 0.0, 0.0);
// umm, where did gl_FragColor go?
}
DOM 内容的每个像素都会乘以 css_ColorMatrix
,在上述示例中,由于 css_ColorMatrix
是标识矩阵,因此不会执行任何操作,也不会更改任何 RGBA 值。如果我们想只保留红色值,则可以使用如下 css_ColorMatrix
:
// keep only red and alpha
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0);
您应该会发现,将 4D (RGBA) 像素值乘以矩阵后,您会在另一侧获得经过处理的像素值,在本例中,该值会将绿色和蓝色分量设为零。
css_MixColor
主要用作您要与 DOM 内容混合的底色。混合是通过您熟悉的图形软件包中的混合模式完成的:叠加、屏幕、色彩 dodge、硬光等。
这两个变量可以通过多种方式操控像素。您应参阅滤镜效果规范,以更好地了解混合模式和复合模式之间的互动方式。
顶点创建
在 WebGL 中,我们全权负责创建网格的 3D 点,但在自定义过滤器中,您只需指定所需的行数和列数,浏览器就会自动将 DOM 内容细分为一系列三角形:
然后,这些顶点中的每一个都会传递给顶点着色器进行处理,这意味着我们可以根据需要在 3D 空间中移动它们。很快,您就可以制作出一些非常棒的效果了!
使用着色器添加动画效果
为着色器引入动画可让其变得有趣且富有吸引力。为此,您只需在 CSS 中使用转换(或动画)来更新统一值:
.shader {
/* transition on the filter property */
-webkit-transition: -webkit-filter 2500ms ease-out;
-webkit-filter: custom(
url(vshader.vert)
mix(url(fshader.frag) normal source-atop),
1 1,
time 0);
}
.shader:hover {
-webkit-filter: custom(
url(vshader.vert)
mix(url(fshader.frag) normal source-atop),
1 1,
time 1);
}
因此,在上述代码中,请注意,时间将在过渡期间从 0
缓慢过渡到 1
。在着色器中,我们可以声明均匀的 time
并使用其当前值:
uniform float time;
uniform mat4 u_projectionMatrix;
attribute vec4 a_position;
void main() {
// copy a_position to position - attributes are read only!
vec4 position = a_position;
// use our time uniform from the CSS declaration
position.x += time;
gl_Position = u_projectionMatrix * position;
}
开始玩吧!
自定义滤镜非常有趣,如果没有它们,您就很难(在某些情况下甚至无法)制作出令人惊叹的效果。这项功能仍处于起步阶段,并且情况会发生很大变化,但添加它们会为您的项目增添一点娱乐性,不妨试试吧!