关于 paper-cinder-transition
用于制作场景切换火焰过渡的图形技能,采用冻结的旧画面、实时新场景和逐像素火焰前沿合成。适合需要火焰揭示效果的页面作者,并提供渲染、性能与无障碍规则。
主要功能
- 01冻结旧帧并实时绘制新场景
- 02按像素距离合成火焰前沿
- 03生成余烬、焦痕与孔洞效果
- 04烘焙噪声并裁剪场景渲染范围
- 05支持减少动态效果与页面生命周期
适用场景
- 01为章节切换制作火焰揭示效果
- 02让火焰边缘呈现撕裂与余烬变化
- 03控制全屏过渡的渲染开销
- 04为减少动态效果偏好提供静态切换
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill paper-cinder-transition安装后在 Claude Code 中输入/paper-cinder-transition确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
demo.htmlPROMPT.mdagents/openai.yaml详细介绍
纸烬转场
页面不会淡入下一个页面,而是被一条火线吞噬。当燃烧效果是加载屏幕上的环形动画,且必须在主线程阻塞时仍能运行,请使用
fire-paper-loader;当燃烧效果是你负责的两个场景之间的切换时,请使用此方案。对于通过遮罩写入而非烧出的文本,请使用 masked-reveal;对于没有燃烧效果、由滚动控制进度的序列,请使用 scroll-scrubbed-visual-sequence。
运作机制
一次全屏渲染会根据到嘈杂前沿的有符号距离,逐像素判断该像素属于冻结的旧帧(火线前方)、燃烧区域(火线处),还是实时的新场景(火线后方)。 其他一切都是准备工作。移除这三个图层中的任何一个,效果就不再像燃烧:没有冻结帧,旧页面就会在火焰下继续移动;没有实时场景,揭示的就只是一张静止画面;没有逐像素精确的距离,边缘就会变成模糊的色块。
xc = signed distance to the front in CSS px (+ ahead, - burnt)
reveal = clamp(0.5 - xc/aa, 0, 1) // new frame from the front back, cut to the pixel
col = mix(fromFrozen, toLive, reveal)
col = mix(col, charcoal, bed * pores) // 26 px band of torn, porous char (alpha-masked)
col += flame(xc) + charGlow(xc) // additive: thread, ember zone, tight glow
分工准备
| 属于此技能 | 准备工作(可自行修改) |
|---|---|
| 合成顺序、距离场、余烬色阶、焦痕规则、噪声烘焙 | 两个场景的内容、场景配色、排版、页面文案 |
| 两个场景缓冲区(冻结的来源场景、实时的目标场景)和裁剪区 | 火线从哪一侧进入(见下文) |
| 时序、缓动、DPR、减少动态效果时的静态画面 | 控制界面 |
规则及其防止的问题
绘制冻结的旧帧和实时的新场景,绝不要让两者相互淡入。 转场开始时,只将离场场景快照一次,存入全分辨率目标缓冲区(时钟冻结),并持续实时渲染进入场景。交叉淡化,或在仍在动画的旧场景上叠加燃烧效果,会让同一屏幕区域同时显示两个章节,火焰也就不再是改变页面的主体。
以像素为单位测量前沿,而不是以场场单位测量。 xc = (field - T)/grad * pxHeight / pxScale。
单独使用等值线时,噪声顺着扫描方向倾斜的位置会形成细如发丝的接缝,逆着扫描方向倾斜的位置则会形成宽缝,因此火焰宽度会无故不均匀。
用相隔数个像素的有限差分求斜率,绝不要对烘焙纹理使用 dFdx。
烘焙噪声在每个纹素内的斜率都恒定;逐像素采样会让色带横穿火线形成条纹。以 +4 px 的间距在 x 和 y 方向采样宽场(hStep = 4*uPxS/uPxH),并将梯度限制在 0.35/aspect .. 3/aspect。
余烬区域应是平滑的发光渐变,而不是分段色带。 其症状是看起来像不真实的海报。根据热度变量连续调色:火线前沿为黄色,然后过渡到橙色,再到边缘清晰、参差不齐的深红色焦边。高热区域会让黄色向后延伸得更远。色阶常量(线性空间,色调映射前):
| 色阶 | rgb | 位置 |
|---|---|---|
| 深色 | 0.34, 0.022, 0.003 | 焦边,tau 0.05 |
| 橙色 | 0.86, 0.20, 0.024 | tau 0.42 |
| 金色 | 1.02, 0.38, 0.055 | tau 0.76 |
| 金黄 | 1.10, 0.64, 0.17 x (0.30 + 0.70*hot) | tau 高于 0.86 |
| 火丝 | 1.40, 0.56, 0.09 至 1.85, 1.02, 0.30,按热度变化 | 火线前沿 |
| 辉光 | 0.95, 0.20, 0.022 x 0.60 | 火丝周围 2 到 5 px |
高热颜色应保持金黄,绝不要发奶油色。 浅黄色在高热时会呈现奶油色,变暗时则会呈现卡其色。色阶中没有灰色或白色;低热区域应沿色阶向下变化,而不只是单纯变暗。
火线前沿应是一条明亮的火丝,带有珠状起伏和收窄处,宽度为 0.7 到 5 px,而不是均匀的笔画。 均匀的笔画看起来像霓虹灯。宽度为 (0.9 + 2.6*heat) * bead,其中 bead 根据沿边缘的噪声在 0.5 到 1.5 之间变化。只添加紧贴火丝的辉光(exp(-|xc+0.5wc| / (2 + 2.8*heat))),不要添加宽阔的光晕。
必须明显移动,即使扫掠静止时也是如此。 症状:“太僵硬、静止”。用沿边缘双向移动的脉冲带动热流(sA*0.018 - t*1.30、sA*0.027 + t*1.70),以 t*7 闪烁,让一股热浪以 -t*2.6 沿区域回卷,让火星以 -t*0.8 飘移,并让火舌在前沿舔动(t*1.1、t*2.8,屏幕占比最多为 0.022 和 0.010)。静帧会掩盖所有这些效果,因此请在实时播放的片段中判断燃烧效果。
用 Alpha 遮罩处理黑色焦痕;它不是一整块平板。 焦痕是一条宽 26 px(前沿另留 12 px 缓冲)的带状区域,会沿着撕裂的远端边缘从实时画面上剥落,随着老化逐渐出现孔隙,露出下方的内容。实心黑条看起来像擦除。覆盖率 = bed * pores,边缘以 8*noise(0.08) + 3*noise(0.25) px 撕裂,这比前沿自身的坡度更平缓,因此会形成毛边,但不会切出孤岛。
绝不要给孔隙围上发光边缘。 边缘会沿焦痕边缘连成一条描绘出的轮廓。孔隙只是洞。
焦痕的边缘取自宽阔的前沿,而不是精细前沿。 独立的二维噪声会产生漂浮在新画面上的焦痕孤岛。只沿边缘使用噪声(kbEdgeNLine),并令 edge = zoneW + fs + bedW*(0.35 + 1.10*n),其中 fs 最大为 12。
在火焰前方连续地熏黑旧画面,延伸 20 到 40 px,通过 tk^2 从棕褐色 0.80, 0.62, 0.42 过渡到深棕色 0.20, 0.10, 0.045,延伸距离随噪声变化。分成三个独立步骤会画出跟随火焰轮廓的线条。
预先生成噪声;不要逐像素叠加 fbm。 在 2880x1800 分辨率下,逐像素 fbm 每次处理耗时 1.5 到 3 ms;使用一张可平铺纹理和一张哈希表,在视觉效果相同的情况下耗时 0.3 到 0.6 ms。预先生成一次:一张周期为 16 个单元的 1024^2 RGBA16F 纹理(r 为 3 个倍频程的 fbm,g 为 3 个倍频程的脊状噪声,b 为梯度,a 为 2 个倍频程的脊状噪声,全部以 x*0.0625 采样),以及一个存储在 2048^2 R16F 表中的整数哈希格点;在平滑步进的分数部分处进行滤波,使结果与值噪声完全一致。
将实时画面裁剪到已显露的一侧,并以较小尺寸渲染。 只渲染线条后方的内容,另外加上屏幕尺寸的 0.16,用于海湾区和薄雾。燃烧期间以 0.75 倍比例绘制画面(合成画面仍保持全尺寸,因为人眼会注意边缘);过渡静止 150 ms 后,在 0.3 s 内淡入全尺寸画面,同时绘制两者,这样既不会看到清晰度变化,也不会影响动态帧的性能。将文字以全尺寸放在合成画面的实时一侧;如果在 0.75 倍缓冲区中绘制,文字会变模糊。
在噪声无法影响到的区域跳过噪声计算。 按 hx 判断:如果位置超出前沿前方 0.215 + 0.15,返回冻结画面;如果位于后方超过 0.25 - 0.215,则返回实时画面。在中间带状区域内,若位置超出前沿前方 62 px(加上缓冲),或位于后方 125 px,也执行相同操作。这样才能承担全设备分辨率的计算开销。
从新文字所在的一侧开始扫掠。 hx = dir>0 ? uv.x : 1-uv.x;章节文字在右侧,就从右侧烧入。向后播放时也使用目标章节所在的一侧。始终从左向右移动的线条会最后才扫过自己的新标题。
为每次燃烧设置种子。 seed = (chapter+1)*17.3 用于确定沿画面分布的 2.0 到 3.6 个宽阔海湾、它们的深度(0.055 到 0.115)及倾斜度((sv.x-0.5)*0.07),从而确保每次燃烧的轮廓都不相同。
常量
| 项目 | 值 |
|---|---|
| 带状区域半宽 | 0.215 个屏幕高度 |
| 前沿,线性 | -0.26 + 1.62 * rc,rc = clamp((f-0.01)/0.97),进入和离开时均在画面外 |
| 时长 | 默认 3.0 s;1.5 到 5 s 均可完整读取 |
| 线条噪声尺度 | 宽尺度 q*1.10(0.055-0.115)、q*2.60(0.050-0.030);细尺度 0.014、0.006、0.0025 |
| 余烬区域宽度 | 12 到 66 px,(18 + 20*min(w,2.6)) * (0.55 + 0.45*life) |
| 炭痕 | 基底 26 px,表层 3 到 6 px,水墨 0.0085, 0.0062, 0.0048 |
| 热浪 | 约 uPxH 的 1.8 px,仅限前沿 18 px 范围内 |
| 泛光 | 亮部通道阈值 1.15,半分辨率和四分之一分辨率模糊,权重 0.26*4 和 0.34*4 |
| 色调 | 暗角 0.42 + 0.58v、ACES、sRGB、颗粒 0.003 |
| 实时场景 | 过渡期间缩放为 0.75,停顿 150 ms,交叉淡化 0.3 s |
| 预算 | dt 限制为 1/30 s,DPR 上限为 2 |
常见问题,按症状排列
- 火焰是一条黑炭上的细红线,没有余烬区域。 烘焙噪声和哈希采样器未绑定,因此
life读取的是帧纹理的值。绑定uKbN和uKbH。 - 线条后面出现杂乱的空白,或发生硬切换。 实时场景只在裁剪区域内绘制(
front + 0.215 + 0.16);边距过窄会让合成过程读取未绘制的纹理像素。 - 炭痕边缘出现阶梯或 2x2 块。 在跳过的分支之后计算了导数。根据场自身的尺度计算像素变化(
kbAA(v, w))。 - 新画面上漂浮着零散的炭痕。 远侧边缘是从细噪声中裁切出来的;应从宽尺度前沿取得。
- 余烬边缘内侧出现乳白色带状区域。 用曲线提亮黑色会让纸面上的区域泛白;应改用增益来照亮暗区。
- 燃烧结束后,线条后面的场景看起来模糊。 0.75 尺寸的缓冲区仍在使用;停顿间隔结束后运行全尺寸交叉淡化。
- 耗时看起来很长。 隐藏的 Browser 窗格仍在渲染并占用 GPU;使用带 GPU 标志(
--use-angle=metal --enable-gpu)的无头浏览器测量,并将窗格停放。噪声烘焙并对场景进行裁剪后,Kibori 的过渡在 120 Hz 上限下保持 p50 8.3 ms;以相同方式测量,本演示在 1440x900 下燃烧效果的 p50 为 8.3 ms,p90 为 8.8 ms。 - 前沿经过时,颗粒和余烬在游动。 将它们固定在纸面上(使用纸面坐标中的
g,再加上每次燃烧的偏移),不要放在屏幕空间中;只有前沿会移动。
无障碍与生命周期
prefers-reduced-motion: reduce:不播放燃烧效果。将每个场景在固定时间渲染为设计好的静态画面,并在 0.35 s 内(smoothstep)交叉淡化冻结帧和实时帧。控件仍可使用。- 当
document.hidden为真或画布离开视口时暂停;恢复时重置时间基准,避免第一帧累积暂停期间的时间。 - 将
dt限制为 1/30 s,DPR 上限设为 2,根据ResizeObserver调整尺寸,并且在视口尺寸为零时不创建任何内容。 - 燃烧期间按下控件会将目标加入队列;前沿离开后执行最后一个目标。
- 使用真实按钮、方向键和 1 到 3 切换章节,并通过实时区域播报到达的章节。
成本
在源页面中测得,成本来自逐像素噪声,而非火焰。烘焙噪声,并在远离前沿处恢复纯场景,是关键优化手段。其次是 0.75 尺寸和裁剪。不要在合成阶段追求 MSAA:边缘由 kbAA 绘制到像素级。
来源
内容摘自 KIBORI,这是一个 WebGL 木工工坊页面,各章节以燃烧效果相互过渡,页脚的纸张燃烧效果塑造了视觉风格。demo.html 中的 EDGE GLSL(火焰、炭痕、孔隙、表层、焦痕)和燃烧通道均来自该页面,经过移植,并将场景替换为三个由着色器绘制的房间。