效果
着色器、模拟与画面程序
现成的效果做不出你要的东西时,可以把效果写成代码:逐像素上色的着色器 (外部网站,在新标签页打开)、元素记得自己上一帧在哪里的模拟,或者画出整个图层的程序。程序是项目的一部分,它的设置是普通属性,可以打关键帧,也可以让 Agent 替你写。
三种程序
它们都是效果和预设里的效果,内容是一段程序:用 WGSL (外部网站,在新标签页打开)(WebGPU (外部网站,在新标签页打开) 的着色语言)或 JavaScript (外部网站,在新标签页打开) 写成。选能做出这个效果的、范围最窄的一种。
| 效果 | 语言 | 适合做什么 |
|---|---|---|
| 着色器 (在新标签页打开)在“风格化”类别 | WGSL | 由这一帧的图层逐像素算出的画面:图案、调色、扭曲、形状。 |
| 模拟 (在新标签页打开)在“风格化”类别 | WGSL | 取决于之前发生过什么的东西:粒子、布料、波纹、拖尾。 |
| 画面程序 (在新标签页打开)在“生成”类别 | JavaScript | 前两种画不出来的:用 WebGPU 画出的整幅画面,需要时可以分几趟画。 |
编写或粘贴程序
选中图层,添加效果:在效果和预设里。新加的着色器或模拟效果原样显示图层;新加的画面程序什么也不画。
打开程序。在效果控件里,效果上写着程序有多长;点编辑程序。
编写或粘贴源码到源码(WGSL),并在参数(JSON)里列出它读取的设置。模拟效果的声明(JSON)还要写明有多少元素、每个元素记住什么、每帧走几趟、怎样画出来。
应用。程序先在这台电脑上检查、编译并试跑;只有通过的程序才会写进项目,算一步,可以撤销。

- 程序的源码。
- 它读取的设置,一行一个。
- 检查并试跑程序,然后应用。
- 程序未被接受时,对话框会说明原因,能定位时给出你自己源码里的行号:源码被拒、编译出错、程序太慢没法编辑、模拟的数值失控,或画面程序在某个阶段失败、什么都没画、同一帧第二次画得不一样。
- 替换程序时,id 和种类都没变的设置保留原来的值和关键帧;新增的设置用默认值,删掉的设置随之移除。
可以做动画的设置
程序声明的每个参数都成为效果控件里这个效果的一个属性,名字就是你起的名字。它和别的属性一样可以拖动、打关键帧、用表达式驱动,程序在每一帧读取它的值。
参数有这几种: number、angle、color、point、boolean和choice。

- 程序有几行、几个设置。
- 重新打开程序。
- 程序声明的一个设置。
- 给这个设置打关键帧,和别的属性一样。
- 数字可以声明滑块的范围,选项列出可选的几项,角度用度数设置,点是图层上的一个位置。
- 一个程序最多声明 32 个参数。
着色器效果
着色器效果为图层的每个像素运行一次,返回这个像素的颜色。它只走一趟,输出与图层同样大小:图层以外什么也不画。
程序要定义 fn shade(s: Shade) -> vec4<f32>。它用 layer 读取图层自己的画面,用 param.id 读取各个设置。
程序从 s 里得知这个像素的情况:
s.pos- 像素的位置,以图层像素计,从左上角算起
s.uv- 同一位置,横跨图层从 0 到 1
s.size- 图层的像素尺寸
s.time- 从图层开始算起的秒数
s.frame- 从图层开始算起的帧数
s.pixel- 每个渲染像素对应几个图层像素(半分辨率预览时为 2),用来画平滑的边
除了 WGSL 自带的函数,程序还能调用所属接口版本的标准库:
| 分组 | 函数 |
|---|---|
| 图层画面 | layer和layerAt |
| 噪波 | kfHash3、kfValueNoise、kfNoise、kfFbm和kfVoronoi |
| 形状 | kfCircle、kfBox、kfRoundBox、kfSegment、kfCoverage和kfRotate |
| 颜色 | kfLuma、kfRgbToHsl、kfHslToRgb、kfHueRotate和kfPalette |
| 混合 | kfOver、kfScreen、kfOverlay和kfSoftLight |
| 模糊 | kfBlur和kfDirectionalBlur |
| 数值 | kfMod和kfAtan2 |
- 源码最多 32,768 个字符。循环的次数必须固定,程序不能自己声明绑定或入口函数。
- 没有时钟,也没有随机:时间只来自
s.time和s.frame,随机性来自标准库的噪波,所以同一帧每次都画得一样。
模拟效果
模拟效果是一格格的元素,最多 262,144 个;每个元素从一帧到下一帧记住几个值,比如位置和速度(最多 8 个)。这些状态由显卡在帧与帧之间保存,所以每个元素都从上一帧的地方接着走。
程序只填三个函数,由编辑器来运行,其余都归编辑器管:
start- 元素在图层第一帧的状态
step- 它的下一个状态。每帧对所有元素运行最多 64 趟,每一趟读到的其他元素都是上一趟留下的样子。
drawPoint- 把每个元素画成一个柔和的圆点
drawSurface- 把相邻的元素连成一张带着图层画面的面,像布料一样
模拟可以调用着色器效果的标准库,读取图层画面的函数除外:
| 分组 | 函数 |
|---|---|
| 噪波 | kfHash3、kfValueNoise、kfNoise、kfFbm和kfVoronoi |
| 形状 | kfCircle、kfBox、kfRoundBox、kfSegment、kfCoverage和kfRotate |
| 颜色 | kfLuma、kfRgbToHsl、kfHslToRgb、kfHueRotate和kfPalette |
| 混合 | kfOver、kfScreen、kfOverlay和kfSoftLight |
| 数值 | kfMod和kfAtan2 |
- 状态不存进项目:它由图层开始以来的设置决定,所以直接跳到某一帧,与一路播放到那里的结果相同。
- 应用前会先跑最初的若干帧,检查数值是否正常。
画面程序
画面程序是一个 JavaScript 模块,用 WebGPU 画出图层的整幅画面,取代图层原来的画面。它是三种里范围最宽的,用来做着色器和模拟都做不到的东西。
它在沙箱里运行:那是另一个源上的页面,碰不到编辑器、你的项目、浏览器的存储和网络。编辑器告诉它画哪一帧,它自己没有时钟。模块的默认导出有这几个函数:
setup- 只运行一次:建立程序的管线,并申请要在帧与帧之间保留的缓冲区和纹理
start- 把第 0 帧的状态写进保留的内容
step- 把保留的状态推进一帧
draw必需- 画出这一帧
- 设置按参数 id 放在
at.params里。 - 同一帧不管怎样到达都必须画得一样:跨帧带过去的东西只能放在编辑器替程序保留的内容里(最多 64 MB),不能放在 JavaScript 变量里。
- 没有时钟、没有随机、不能导入:
Math.random、Date.now和定时器都会抛错,模块就是整个程序(最多 196,608 个字符)。 - 应用前,程序必须能加载、按时画出最初几帧、画出东西,并且把同一帧再画一遍时完全一样。
预览与导出
- 查看器和导出运行的是同一段程序,在同一台电脑上画出的帧相同;换一台电脑,显卡的舍入可能略有不同。
- 带画面程序的帧在编辑器自己的页面上渲染,不进并行导出的 Worker,因为只有这个页面连得到沙箱。
- 程序在渲染时出错,会在问题里报出。
- 沙箱挡不住压垮显卡本身的程序:过重的程序会拖慢整个浏览器。
让 Agent 来写
把想要的效果告诉 Agent (在新标签页打开),三种程序它都能写。它读的就是这里说的接口,每写一版都在你的电脑上试跑、看它画出的帧,改好了再交给你。
程序和 Agent 的其他改动一样以草稿 (在新标签页打开)的形式送来:先预览,再应用或丢弃。应用之后就归你了,可以用编辑程序继续改。