借助自定义过滤器(以前称之为 CSS 着色器),您可以将 WebGL 着色器的强大功能用于 DOM 内容。由于当前实现中使用的着色器与 WebGL 中的着色器几乎相同,因此您需要退一步了解一些 3D 术语和一些图形管道。
我添加了最近向 LondonJS 提交的演示文稿的录制版本。在本视频中,我将简要介绍您需要了解的 3D 术语、您将遇到的不同变量类型,以及如何立即开始使用自定义过滤器。您还应观看幻灯片,以便自己动手演示。
着色器简介
我之前撰写了着色器简介,其中详细介绍了什么是着色器,以及如何从 WebGL 的角度使用着色器。如果您从未接触过着色器,那么在您继续深入学习之前,有必要读一读,因为自定义滤镜的许多概念和语言都依赖于现有的 WebGL 着色器术语。
话虽如此,让我们启用“自定义过滤条件”并继续操作!
启用自定义过滤器
Chrome 和 Canary 版以及 Android 版 Chrome 均支持自定义过滤器。只需前往 about:flags
并搜索“CSS 着色器”,将其启用并重启浏览器。现在,您可以开始操作了!
语法
自定义过滤器扩展了您已应用到 DOM 元素的一系列过滤器,例如 blur
或 sepia
。Eric Bidelman 为此类请求编写了一个很棒的游乐场工具,值得一看。
要将自定义过滤器应用到 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。
这里最后要指出的一点是,我们针对 fragment 着色器使用了 mix()
函数,其中包含混合模式 (normal
) 和复合模式 (source-atop
)。我们来看一下 fragment 着色器本身,看看为什么还需要 mix()
函数。
像素推送
如果您熟悉 WebGL 的着色器,就会发现“自定义滤镜”中的内容略有不同。例如,我们不会创建片段着色器用来填充像素的纹理。相反,应用了滤镜的 DOM 内容会自动映射到纹理,这意味着两点:
- 出于安全考虑,我们无法查询 DOM 纹理的各个像素颜色值
- 我们没有(至少在当前实现中)自行设置最终像素颜色,即禁止使用
gl_FragColor
。而是假定您想要渲染 DOM 内容,并且您执行的操作是通过css_ColorMatrix
和css_MixColor
间接操控其像素。
这意味着 Fragment 着色器的 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
相乘,在上述情况下,该像素不会作为其身份矩阵,也不会更改任何 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 内容混合在一起。混合是通过您在艺术包中熟悉的混合模式完成的:叠加、屏幕、色彩减弱、硬光等。
这两个变量可以通过很多方法操控像素。您应查看“滤镜效果”规范,以更好地了解混合模式与合成模式的交互方式。
顶点创建
在 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;
}
开始玩吧!
自定义滤镜非常有趣,但如果没有自定义滤镜,制作出的神奇效果将非常困难(在某些情况下,甚至无法实现)。现在仍处于早期阶段,一切都发生了很大变化,但添加项目可为您的项目增添一点风潮,何不大胆一试?