mcpField
F
falling-leaves

falling-leaves

github.com/mengto设计与 UI7.1k1.2k 次安装3 天前更新
代码仓库

关于 falling-leaves

用于在 2D Canvas 或 3D 场景中制作可辨识落叶效果的设计技能,涵盖翻滚、空气滑移、分层景深、生命周期管理和减少动态效果支持。

主要功能

  1. 01叶片侧转至边缘的翻滚
  2. 02滑移与翻滚角度联动
  3. 03区分正反面的精灵图
  4. 04为每片叶子设置不同运动参数
  5. 05按视口调整回收与密度
  6. 06生命周期与减少动态效果处理

适用场景

  1. 01在 2D Canvas 中制作可辨识的落叶
  2. 02在 3D 场景中构建分层落叶效果
  3. 03按屏幕尺寸调整叶片密度
  4. 04为减少动态效果设计静态画面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mengto/skills --skill falling-leaves

安装后在 Claude Code 中输入/falling-leaves确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
demo/
index.html
PROMPT.md

详细介绍

飘落的叶子

让飘落的物体看起来像叶子。想要营造范围有限、由通用微粒构成的氛围时,请使用 ambient-section-particles。如果形状必须清晰可辨,就使用这个。

先做好翻转效果

让每片叶子绕自身长轴旋转,正面朝向观察者,侧边朝向观察者时薄至消失,然后在另一侧重新展开。那一瞬间近乎消失的效果,正是眼睛辨认出“叶子”的关键。无论美术效果多好,只在画面平面内旋转的精灵看起来都像五彩纸屑、硬币或纸片。

在二维画布上,翻转表现为水平缩放穿过零:

ctx.save();
ctx.translate(l.x, l.y);
ctx.rotate(l.roll);              // long axis drifting in-plane
ctx.scale(Math.cos(l.spin), 1);  // the tumble: cos crosses 0, edge-on
ctx.globalAlpha = l.alpha;
ctx.drawImage(sprite, -w / 2, -h / 2, w, h);
ctx.restore();

驱动两个轴,而不是一个。roll 让叶子在画面平面内旋转;spin 让叶子穿过画面平面旋转。为每片叶子的两个轴分别设置速率,否则无论缓动效果如何,运动都会显得机械。

在三维中,使用实例化的四边形,绝不要使用点精灵。点精灵始终面向摄像机,永远无法转到侧边朝向摄像机,因此也就永远无法呈现侧面。仅此一项限制就决定了整个实现方式。

让侧滑与翻转联动

根据翻转角度驱动横向运动,并让二者相位相差 90 度。叶子侧边朝向观察者、切入气流时会横向滑动,正面平展时则会停滞:

l.x += Math.sin(l.spin) * l.slip * dt;   // fastest when cos(spin) ≈ 0
l.y += l.fall * dt;

不要在 x 上叠加独立的正弦波。那会让效果看起来像风,或是缓动出错。这种联动只需增加一项,就能让轨迹显得符合空气动力学。

烘焙正反两面

为每种颜色烘焙两张精灵图,并根据翻转方向选择:

const img = Math.cos(l.spin) < 0 ? sprite.back : sprite.face;

让背面比正面更暗、更淡。否则叶子看起来就像旋转的平面剪纸;有了明暗差异,它才会像有正反面的实体。这是系统中成本最低的真实感提升方式。

为每片叶子设置不同参数

在生成时随机设置下落速度、翻转速率、滚转速率、侧滑幅度、相位、缩放和不透明度。任何一个参数都不要共用,否则叶片效果就会退化成沿屏幕滚动的纹理。眼睛大约两秒就能发现共同的节奏。

选择叶子的重新出现位置

这决定了屏幕上实际有多少片叶子。

在二维中,让叶子在视口范围内循环。当叶子经过底部时,在顶部上方以新的随机 x 坐标重新生成。也要让 x 坐标循环,这样风就不会让一侧空空如也。

在三维中,让叶子在摄像机前方循环,而不是围着摄像机循环。以摄像机为中心的带状区域,大部分体积都在视锥体后方和侧面;对于 36° 的摄像机,几百片叶子中只有十来片能进入画面。改为将它们放入沿摄像机视线向下延伸的圆盘中,同样数量的叶子在画面中就会多出好几倍:

camera.getWorldDirection(fwd); fwd.y = 0; fwd.normalize();
const cx = camera.position.x + fwd.x * AHEAD;
const cz = camera.position.z + fwd.z * AHEAD;
const a = Math.random() * TAU, r = Math.sqrt(Math.random()) * SPREAD;
l.x = cx + Math.cos(a) * r;  l.z = cz + Math.sin(a) * r;  l.y = camera.position.y + 16;

保留远处循环机制作为后备,以应对摄像机走出自身天气范围的情况;并将其放在雾效范围之外,以免看到叶子突然跳转。

根据带状区域面积而非数量设置密度

屏幕上的密度与数量 ÷ 带状区域面积成正比。增加数量之前,先缩小带状区域:

  • 循环半径减半,在数量不变时,屏幕上的密度会变为四倍。
  • 数量翻倍,在屏幕上的增益相同的情况下,绘制成本也会翻倍。

按视口面积缩放设定的数量,而不要照搬原始数值;否则,桌面端看起来舒缓飘落的效果,在手机上就会变成暴风雪:

const k = clamp(Math.sqrt((W * H) / (1440 * 900)), .5, 1.3);
const n = Math.round(authored * layerShare * k);

分层营造景深

使用两到三层,每层分别设置缩放、速度、不透明度和模糊:

层缩放下落速度不透明度备注
远景0.3–0.5慢0.22–0.40最先绘制,可置于内容后方
中景0.5–0.85中等0.46–0.78效果的主体
近景1.05–1.9快0.50–0.82数量少,可选择模糊,绘制在内容上方

让近景叶片从文字前方掠过。这种交错效果就是景深线索。这里放两三片叶子就够了,不要做成一道帘幕。

处理颜色和光照

  • 从一个小范围的色阶中为每片叶子取样——从深牛血红到朱红,再到干琥珀色,并让每片叶子的饱和度有所不同。每片叶子都用同一种红色,一眼就能看出来。
  • 3-D 叶片使用 Alpha 测试,而不是 Alpha 混合,这样它们在任何角度下都能正确排序,无需逐帧进行深度排序。
  • 经过色调映射合成后,发光的红色会变成粉色。 如果叶片在暗场景中自发光,请将绿色和蓝色设为零(0x780200,而不是 0x8c1410),否则整个秋景都会变成糖果色。

控制开销

逐片更新的开销可以忽略不计;几百片叶子的三角函数运算,连 CPU 分析器的采样阈值都达不到。开销主要来自像素填充和绘制:

  • 2-D canvas:每片叶子调用一次 drawImage。将精灵预渲染一次,尺寸取你会绘制的最大值。切勿逐帧重新绘制叶片路径。
  • 3-D:使用一个 InstancedMesh,将矩阵合成为共享的 Matrix4。将临时用的 Matrix4/Quaternion/Euler/Vector3 提到模块作用域。
  • Alpha 测试会使叶片无法利用早期 Z 测试,因此其每像素开销会高于三角形数量所暗示的水平。宁可使用更多、更小的叶片,也不要使用更少、更大的叶片。

停止不可见时的工作

  • 在 document.hidden 或所在区段离开视口时(IntersectionObserver)暂停。恢复时重置 lastTime,避免恢复后的第一帧累积整个暂停期间的时间。
  • 将 dt 限制在约 1/30 秒,避免卡顿导致场景瞬间跳跃。
  • 将设备像素比上限设为 2。
  • 使用根元素上的 ResizeObserver 来设置尺寸,而不要只在脚本运行时测量一次。若页面稍后才完成布局,canvas 就会永远保持 0×0,因为它等待的 resize 事件已经触发过了。
  • 构建时要防止视口尺寸为零。若控件通过一次性运行来接入,就可能在首次布局前调用构建器;若不做保护,整个场景都会堆在原点。

尊重读者

当 prefers-reduced-motion: reduce 生效时,渲染一帧构图良好的静态画面,不要播放动画。不要直接隐藏叶片;构图本来就包含它们。控件变化时重新绘制这帧静态画面,让控件仍然能发挥作用。

验证

  • 叶片翻转时会经过侧对镜头的状态;叶片会明显变薄,并在每次翻转时消失一次
  • 滑移由翻转角度驱动,而不是由独立的正弦函数驱动
  • 正面和背面有所不同
  • 每个参数都因叶片而异
  • 在增加数量之前,尽可能缩紧回收带
  • 3-D:在镜头前方回收,而不是绕着镜头回收
  • 3-D:发光红色的绿色和蓝色设为零
  • 数量随视口面积变化;读数显示实际构建的数量
  • 隐藏或离开屏幕时暂停;dt 已限制;DPR 上限为 2
  • 减少动态效果时显示经过设计的静态画面
  • 在 390px 和 1440px 下控制台无错误

评论

支持 Markdown