Tephra v8
SkillDev tools把一张图重画成黑底像素画,两种正典画法按题材二选一:**纯粒子**(会动的短笔触沿图像自身 的结构方向流动,适合浪/烟/光柱这类本来就在流动的东西),或**网点实体**(把明暗翻译成 逐帧静止的有序点阵,适合人像/雕塑/动物这类要读出轮廓和体积的东西,并且只有它能把形体 梳成硬边碎裂条)。动效按画面的物理规律分区规划;可选地让一部分形体溃散成飞灰,同时锚定 另一部分保持实体与原色。输出静态 PNG 与无缝循环 GIF/MP4。 触发:/tephra、「粒子流动效果」「把这张图重绘成笔触」「黑底像素画」「网点碎裂」。 算法与代码全部原创,输出不基于任何模型,也不复制任何具体作品。
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Tephra v8 skill
What this skill tells your AI
The instructions your AI receives, as published by haorantang97/labart in plugins/tephra/skills/tephra/SKILL.md and read by ahel’s review.
核心脚本 scripts/tephra.py,确定性(同参数同 seed 逐字节相同)。
改代码时的铁律:不要往共享
rng上加无条件的抽取。 每次渲染都会跑到的新噪声场,必须自己开一条流(np.random.default_rng(seed * 常数))。 从共享流上抽会把后面所有抽取(笔触播种、雨点)整体挪位,同一条命令换个版本就渲不出 同一张图了。0.2.0 修的就是这个(湍流卷曲场_n1)。 交付时必须报出完整命令——那就是版本号。
先读:工作流(七步,不要跳)
这套东西最常见的失败不是参数调错,是跳步。按顺序走:
- 叠网格 在源图上叠 0.1 间距的归一化网格,所有坐标参数(漩涡中心/分区/锚定)从图上读出来,不要估。
- 体检
python3 scripts/dp_fitness.py 图.png [--invert]。不过就别硬渲,回第 3 步。 - 前处理 见下「M0.5」。改的是明暗,不是几何。
- 列元素 → 逐个判物理 画面里有哪几样东西,每样在物理上往哪走。见「M3 物理对照表」。
- 写成分区 一个元素一条
--flow-region;天然同心的结构才用--swirl。 - 渲
- 验 相位互相关逐区量位移,与第 4 步的判断核对。方向靠眼睛看会错。
两种正典画法(先选一种,再往下调)
这套东西有两条都成立的正典,各自对应一批不同的参考作品。选错了后面全是白调。
同一张源图、同一个主体,左 A 右 B(动图:assets/examples/canon-modes.gif)。
两边都是各自路线调到最好的版本,不是「同参数开关 mass」——把 B 的参数原样关掉 mass,
画面会直接空掉(B 的 --density 0.02 几乎没有笔触,整张画都靠网点层撑着)。
| A · 纯粒子 | B · 网点实体 | |
|---|---|---|
| 一句话 | 用会动的短笔触把图重画一遍 | 把明暗翻译成一层静止的有序点阵 |
| 参考作品 | 样本J 的「尘」那一层 | 样本A / 样本K |
| 开关 | --mass 0(preset flux 的默认) | --mass 2.6 + 原生网格渲染 |
| 主体逐帧 | 一直在动(帧间留存 ≈ 0.83) | 完全静止(≈ 0.999) |
| 落点怎么定 | 按亮度加权随机采样 | 跟固定 8×8 Bayer 表阈值判定 |
| 质感 | 密集绒毛状笔触,边缘硬 | 规则点阵,能读出连续调子 |
| 擅长 | 浪、烟、光柱、火、水花——本来就在流动的东西 | 人像、雕塑、动物、建筑——要读出轮廓和体积的东西 |
| 不擅长 | 人像(糊成一团毛,读不出结构) | 流体(点阵钉死,动感全没了) |
A · 纯粒子——裸跑即正典
python3 scripts/tephra.py in.png -o out --size 1400 --seconds 1.6 --fps 25 --mp4
默认 preset flux 就是它,--floor auto 从这张图自己的直方图读。
不要为了「用上正典」手打一长串参数——那串就是默认值。
B · 网点实体——必须走原生网格
python3 scripts/tephra.py in.png -o nat --size 768 --supersample 1 --palette ash \
--mass 2.6 --density 0.02 --sparsity 6.0 --floor 0.008 --contrast 1.15 --coherence 16 --stroke 3 \
--dissolve 溃散方向,0.45,0.40 --dissolve-patch 0.35 --escape 0 \
--comb-life 0.04 --comb-emit 0.2 --comb=梳向,2.3,30,1.45,0.16,5,0.70,66,0.24,7.0,0.26,3.6 \
--seconds 0.96 --fps 25 --mp4
python3 scripts/upscale_nearest.py nat.png out.png 1400
细节与实测验收表见 M4。这一路必须在 512–768 的原生网格上渲再 NEAREST 放大—— 直接渲 1400 会把碎料糊成烟,这是最容易踩的一个坑。
--comb的碎裂条只能从网点上梳出来。 没有 mass 层就没有点阵可梳,梳笔触只会得到一团运动模糊。 所以 B 那种硬边碎裂条在 A 里做不出来——不是参数没调对,是缺一层。
--mass不是风格开关,是亮度门槛。 它只决定「多暗算暗到不画」。 画出来长什么样由源图的明暗分布形状决定,不由这个数决定: 侧光雕塑的调子摊在中间段 → 网点连续过渡,读得出体积; 逆光剪影被相机压死、内部几乎一个值 → 近三成面积顶到 90% 以上,读起来是一块剪纸。 (实测:同一套参数,雕像只有 8.5% 的面积超过 80% 占空比,马是 29.8%。) 想让剪影也有层次,要在 M0.5 前处理里造体积明暗,改--mass没用。
共用的加料(两条路都适用)
| 想要 | 加什么 |
|---|---|
| 换色 | --palette gold / ember / blood / violet / phosphor / neon |
| 虚空不够/主体发碎 | --floor 0.05 覆盖自动值 |
| 白底图或暗主体 | --invert(注意副作用,见 M0.5 P1) |
| 焦点星爆 | --burst-at X,Y,强度 |
| 三维漩涡 | --swirl X,Y,S,R,RATIO,TILT,PITCH |
| 保留一个实体物 | --anchor X,Y,RX,RY[,S][,#色][,块][,曝光][,黑点分位][,彩度] |
| 一部分溃散 | --dissolve DEG,START,SOFT + --dissolve-patch 0.5 |
模块(一个模块只负责一件事)
铁律:模块之间不许偷偷改对方的参数。
历史教训:--dissolve 曾经把 --mass 从 0 改成 1,于是「只加一点溃散」实际换掉了整张图的画法。
M0 输入判定
scripts/dp_fitness.py。判据:中位亮度 < 0.20 直接过;否则要求可分离性 > 0.55。
(依据:4 张成功/失败样本实测。中位亮度低 = 虚空是图自带的;可分离性高 = 删哪儿由图决定不由你猜。)
M0.5 前处理(只改明暗,不改几何)
| 规则 | 触发 | 手段 |
|---|---|---|
| P1 反相 | 主体比背景暗 | --invert。副作用:主体自己的高光会变成 floor 首先删除的对象,造型强的物体会掉一截 |
| P2 去梯度 | 大尺度亮度梯度(大气/景深/渐晕) | 减一个超大尺度高斯背景再归一化 |
| P3 裁非画面物 | 水印/字幕/拼接缝 | 裁掉,不要修补——修补留下的直角缝会被忠实渲染成方块 |
| P4 换轴 | 主体与背景亮度接近(差 < 0.05)但纹理差 1.5 倍以上 | 用局部对比度当门槛 |
| P5 重打光 | 前四条都救不回来 | img2img,denoise 0.25–0.35,几何锁死,结构保真 > 0.55 |
什么时候必须上 P4:主体上某个你要保住的部件(耳朵、下颌、发丝)与背景中位亮度差 < 0.02。
量一下再决定——雕像的耳朵和背景烟都是 0.055,试多少个 --floor 都是白试。
现成脚本:scripts/p4_mask.py 源图 输出.png(小而亮的东西如余烬会被纹理判据误杀,脚本单独保留)。
P4 的陷阱(必读):纹理能量本质上是边缘检测,直接拿它当形体会把实心物体变成铁丝网—— 只有一圈亮边、内部是空的,粒子化之后糊成一团。正确用法是: 纹理只用来造「区域掩膜」(闭运算→填洞→大尺度平滑),形体交回给主体自己的明暗。 这样还有个好处:背景已由掩膜杀掉,就不需要反相,主体内部直接用源图明暗—— 受光处密、背光处疏,「张弛有度」是光影本身给的,不用另外发明规则。
M1 形体
互斥三选一,前两个都是正典,按上面那张表按题材选:
A 纯粒子 --mass 0 / B 网点实体 --mass 2.6(要碎裂条就只能选它) / 局部锚定 --anchor。
--anchor 不做主体分割:逐 RGB 通道对源图抖动,暗的自己不印、彩的自己出色,
黑点分位从椭圆区内自取(区内多数面积是背景 → 背景自动归零)。
彩度提升必须按已有彩度开门,否则中性色的微弱色偏会被放大成假色(白浪变青)。
M2 笔触质感
--stroke 最长笔长 / --length-mix 长短偏斜 / --aniso-len 各向异性区才给长笔 /
--fray 子段错位 / --gap 断口 / --jitter --curve 歪与弯 / --density --sparsity。
笔长必须小于你要读出来的最小部件。一根 22px 的笔画不出一个 25px 的蹄子,只会盖过去。
细节多的图:--stroke 10 --coherence 18;大形为主:--stroke 22 --coherence 45。
M3 流动(三层,各自独立、都可局部化,权重最强者赢)
| 层 | 参数 | 何时用 |
|---|---|---|
| 全局朝向 | --flow-bias / --flow-from-burst | 兜底 |
| 分区朝向 | --flow-region X,Y,RX,RY,ANGLE[,S] | 默认手段,一个元素一条 |
| 分区旋转 | --swirl X,Y,S,R[,RATIO,TILT,PITCH] | 只给天然同心的结构 |
物理对照表:光柱/雨/瀑布/坠落=90° | 火焰/余烬/上升烟=−90° | 破碎浪的浪背≈−40° |
浪的桶内/发髻/年轮/星系=--swirl | 爆炸碎片=--flow-from-burst | 岩石/建筑不动,什么都不给。
拿不准就问:关掉重力和风,它还会往那个方向动吗?不会就是编的。
M3.a 三维漩涡:RATIO = 投影椭圆的短长轴比(必须实测,拟合泡沫环主轴),TILT 长轴倾角,PITCH 径向内收。
在水面平面里旋转再把速度投影回画面——透视压缩自动产生(端部慢、鳍部快),速度按 1/r。
平面圆形漩涡会把水推着横切过所有的圈,读起来就是一个转盘。
--conserve 1.5 必开:消除吸引子造出的假轮廓。
M4 溃散(可选,非每图必用)
最关键的一条:在原生网格上渲,再 NEAREST 放大
样本A 的原生画幅是 512×384(GIF 容器 2048×1536,4×4 块 100% 一致,实测), 墨严格二值(只有 0 和 210 两个值),点亮率 10.2%。 一个"像素"= 画幅宽度的 1/512。 直接在 1400 宽上抖动,格子就细了 2.7 倍—— 单个像素永远读不出来,整片碎料糊成烟。这是"感觉像烟不像破损"的唯一根因。 做法:
--size 512 --supersample 1渲,再用scripts/upscale_nearest.py放到交付尺寸。 验收用scripts/native_check.py 成品.gif,逐项对照下表实测值。
指标(512 网格) 样本A 实测 点亮率 10.21% 实体(≥80% 帧点亮) 8.18%,逐帧完全静止 1.000 碎料 2.03%,1101 个独立块/帧 单块面积 中位 2,p90 8,p99 25,max 36(最大约 15×10) 碎料运动 每帧漂移 +1,+1 px,最佳平移后仍每帧死掉 61% 碎料射程 184 px ≈ 1.0 S 漂移与消亡是两件事:慢漂移给方向感,逐帧重掷给闪烁感。只做其中一个都不对。
人像先看这条。 这一路的实体半边是平滑的有序网点(画法 B,
--mass 2.6),碎的半边是同一块网点被朝一个方向梳出去的 硬边条纹——不是粒子、不是脉络、不是喷射。用--comb ANGLE,LEN[,SPREAD,THIN,WOBBLE]。 梳出去多少,原处就少多少((1-R)^0.85),质量守恒是机制自带的,不用另外"咬"。 纯粒子--mass 0画人像是错的(MODULES E-8 早写过,我在这条上连续走了很多轮弯路)。--dissolve DEG,START,SOFT+--escape+--turbulence。与 M1 完全解耦,不会替你开 mass。
溃散是局部侵蚀,不是整体擦除。 原作实测(4 件,PROPOSALS_EDGE):
- 灰烬不是飞出去的,是生在那儿的。逃逸粒子寿命 2–3 帧、速度 1–5 px/帧,一生只挪 0.02–0.10 S。 远场密度是出生分布:近场指数(e 折减 0.05–0.10 S)+ 约 1/d 的肥尾,延伸到 ~0.9 S。 旧实现让每颗灰一生飞 0.22×画幅(长 10–30 倍),结果不是"崩解"而是一层均匀灰雾。
- 溃散侧的实体本身也要变稀:同等深度只有完好侧的 0.46 倍,从边界往里渐变。
- 散出去的东西要变成画面的一部分,不是消失。 实测 样本S 单帧:核内填充 0.16, 出核后掉到 0.05,然后从 0.25 S 一直到 3 S 保持 0.017–0.020 的平台不再衰减(是核的 0.10–0.12 倍)。 没有"淡出"这回事。材料一路铺到画外,靠结构——分叉的丝状脉络——继续成形。 连通笔画最长到 0.35 S(约单笔的 10 倍),这就是碎片长成翅膀/根系/神经的物理。
- ⚠️ 不要用"绕核心质心的径向平均"去判断飞得远不远。翅膀是一个窄的定向叶: 在 ±15° 扇区里 d=1 S 处可能有 0.10 填充,除以 360° 平均下来只剩 0.008,和"什么都没有"无法区分。 本项目就是这么得出过"原作飞得不远"的错误结论(第 4 次栽在自选指标上)。要用扇区剖面或连通长度。
- 前沿是咬出来的:核会裂成 ≥2 块,轮廓粗糙度 2.3–6.0(光滑圆 ≈1.3),前方有脱离主体的离岛。
- 完好侧是真空崖:0.03 S 内从 0.86 掉到 0.006,崖外纯黑。
- 墨是二值的:四件各只有一个亮度值。淡出靠存活抽签,不靠 alpha。
| 参数 | 默认 | 含义 |
|---|---|---|
--dissolve-life | 0.10s | 溃散粒子寿命;每个周期在新位置重生(模周期数保无缝) |
--dissolve-near | 0.08 | 近场 e 折减,单位 = S。不要拿它当"飞多远"的旋钮——调大它是把整朵云一起加厚,出来是一张均匀灰毯(实测:调到 0.26 后断口只有参照的 0.3 倍、远场是参照的 3.8 倍,contrast 从 63:1 塌到 5:1)。要飞得远,调 --dissolve-reach |
--dissolve-reach | 0.9 | 肥尾能到多远(单位 S)。肥尾只带少量粒子,拉远它不加厚 |
--dissolve-floor | 0 | 按面积均匀撒出去的比例 → 远场是平台而不是衰减尾。要"散出去的成为画面一部分"就开 0.3–0.5 |
--dissolve-strand | 0.6 | 逃逸位移沿图像自己的流场走而不是直线弹射;越小越跟着场走 |
--spray | — | N,ANGLE,FAN[,SPEED,SPAN] 把碎掉的像素推出去。和雨点是同一套引擎(短命闪现、小块、逐条抽角度),区别只有两点:发射点是碎块本身(按溃散场播种),角度是围绕 ANGLE、半角 FAN 的扇形。碎片于是离开形体、越飞越开,而不是待在原地 |
--filament | — | N,LEN[,BRANCH,WIDTH,GAIN] 让碎片长成脉络。在溃散前沿播 N 根,顺流场积分 LEN×S 长,逐级分叉,画进 dust 权重里让笔触沿着它排队。这是"翅膀/根系"的唯一来源——散点无论多少都长不出结构 |
--dissolve-min | 0.05 | 最小逃逸位移(不设下限的话灰烬落回自己核里,溃散侧永远稀不下来) |
--dissolve-tail | 0.25 | 走肥尾(远场基座)的比例 |
--dissolve-patch | 0.35 | 局部性。0 = 旧的单调半平面梯度(像扫描线擦除);0.45–0.6 出咬痕和离岛 |
--escape | 1.0 | 现在的含义是逃逸概率倍数(P(出走)=clip(DIS×escape)),不是"飞多远" |
--dissolve-keepout | — | X,Y,RX,RY[,S] 软椭圆:灰烬不许落进去。给画面里另一个要自己成立的元素留真空(余烬、亮窗、锚定物)。落进去的位移被拉短而不是删除,墨量不变;椭圆半径逐颗抖动 ±15%,免得出现一条一眼可辨的遮罩边 |
--escape-legacy | — | 回到旧的长途飞行,只为复现老图 |
三条硬规则:
- 必须留一块不散的。
--dissolve START要让 ramp<0.05 的区域覆盖主体的 ≥25%。 全散 = 每颗都出走 = 整体平移+糊,不是崩解(实测:马 START 0.38 时主体只剩 4% 不散,结果是一坨模糊)。 - DEG 要让识别特征排在最后。 沿 DEG 轴投影主体,识别靠哪儿哪儿就得投影值最小。 (雕像仰头,脸在"上"——用 −90° 会先吃眉骨和眼窝;换成 −30° 才是"脸留住、后脑散"。先算再定。)
--conserve会抵消侵蚀:它按"home 密度/当前密度"补亮,而溃散造成的变稀正是它要修的东西。 home 密度必须按逃逸后的周期平均位置算(脚本已改);别用旧版跑新参数。
M4.a 断口要切在哪:切过识别特征,不是绕开它。 参照 样本A 是「脸的左半完整、右半崩解」——溃散穿过脸,不是从后脑开始。 我在这个项目里连续几轮把断口放在后脑以「保住脸」,方向反了: 被打碎的那部分正是延伸并充实画面的材料,全绕开就只剩「一个完整的头 + 旁边一点灰」。
做法:把主体的识别地标沿 DEG 轴投影,读出各自的 t,再挑 START 落在地标之间。 雕像实测(DEG=0,t 越大越先碎):
| 耳 | 眼窝 | 鼻梁 | 颊 | 颅顶 | 后脑 |
|---|---|---|---|---|---|
| 0.06 | 0.28 | 0.39 | 0.67 | 0.64 | 1.06 |
START 0.20 → 耳与眼窝守住(ramp 0 / 0.20),鼻梁半碎(0.47),颊与颅顶全碎。
一句话:留住一只眼 + 一条轮廓就够认了,剩下的交给画面。
锚区仍要 ≥25%(见上文硬规则 1)。
溃散方向 = 该作品的全局流向下游(实测 4/4 件:溃散侧、粒子漂移、独立雨层三者同向,差 <30°)。
--dissolve DEG 应等于 M3 主 flow-region 的 ANGLE,不要独立拍——除非撞上规则 2,那时以规则 2 为准。
溃散比例有上限:辨识度靠轮廓的(马)不能多散;靠亮部脊线的(人像)可以散到 55%。
验收(scripts/m4_erode.py 源图 成品图 DEG START SOFT FLOOR [--invert]):
按源图算出的 ramp 给成品分箱(不是按成品自己的密度分——那是自指的),
fill 应随 ramp 单调下降,最深箱 ≈ 0.33–0.50 倍。
scripts/m4_check.py 成品图 DEG:完好侧 0.05 S 外 <0.008、粗糙度 2.3–6.0、核 ≥2 块,
并逐箱对照 样本A 实测剖面(偏离 >1.8× 或 <0.55× 会标出来)。断口/远场比值要在 20:1 以上。
M5 空域层(与 M1–M4 无关的独立层)
雨点的模型是照原作动画逐帧实测标定的,不要凭感觉改:
| 参数 | 值 | 实测依据 |
|---|---|---|
--rain-block | 1.1 | 原作网点面积中位 4px²@2560 ≈ 边长 1.1 输出 px |
--rain-short | 4 | 长度分位 4/4/5/7/17,中位只有 5px,长条只占 5% |
--rain-spread | 25 | 链间角差在 200px 和 1200px 上相同 → 逐条独立抽,不是平滑场 |
--rain-flow | 0 | 同上;平滑角度场是错的结构 |
--rain-life | 0.16s | 原位残留为零、像素存活 14.7%、轨迹从不超过 4 步 → 短命闪现不是巡航 |
--rain-px-per-sec | 0.6 | 位移中位 1600px/s @2560 = 0.62 画幅/秒 |
--rain-speed-len | 0.5 | 位移与长度相关 +0.36 |
--rain-taper / --rain-spacing | 1.0 / 1.0 | 链内无梯度。调成非 1 会出现流星尾,那不是这个语言里的东西 |
--rain-keepout X,Y,RX,RY | 视构图 | 软椭圆禁入:不生成也不飞入,边缘淡出 |
--rain-wild 一个数放大四个维度的离散度。重尾分布调大方差时必须同时封顶,否则调的不是「更乱」而是「偶尔炸一个巨块」。
M6 输出
--palette --size --seconds --fps --mp4 --seed。
无缝循环校验:|末帧−首帧| ≈ 相邻帧差。参照的节奏是 0.48s / 12 帧 / 25fps 的快循环。
调参纪律
- 不要用静帧调流动。
--static-only时 frames=1,粒子完全不推进——凡是涉及 flow/boil/conserve 的调参,静帧测的是另一张画。 - 不要相信自己临时写的分析程序。 本项目已三次栽在这上面:聚类阈值造出了原图没有的「彗星尾」;手放的取样框让同一张图得出 0.16σ 与 0.59σ 两个相反判断;「墨在核内的份额」这个指标是自指的(核由渲染图自己的密度定义),越加外围墨它反而越高。凡用到阈值/网格/聚类半径的地方,先换两三个取值看结论翻不翻。
- 参考是动画就必须按动画取证。 静帧给不出速度、寿命、相位。而且要确认抓到的是相邻帧——间隔 1 秒的截屏落在 0.48s 循环的随机相位上,测出来的位移全是假的。
常见失败
- 蚯蚓状意义不明的亮线 →
--conserve 1.5没开 - 光柱向上流 / 方向违反物理 → 用了全局 flow-bias 兜所有区,改逐元素
--flow-region - 满屏中长线发土 →
--length-mix调到 3,--density降到 1.5 以下 - 主体像剪纸/贴纸 → 先分清是哪一种。画法 A(纯粒子)出现这个,是做了无差别的边缘填充,去掉它; 画法 B(网点)出现这个,是源图内部被压死(近三成面积顶到 80% 占空比以上), 要回 M0.5 造体积明暗,不是回纯粒子——人像回了纯粒子只会更糊。量一下再决定: 按 8×8 格统计占空比,中间段(35–65%)占比低于 15% 就是压死了。
- 主体成了铁丝网(只有轮廓、内部空) → 拿纹理能量当形体了。见 M0.5 P4 的陷阱
- 雨点像蛆 → 给单根加了曲率。原作每根都是直的,活力来自多条不同角度的短命闪现交错
- 雨点像流星有尾巴 →
--rain-taper不是 1
边界与伦理
细节(五官、文字、小窗)必然消失;平缓明暗过渡会变均匀绒毛。适合「一个大形 + 强对比」。
算法与代码全部原创,输出由确定性脚本渲出,不含任何模型。参数中的目标常数来自对一批 公开发布的粒子动画作品的逐帧实测——测量的是可量化的统计特征(网格尺度、点亮率、 块面积分布、帧间留存),不复制任何具体作品的画面、构图或主体。不得以他人名义署名或宣传。
Signals
- GitHub stars
- 286
- Forks
- 16
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
tephra- Source
- github.com/haorantang97/labart