news 2026/10/9 5:14:42

Ice and Frost Skill VFX:用 Three.js 构建一套“冰与霜“技能视觉语言

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ice and Frost Skill VFX:用 Three.js 构建一套“冰与霜“技能视觉语言

【免费下载链接】Skills

Agent skills for designers and builders using Codex, Claude, Cursor, and other AI coding agents

项目地址:https://gitcode.com/gh_mirrors/skills48/Skills
点击查看免费下载

导读

本文围绕 Skills 仓库中 ice-frost-skill-vfx 技能 的核心文档展开,完整讲解如何在 Three.js 中为一款冰霜技能实现"从手中凝结、向地面蔓延、拔地而起、卷起暴雪、再到碎裂崩塌"的完整视觉语言:CPU 上生长的树状霜花、按自身平面光线追踪的棱面冰刺、原地翻涌的雾堤与螺旋粉雪暴风柱。读完你不仅能直接复用仓库中的两个可运行模块(frost-energy.mjs 与 frost-growth.mjs)搭出同样的五拍 demo,还能掌握这套效果背后"生长一次、按到达时间揭示""凸包精确追踪""单一风场驱动一切""分层数字即规格"的设计方法论,以及每条效果规则所预防的具体失败。

一次效果,五种身份

本技能适用于冰霜技能的"效果半场":手中凝结什么、地面蔓延什么、什么拔地而起、风做了什么、什么碎裂了。文档开篇即给出了明确的"何时去兄弟技能"的边界:

  • 闪电、能量球、黑色残影烟雾与空气冲击波:用lightning-energy-skill-vfx,本技能复用了它的时钟、合成、冲击帧与闪光限制器;
  • 火焰、烟雾、余烬与燃烧地面:用fire-smoke-skill-vfx;
  • 通用可读、预算友好的游戏内效果(预警、状态、清理规则):用create-game-vfx,其可读性规则同样适用于本技能;
  • 为技能打分或打磨施法者体型与节奏:用game-dev-combat-skill-polish。

效果中没有角色模型:霜花在一个"暗示的手"中于胸口高度绽放,随后落到雪地上完成其余全部过程——观众看到的纯粹是这套效果语言本身(参见 demo/src/stage.js 的注释)。

它的创作背景源自用户在 Black Lightning demo 上的批注:"不要太咄咄逼人""更细的线条""平滑过渡""更真实的地面""不要滑动纹理""阴影里更多细节"。随后经过了八轮自我批评、两轮针对暴风雪的评审,以及与 GPT Image 对页面自身帧进行编辑的三轮"梦境循环"(完整评分历程记录在 references/scorecard.md,页面得分 4.90 → 5.88 → 5.95 → 6.25)。

技术栈是自验的:Three.js r170、WebGL2、GLSLShaderMaterial(其中一个是 GLSL3 双目标烘焙)、带深度纹理的 HalfFloat 渲染目标、一个 96³ 烘焙噪声纹理、手写的 bloom、合成与 FXAA——没有任何后处理库。地面使用 Poly Haven 的 CC0 "Snow 02" 扫描(作者 Rob Tuytel)的 1k 颜色、法线及 AO/粗糙度贴图,位于 demo/assets/ground/,由 demo/build.mjs 以 data URI 内嵌,使页面可以从磁盘直接打开且不发任何网络请求。

核心机制:四条铁律

霜花只生长一次,由到达时间绘制;冰按自己的平面追踪;空气在冷气积聚处被行进。(SKILL.md "The mechanism" 一节)

  • 先生长霜花,再由着色器揭示它。growFrost在 CPU 上约 50 ms 内跑完一次小型扩散受限生长:主茎、芒刺与次级芒刺作为事件进入同一个按时间排序的堆,谁的尖端先到达一块地面谁就占据它,后来的尖端撞上就停下。每一段都携带前沿到达它的时间。整棵树被一次性烘焙进一张 2048 宽的 HalfFloat 距离场,地面着色器通过把"到达时间"与霜花时钟比较来画出发丝般的霜线。没有任何重掷或滚动:图案是长出来的。这段生长逻辑完整实现在 frost-growth.mjs 中:三级(LV)步长 24 / 14 / 8 mm、间距 55 / 10.5 / 6.2 mm、半宽 1.6→0.9 / 1.1→0.5 / 0.5→0.28 mm,芒刺以约 60°(冰的六方习性的 0.98–1.12 rad)离开主茎,次级芒刺再以 60° 离开芒刺——竞争把霜花挤成羽毛状而不是交叉线条。
  • 冰是可精确追踪的凸包。每根尖刺是一组平面(侧面、尖端刻面、切屑、底座)从一个立方体上裁剪而来。片元着色器折射进凸包内部,以封闭形式求出射出平面,在全内反射成立时内部反射,并采样一张带 mipmap 的世界通道副本来得到透过冰看到的东西。裂纹也是平面:尖刺沿观众亲眼看到开裂的那组平面碎裂。凸包裁剪(clipHull)、边缘倒角(bevelPlanes)与几何构建(hullGeometry)都在 frost-energy.mjs 中。
  • 碎块是只有一个接触点的刚体。每一块碎片与岩屑都保留自己的凸包顶点,因此每个子步都能知道最低角。带摩擦的冲量让它落地;当它慢下来时,会翻到最接近雪的那个面上休息——所以没有任何东西会立在尖角上、浮空或陷入雪里。
  • 雾是分堤行进的光线行进,不是卡片。每一条雾堤是一个被风拉长的椭球,以三分之一分辨率在自己的跨度上行进;域扭曲纤维以两个交叉淡入相位被平流,因此它在原地翻涌。fx.debug = 'mist'这个"只渲染雾"的调试视图正是当年校准它的工具。
  • 一个风场驱动一切。一个柱状涡旋(脚部入流、核心抬升、顶部出流、S 形轴心)驱动粉雪、雪片、发丝状飘带以及雾的旋转。其解析式在 frost-energy.mjs:切向风速swirl·2r·rc/(r²+rc²),雪面把旋转拖慢到 0.45,脚部入流 2.1、核心抬升 3.2、顶部出流 0.4。
  • 一个时钟与一个合成(与lightning-energy-skill-vfx相同):冻结(hold)会定住每一层,一个时间斜坡(time ramp)放慢裂纹,折射环进入半分辨率畸变缓冲,bloom 有高阈值,冲击帧在色调映射之后绘制。FXAA 只跑在冰通道标记的像素上,因为 HalfFloat 目标上的 MSAA 会解析成一堆垃圾。

复用现成效果:两行模块 + 一个循环

将 assets/frost-energy.mjs 与 assets/frost-growth.mjs 并排复制(frost-energy通过import { growFrost, mulberry32 } from './frost-growth.mjs'引用)。传入你自己的THREE,两个模块都不会再导入第二个副本。demo/index.html 运行的就是这两个模块,由 demo/build.mjs 内联(strip掉 import/export 后打包进window.FrostEnergy),因为从磁盘打开的页面不能 import ES 模块——这是你离线复用时的唯一注意事项。

文档给出的最小接入代码(完整继承,含逐行注释):

import { createFrostEnergy, LIGHTS_GLSL, FROST_GLSL, TNOISE_GLSL, HASH_GLSL, LAYERS } from './frost-energy.mjs'; const fx = createFrostEnergy(THREE, { renderer, scene, camera, seed: 11 }); // 烘焙 96³ 噪声 fx.setSize(width * dpr, height * dpr, dpr); // 绘制缓冲像素,resize 时再次调用 fx.options.shake = 0.6; fx.options.fisheye = 0.65; // 可感知但不对着观众砸 // 只生长一次霜花:一个种子点、一个前进方向、一个可达半径和它允许使用的地面区域。 const F = fx.growFrostField({ origin: new THREE.Vector3(1.05, 0, -0.08), dir: new THREE.Vector3(1, 0, 0), radius: 5.6, area: [-3.0, -4.8, 7.6, 4.8], seed: 7 }); fx.frost.t = 0; // 启动生长前沿;在效果时钟上推进它 // 沿霜花主茎放尖刺,然后构建雾与雪读取的障碍图。 const s = fx.spineAt(2.35); // { x, z, t, dir } 沿主茎 2.35 米处 const spike = fx.createSpike({ base: new THREE.Vector3(s.x, 0, s.z), axis: new THREE.Vector3(0.2, 1, 0).normalize(), height: 1.1, radius: 0.12, seed: 3, erupt: 0.7, rise: 0.27 }); fx.buildObstacles(); fx.state.spikeT = 0; // 喷发时钟;每根尖刺经过 `erupt` 时升起 // 地面(或任何道具):加入 TNOISE_GLSL + HASH_GLSL + LIGHTS_GLSL + FROST_GLSL, // 把 fx.lightUniforms、fx.frost.uniforms、fx.obst.uniforms 展开进其 uniforms, // 用 energyLight(P, N, V, albedo, rough) 打光,再用 frostAt(p.xz, metresPerPixel) 在顶层画霜。 // 放在 LAYERS.world 上。 function frame(realDt) { const dt = fx.update(realDt); // 模拟秒:hold 期间为 0,受斜坡缩放 fx.frost.t += dt; fx.state.spikeT += dt; fx.render(); // world、ice、mist、column、光效、折射、后处理 } // 先裂,再沿同一组平面碎裂: for (let k = 0; k < 4; k++) fx.crackSpike(spike, k, 0.12 + 0.14 * k, 0.4); // ...1.4 秒后: fx.shatterSpike(spike, spike.base, 1); fx.impact({ hold: 0.1, frame: 'full', shake: 0.02, at: spike.base });

公开 API 一览(来自 SKILL.md,均已在源码中实现)

调用说明
growFrost(opts)(frost-growth.mjs)返回{ seg, count, spine, paths, tips, duration, branches }:seg每段 12 个浮点(x0, z0, x1, z1, t0, t1, 半宽 0/1, level, rand, 弧长 0/1);spine/paths是主茎与各种子茎的[x, z, t]点(供贴在前沿的光);tips是用于闪光的分支末端。选项:seed、origin、dir、radius、area、speed(2.6 m/s)、stems(11)、density、symmetric(n 茎的星形霜)、fan、maxSegments。
fx.growFrostField(opts)运行growFrost,烘焙距离场(默认 2048 宽,res可调),构建尖端星与枢纽晶体簇,填充fx.frost.uniforms。fx.frost.t、.fade、.glow驱动它。fx.stemTip(path, t, out)与fx.spineAt(s)读取生长好的树。
fx.createSpike({ base, axis, height, radius, seed, erupt, rise })返回一根尖刺;fx.crackSpike(S, k, delay, duration)启动裂纹 k(0–2 横切杆身、3 沿其长度);fx.shatterSpike(S, center, strength);fx.resetIce(keepFrags)、fx.clearFrags();放置尖刺后fx.buildObstacles()。
fx.spawnChunks(pos, { count, speed, size, chips, dir, life, glow })雪块,或chips: true的半透明冰屑。
fx.spawnMote(kind, pos, vel, opts)kind 0 雪花、1 钻石尘(翻滚刻面、会闪光)、2 冷火花、3 碎冰片、4 长直线轨迹;life、size、gravity、drag、wind、bright、column(离开涡旋体即淡出)、soft(永不小于 2.2 px)。
fx.spawnWisp(type, pos, vel, opts)type 0 水汽、1 粉雪、2 地面雾;size、grow、life、opacity、drag、buoy、gravity、wind、stretch(对齐屏幕运动方向)。
fx.spawnStreamer(pos, { life, width, opacity })一条被风携带的发丝状飘带。
fx.setBanks([{ id, c, ra, rc, hb, ang, dens }])至多 8 条雾堤(中心、沿风半长、半宽、高度、风向角、密度);不在列表中的堤以 1.1/s 淡出,新堤淡入。
fx.wind.vortex/fx.windAt/fx.vortexAxis/fx.coreAt涡旋参数(on、center、radius、height、swirl、inflow、lift、outflow、sDir、sAmp、veil、phase)与风场查询。
fx.mist/fx.bloom/fx.setLight/fx.flashLight/fx.lightAt雾(amount、height、origin、flowSpeed)、手中霜花(group、t、fade)、8 路点光(setLight缓动趋近目标,flashLight叠加衰减闪光)、任意点查询光。
fx.ring/fx.impact/fx.ramp折射环(仅折射)、冲击帧(hold、frame: 'center' \| 'full' \| 'none'、flash、shake、shakeMode、fisheye、at)、时间斜坡。
fx.options/fx.debug/fx.stats()flashes('full' \| 'safe')、shake、fisheye、ice('traced' \| 'flat',即 demo 的Shaded失败开关);fx.debug = 'mist'只渲染雾;fx.stats()输出 motes/wisps/frags/chunks/frostSegs 计数。

组装你的地面与障碍物

地面着色器通过FROST_GLSL的frostAt(p, metresPerPixel)叠加霜(frost-energy.mjs):行覆盖由像素足迹盒滤波保证"永不细于 0.5 px(更细的线变暗而非消失)",亮度沿每一笔变化、生长尖端锐利、白霜呈簇状珠粒、f.micro在 2.8–4 mm 间距画出 45° 倾向尖端的细针、f.rime是双指数雾凇、f.fresh是新霜余晖、f.tip是e^(−45·|age|)的尖端炽光,还有 2.2×1.6 mm 晶胞上的刻面与朝向月亮的接触阴影。雾与雪读取的障碍图由buildObstacles()烘焙成 192 宽的 HalfFloat DataTexture(记录到每根尖刺脚的距离、喷发时间、高度与雾顶噪声,见 frost-energy.mjs)。

分层数字:即规格(The layers, with the numbers that shipped)

下表完整继承 SKILL.md 的"分层与交付数字",是这套效果可复现的核心依据——每个子系统都有精确参数:

层数字
霜花生长11 条种子茎(前进主茎 + 10 条在 ±0.42–3.14 rad,后方茎只有 0.42–0.7 倍长),2.6 m/s 在边缘减速到 0.7×,主茎 1.1×。逐级:步长 24 / 14 / 8 mm,间距 55 / 10.5 / 6.2 mm,半宽 1.6→0.9 / 1.1→0.5 / 0.5→0.28 mm。主茎上每 18–30 mm 出芒刺(55% 成对),芒刺上每 9–16 mm 出次级芒刺,角度 0.98–1.12 rad(冰的 60°);芒刺 0.25–1.2 × 每茎 0.16–0.3 m,次级芒刺 12–42 mm。主茎每 0.28–0.65 m 分叉(88% 可达半径内 p 0.8)成 0.45–0.8 rad;5.5% 的芒刺(主茎上 2%)继续长成新的羽毛。尖端死于最大长度、区域边缘或另一分支点在间距内的冲突(离开父茎的分支忽略父茎)。随后侧枝向尖端倾斜:各自绕根旋转 −0.36 rad(芒刺)或 −0.28 rad(次级芒刺)朝向其茎的尖端,并按 min(1.05, 0.5 + 0.75(1 − 该茎已生长占比)) 缩短,子枝随动。结果:41,610 段、14,618 条分支,2.4 s 效果时间长成,CPU 耗时 40–65 ms。
霜花烘焙实例化胶囊(半径 45 mm)进入 2048 × 1855 双目标 HalfFloat MRT,覆盖 10.6 × 9.6 m(5.2 mm 纹素),每个写 depth = 自身距离,最近段胜出。目标 A:到中心线距离、到达时间、方向;目标 B:半宽、层级、种子、弧长模 0.5 m(半浮点在此保持 0.2 mm 精度)。对应源码见 frost-energy.mjs。
地面上的霜线对像素足迹做盒滤波的覆盖率,永不细于 0.5 px(更细的线变暗);宽度 × 2 × 沿线的珠粒 0.55–1.45。着色器画 45° 倾向尖端的细针,间距 2.8–4 mm、长 2.2–7.7 mm,仅在像素处于 1.4–4 mm 之下时出现。雾凇 e^(−d/3.5 mm) × 0.5 + e^(−d/10 mm) × 0.08。生长尖端炽光 e^(−45·|age|);新霜发光(0.85e^(−1.8 age) + 0.15e^(−0.12 age))。2.2 × 1.6 mm 晶胞上的刻面:38% 反射月亮(pow 70 × 5)或冰光;其中 10% 发蓝。朝月亮方向 6 mm 的接触阴影把雪压暗 0.45。受光色 (0.86, 0.93, 1) × (3.4 天光 + 月亮 (0.8 N·L + 0.45) + 0.55 冰光) × 2.95。
枢纽与尖端种子处 0.56 m 的硬壳;8 / 5.5 / 4 mm 晶胞的三层短针缠结;170 个实例化晶体簇(11 平面凸包,压扁到 0.45,12–34 mm,中间最高 1.9×),每个坐在最低角上,按散射白霜打光。尖端星:距种子超 3.2 m 的主臂末端获得 ≥14 px × clamp(6.5/d, 0.35, 1.2) 的光晕并带 0.12 m 内 10 个微闪光;近端尖端只有 3 px 闪光;9% 的侧枝尖端有一颗小蓝火花。
冰刺沿主茎 0.95–5.45 m 的 7 个簇,主刺高 0.55 + 1.45k^0.9(k = 0..1 沿线),半径 0.035 + 0.075h,前倾 0.16–0.34 rad;1–3 颗卫星刺为 0.32–0.6 × 高、在 1.35–1.7 半径外、外倾 0.38–0.62 rad,绕脚互不近于 1.1 rad。喷发在 0.25 + s/5.2 s(约 5 m/s 的波),沿 1 − (1 − x)^3.2 在 0.2 + 0.06h s 内从雪下升起(y < 0 的像素被丢弃)。凸包:4–6 个侧面平面在半径 × 0.7–1.22,45% 被压成 0.25–0.55 的刃,1–3 个只朝上倾斜的切屑平面(向下倾斜会削掉整个杆身),2–4 个尖端平面在 1.12–1.38 rad 环绕一个偏心顶点(±0.18 r),一个底面,以及每条边上 2.2 + 2.2h mm 的倒角。平面生成逻辑见 frost-energy.mjs。
冰着色在物体空间追踪:IOR 1.31、Schlick F0 0.02、最多 3 段全内反射、地面平面作为出口;Beer–Lambert σ = (3.3, 1.75, 0.78) 每米。一段看到的景物是带 mipmap 的世界通道副本(LOD = rime × 3 + 0.7 每段),屏幕外回退天光渐变。内部:Voronoi 断裂网络(0.1 m 胞、边 <0.04、成片出现)在第一段 3 个深度采样;背面刻面边缘(3.5 mm 内)微光;柱状气泡(22 × 60 × 22 mm 胞、0.7–3.1 mm);轴周围 0.24r 的乳白核心、底部最热(× (0.25 + 4e^(−5y)))。外部:只有边缘(1–12 mm)与脚部(25–70 mm)有雾凇,断裂网是发丝线,刻面边缘是解析线(fwidth)以 2.4 + 14 pow(N·H, 6) 捕捉月亮。内光 0.24 + 1.4e^(−3.5 age) + 0.65 × 裂纹增益,色 (0.44, 0.64, 1)。Alpha 0 标记冰,供 FXAA 使用。着色器本体在 frost-energy.mjs。
裂纹与碎裂每根尖刺四个平面:三条在杆身 16–24%、37–48%、63–73% 高度处横切(倾斜 0.25–0.7 rad),一条沿其长度;两条旧裂缝从开始时冻结在内部。裂纹以 (t/d)^0.7 在 0.32 + 0.12h s 内从表面点跑到 2.4 半径(长的那条 0.9 × 高):内部是带羽毛纹理与亮前进前沿的银色薄片0.12 + 0.88·graze³;外部是锯齿发丝线。demo 近到远开裂(每簇 0.1 s、每卫星 0.05 s),裂纹运行时把时间放慢到 0.55,爆裂前弹回。碎裂:凸包依次被每个裂纹平面切开;仍在地下的那块留作残桩;其余以 (1.6 + 2.2e^(−0.4d) m/s) 抛出、抬升 1.6–3.4、自旋 3–9 rad/s。
碎片与岩屑3 个子步、重力 9.8、最低凸包角一个接触点:>0.6 m/s 时恢复系数 0.22(蠕动时不弹——只会助长摇晃)、摩擦 0.55。当 |v| < 0.35 且表面速度 |ω|·r < 0.12 持续 0.18 s,或接触 0.9 s 后,碎片休眠,然后在 0.22 s 内翻到离雪最近的面,并以 6 mm 步长推出任何其他碎片。融化的碎块沉进雪里而不是变暗。碎块:3 种雪块与 2 种冰屑凸包(14–19 次随机切、压扁 0.35–0.8),每根尖刺 6 + 8h 个 16–(45 + 25h) mm 雪块和 5 + 6h 个 12–(30 + 15h) mm 冰屑,活 11–15 s,在面上休息、陷入硬壳约四分之一高度、并被推出尖刺足迹。冰屑内部受光(天光 × 7、月亮 × 1.2、冰光 × 0.7)带月亮闪光。刚体解算与去穿透逻辑在 frost-energy.mjs。
雪面2 m 平铺扫描、2.63 m 处转 37° 再采样(噪声混合),加上 0.55 m 的第三采样作糖粒:法线 × 0.9、亮度作反照率 0.78–1.22×。反照率 (0.68, 0.84, 1) × 0.93 × 宏观变化。起伏(仅着色;表面保持 y = 0):0.05 m 长浪、尖脊沙丘 (1 − |n|)² × 0.13 m 沿风 2–4 m 间隔、第二倍频 × 0.05、雪脊 × 0.03、碎硬壳 0.03 + 0.012、涟漪 0.004;像素足迹从 12 mm 长到 50 mm 时小项淡出,距站立冰 1.5–5 m 内沙丘降到 0.35×(喷发把沙丘破成硬壳)。一个 0.12 m 的雪堆跟随冰。max(3 cm, 1.5 px) 的有限差分法线。光:月亮在 (0.35, 0.36, −0.86)、约 21° 高、色 (0.05, 0.062, 0.082) × (2.3 N·L × 阴影 + 0.04);天光 (0.0052, 0.0088, 0.0148) × (1.25 + 0.9 N_y) × (0.6 + 0.4 槽);向月亮 0.12i² m 的四步自阴影行进。
闪光两个族群,分别计分。强:12 mm × 2^lod 晶胞(lod 保持晶胞约 7 px)、70% 占据、刻面法线倾 1.1、月亮 pow(R·L, 55) × 5 × 阴影、冰光 pow 260、在像素空间画成方差 1.3 px² 的高斯(晶胞中心经逆屏幕 Jacobian 映射)。糖粒:4 mm × 2^lod 晶胞(2.4 px)、65% 占据、倾 1.2、pow 22 × 0.45。22% 发蓝 (0.45, 0.66, 1);强闪光在 25–55 m 外淡 45%,糖粒在 15–40 m 外淡 60%。
雾基层:三分之一分辨率行进、种子周围 8.5 m 圆柱内 8–16 步(每 0.6 m 一步),被扭曲噪声沿径向出流与涡旋旋转平流(核心内刚体旋转)、两个相位 4.5 s 交叉淡入;在站立尖刺周围变薄并靠上去堆积。堤(至多 8 条):各是一个椭球,在自身跨度内 10 步行进;包络 e^(−1.3e²) × 高度高斯,e² 在 0.9–1.6 间切到零;纤维在 (0.38u, 2.6y, 1.7v) 被 0.9 扭曲、沿风两个相位 5.6 s 交叉淡入;密度 (0.06 + 0.94·s²) × 包络 × 0.2 × dens。天光 × 9 打光、月亮前向散射 (0.55 + 1.8 pow(R·L, 2)) × (2.8 + 1.6h) × 1.25,加三个最强冰光 × 2.4,色调 (0.64, 0.73, 0.88)。白色逐像素抖动、5 抽头分离模糊、帐篷滤波上采样。着色器见 frost-energy.mjs。
暴风柱高 3.8 m;核心半径 0.75 × (0.3 + 0.85h^1.4);轴 −0.22 sin(1.5πh) m 侧向(中段偏左、顶部在右)。风:切向 swirl·2r·r_c/(r² + r_c²) 于 swirl 5.2、雪面拖到 0.45;脚部入流 2.1、核心抬升 3.2、顶部出流 0.4。纱幔:自己的半分辨率通道、柱内 20 步;0.8r_c ± 0.45r_c 处的外壳加四分之一强度核心;四条绳股0.15 + 1.6(0.5 + 0.5cos 4(θ − 1.7y − phase))^5;螺旋坐标中的纤维(一圈 12 单位,6 平铺噪声无缝包裹)被第二噪声撕碎;远侧 0.25。粒子:每秒 11,000 个粉雪点分布在 5 股绳上(寿命 0.45–0.9 s、至少 2.2 px、亮度 0.5–3.9 在脚部抬升)、每秒 16 个碎冰片、每秒 30 条发丝飘带、每秒 1.6 条直线轨迹、行两端每秒 110 个喷溅粒子;柱内出生的一切在离开 1.25 × 核心 × 1.3(顶部 1.15)后淡出。0.1–1.3 s 淡入、3.2–4.8 s 淡出;柱下天空压暗 30%。
手部霜花17 条树突在手掌法线 72° 内,各带 60° 侧枝与细枝,加 26 根白霜针:0.75–4 px 屏幕空间丝带、白色核心加青色光晕、以 0.85 臂长/秒在自己的时钟上长出来、丝带转向时闪光。手掌水汽:软体精灵、每秒 4–10 个、下沉。
光8 个点光缓动趋近目标(setLight,rate 3–6/s),上面叠加指数衰减闪光:霜花、两个贴霜前沿(0.9 与 0.6)、三个在尖刺群上(每站立簇 0.3 + 0.2h)、一次喷发闪光与一次碎裂闪光。漫反射 1/(1 + 2.6d²)、高光 pow(mix(160, 8, rough)) × (1.2 + 8F) / (1 + 0.45d²)。色 (0.5, 0.68, 1):钢蓝,不是青。
天空与林线地平线 (0.0115, 0.02, 0.036) 到天顶 (0.0006, 0.0026, 0.006) 在 0–0.62 rad 间。方向网格上的两层圆高斯星点(每弧度 300 颗、2.2% 亮;520 颗、4.5% 暗),被微云遮蔽。0.039 ± 0.0045 rad 高的森林边缘带圆冠(每弧度 230 颗、1.2–4 mrad),fwidth 抗锯齿。天空着色器见 demo/src/stage.js。
合成HalfFloat 颜色目标加深度纹理。世界(层 0)→ 供冰看的 mipmap 副本 → 冰(层 2)→ 基层雾与堤(三分之一、模糊)→ 柱幔(一半)→ 光效(层 3)→ 折射(层 1、一半)→ 双 Kawase bloom、5 级、阈值 1.3、膝 0.6、× 0.26、色调 (0.6, 0.74, 1) → ACES → sRGB、颗粒 0.016、暗角 0.85、进入 alpha 标记冰的 8 位目标 → 只对冰像素 FXAA。冲击帧同lightning-energy-skill-vfx(淡冰蓝纸上的墨),每秒至多 3 次反相闪光,然后 0.32 压暗。渲染顺序见 frost-energy.mjs。

把一段 brief 切成五拍

SKILL.md 给出了"brief 说什么 → 调什么"的对照表(完整继承):

brief 说调用
霜花在手中绽放、水汽盘旋fx.bloom1.6 s 内长到 11 cm、缓慢转动、掌心向上;光 0 从 0.35 → 2.5;水汽薄雾下沉;2.45 s 时握紧(0.72×)并闪光,2.8–3.3 s 掉落,一个柔和的闪光和落地处的光环,霜从那里开始
霜花沿地面飞驰而出霜花时钟运行(2.4 s 完全长成);光沿主茎与第二条茎骑乘;前沿扬起钻石尘;冷气聚集处铺四条雾堤;镜头升到后侧方
冰刺沿霜线喷发fx.state.spikeT从 0 开始:每根尖刺在 0.25 + s/5.2 s 升起,带雪块、冰屑、折射环与闪光;最高的有 0.06 s 星爆;三路尖刺光膨胀;基部雾堤与顺风尾迹
暴风雪螺旋、随后平息fx.wind.vortex.on缓入缓出;粉雪、雪片、飘带与喷溅喂到绳股上;柱幔与五条雾堤;缓慢环绕并保持整根柱
尖刺开裂爆裂、尘埃落定近到远慢动作crackSpike(0.55),然后 1.42 s 时对每根尖刺shatterSpike并带 0.1 s 全墨冲击帧、冰屑、钻石尘与闪光;碎块落地休息;尘埃在镜头推近时闪光沉降

拍与拍之间,处处缓动。一拍设置的每个尺寸与强度都走自己的曲线;雾堤缓入缓出;重播尖刺会把站立的冰先沉回雪里再升起;新一次施放会把上一次的霜融化、把碎块移除,而不是"啪"地消失。demo 的完整五拍编排(Chill / Frost creep / Ice spikes / Blizzard / Shatter)与逐拍时间说明见 demo/PROMPT.md。

规则,以及每条规则防止的失败

文档特别强调:每条规则都来自这个 demo 的一次翻车——被用户、评委或一次测量标记出来的。下面按四组完整继承。

测量

  • 调参前先搭区域统计工具。每帧把画面与目标分成区域(天空、林线、远/中/近雪、尖刺带、每片雾区、暴风柱)并排打印平均 RGB、亮度标准差、p99/p99.9、平均 |Laplacian| 与闪光块计数。评委两轮都点名同样的四个缺口("雾缺失""绳股太暗""冰不清晰""地面太平"),只有每个改动都由缩小这些数字驱动时才会推进。
  • 在两根轴上给闪光计分:高频能量与闪光数量。一轮是"盐噪声、闪光太多";修复后下一轮变成"少了大约 10×"。目标是致密细粒火花加少量亮闪光。用糖粒族群匹配细能量、用独立的强族群匹配亮数量,摇摆就停止了。这与评分卡中 r2 → final 的"step change"对应(references/scorecard.md)。
  • 单独渲染雾来校准它。两次雾堤在代码里存在、在画面里却"仍然缺失":太薄、太暗或欠采样。一个在黑底上只画雾的调试开关让每个问题一目了然。
  • 最终评委仍把表面闪光读成空中圆点。均匀散布在雪上的柔和 3–5 px 圆形闪光被读成镜头上的灰尘。强闪光保持在 1.5–3 px、只在受光沙脊上、并随距离淡出。

渲染

  • HalfFloat 目标上不要 MSAA;用合成里的 FXAA。多重采样 HDR 目标会解析成一堆块状垃圾。在 8 位目标的 alpha 里标记冰、只在那里跑 FXAA,霜发丝与闪光才保持锐利。
  • 每条雾堤在自身跨度上行进,配白色抖动与小模糊。在全部堤的并集上一次行进,给 1 m 的羽流只有两个采样,于是它消失了;图案化(交错渐变)抖动在薄雾上印出竖条与纱门。逐堤跨度、哈希抖动、5 抽头模糊与帐篷上采样同时治愈两者。
  • 把起伏限制到像素足迹。小沙丘与涟漪项在掠射距离混叠成水平水纹;足迹从 12 mm 长到 50 mm 时让它们淡出。
  • 多边形晶胞读起来像鱼鳞。法线平的 Voronoi 硬壳圆顶读成鹅卵石;扫描自身在小平铺 mipmap 上的法线读起来才是雪。
  • 切屑平面只朝上倾斜。向下倾斜的切屑平面会削掉整个下杆身、让尖端悬空。
  • 移动共享凸包顶点前先复制。裁剪器在面之间共享角对象;把碎片重新居中会移动它们两次,画出黑白垃圾。
  • 闪光需要像素空间的斑点。世界单位尺寸的斑点会在掠射角拉成条纹;把晶胞中心映射过逆屏幕 Jacobian、在像素里画高斯。

布局与随机

  • 绝不为换风格而改动共享随机流。在生长内部改芒刺角度会重排生成器、移动主茎、移动沿它放置的每一根尖刺。在各自根上做后变换来倾斜芒刺,改了外观却不移动任何东西。这正是 frost-growth.mjs 中"post-transform about its own root"注释的由来,也是评分卡 r2 的教训(references/scorecard.md)。
  • 保持目标裁剪时的取景。移除涡旋的倾斜也移动了瞄准它的镜头,每根尖刺偏移 30 px;换主题时把镜头钉在旧取景上。

观感

  • 霜是长出来的树,不是纹理。固定角度与间距的芒刺读起来像鱼骨梳;时间排序队列中的竞争把它们挤成羽毛,细针、珠粒、雾凇与闪光托起真实感。
  • 被追踪的冰读起来是冰;被着色的冰读起来是塑料。Shaded开关展示同样的刻面只从外部受光的样子——这正是 frost-energy.mjs 里uFlat分支的实现。
  • 冰光白蓝、低且大部分在核心。青色体光读起来像亚克力;让它留在基部附近的乳白核心,让月光边缘与折射承担其余。
  • 尖端不要闪光精灵,不要棉球雾团。每个晶体尖端的四角星与孤立的圆形雾团每次都被点名。
  • 暴风雪是精细粉雪的柱,不是涂鸦。十几条粗的不透明飘带随机交叉读起来像镜头上的涂鸦。许多发丝线、几千个柔粉点在少数螺旋绳股上、一层半透明纤维纱幔、冠部保持在柱体宽度的 1.3× 内。
  • 碎块在面上休息、埋一点。平衡在角上的岩石、浮空几毫米的碎块、切断地面的雾卡片都被点名。每个碎块的接触都被数值检查。

成本:填充率主导,帧内全部达标

成本就是填充率:地面(起伏、自阴影、两个闪光循环)、被追踪的冰、雾与柱行进。用 WebGL 计时查询在 1440 × 900、像素比 1.5(即 2160 × 1350)、负载均值 3.1–4.8 下测得(毫秒,median / p90):

采样chillcreepspikesvortexshatter
每第 4 帧重绘 5 次、保留最快(中位数 / p90)5.7 / 8.75.9 / 9.85.1 / 7.45.5 / 8.55.7 / 8.4
每帧、每次一个查询(中位数 / p90)5.7 / 8.89.7 / 10.79.9 / 16.33.9 / 7.68.7 / 12.7

第二行包含共享机器上其他 GPU 工作的争用;第一行才是页面自身成本的更佳估计。CPU 上每帧约 2–4.5 ms(暴风雪峰值:生成、4,000+ 粒子及其缓冲区)。demo 把像素比上限设为 1.5。生长与烘焙霜花在加载时一次性约 50 ms。评分卡还补充了重要前提:循环期间负载均值 25–75 时测得的 16–28 ms 不可信(references/scorecard.md)。

生命周期与可访问性

  • fx.update(realDt)把步长钳制到 0.1 s;visibilitychange时重置帧时钟(源码见 frost-energy.mjs)。
  • prefers-reduced-motion: reduce下,demo 保持一帧合成静帧(尖刺立于霜中、雾在场),只在请求时播拍,使用安全闪光、无晃动、无鱼眼——stage.js 在页面加载时据此关闭 shake/fisheye 并切换到flashes: 'safe'。
  • demo 界面极简:无面板,小号文字控件压在淡色遮罩上。开关是带aria-pressed的真实按钮,拍子绑定在键 1–5(0 为完整施放)、空格暂停、H 隐藏控件、变化在 live region 中播报。任何文字不低于 11 px。
  • window.stage(play、step(dt, n)、setPaused、setOption、fx、BEATS、clusters等)为确定性无头抓帧而设;竖屏时镜头按 (1/aspect)^0.55 后退。

交付前的验证清单

SKILL.md 以一组可勾选验证收尾(完整继承):

  • 霜花从落地处长出:主茎前冲、芒刺在它们身后朝尖端倾斜地展开、生长尖端炽亮、新霜发光后安定;没有任何滑动。
  • Shaded下尖刺是平涂塑料;Traced下雪与天光穿过它们弯曲、背面刻面与裂纹在内可见、边缘抓住月光。
  • 尖刺沿霜线成波喷发,雪块与冰屑在根部沉降;雾贴着根部、顺风拖尾。
  • 暴风雪是螺旋绳股上的精细粉雪柱、带半透明纱幔、淡入淡出;没有任何粗条划过画面。
  • 裂纹近到远慢速跑动,随后每根尖刺沿那些平面爆裂;每块碎片在离雪 1 mm 内落在面上、互不重叠、残桩保留。
  • 一秒内连打五发反相命中(Full闪光):前三发反相,第四、第五发改为压暗 0.32。
  • 减少动效时保持静帧加安全闪光。从磁盘打开时页面只请求自己的文件。1440 × 900 与 390 × 844 下控制台干净。

源码阅读地图

如果你想继续深挖,按此顺序读效果最佳:

  1. assets/frost-growth.mjs(213 行)——整套"竞争生长"的核心:时间堆、空间哈希、三级生长规则与"绕自身根的后变换倾斜";
  2. assets/frost-energy.mjs(2402 行)——冰追踪、刚体、风场、雾、暴风柱、合成与全部 API;
  3. demo/src/stage.js(844 行)——天空、地面着色器、五拍编排与镜头;
  4. demo/build.mjs(59 行)——离线单文件构建管线;
  5. references/scorecard.md 与 demo/PROMPT.md——评分历程与可复述的 prompt,包括把它重做成"紫水晶洞穴"的 remix 案例。

本技能与lightning-energy-skill-vfx、fire-smoke-skill-vfx共享"一个时钟、一个合成、冲击帧、闪光限制器"的骨架,也与 game-dev-combat-skill-polish 的评分规范形成配套——在真实项目里,把本技能的效果语言与其他技能的效果语言并排放进同一套时钟与合成体系,是让整套"元素技能页"保持统一观感的关键一步。

【免费下载链接】Skills

Agent skills for designers and builders using Codex, Claude, Cursor, and other AI coding agents

项目地址:https://gitcode.com/gh_mirrors/skills48/Skills
点击查看免费下载

相关推荐

上一篇:KMS_VL_ALL_AIO:3分钟解决Windows和Office激活难题的智能方案
下一篇:告别激活烦恼:KMS_VL_ALL_AIO让你的Windows和Office永不过期

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 5:14:16

并联式混合动力Simulink控制策略模型搭建全解析

做混动整车仿真这几年&#xff0c;有一个体会特别深&#xff1a;并联式混合动力系统Simulink控制策略模型&#xff0c;表面上是个建模问题&#xff0c;实际上是个决策问题。它真正检验的不是你会不会搭Simulink模块&#xff0c;而是你能不能把“发动机和电机分别在什么时刻出力…

作者头像 李华
网站建设 2026/10/9 5:14:07

PowerToys 排错指南:5 步定位启动失败、快捷键失灵与配置重置

PowerToys 排错指南&#xff1a;5 步定位启动失败、快捷键失灵与配置重置 【免费下载链接】PowerToys Microsoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows 项目地址: https://gitcode.com/GitHub_Trending/po/Po…

作者头像 李华
网站建设 2026/10/9 5:12:22

商家小程序落地四步法:从痛点出发构建最小业务闭环

1. 为什么“比排行”是商家小程序启动阶段最典型的认知陷阱&#xff1f;我陪某连锁烘焙品牌做过三轮小程序上线复盘&#xff0c;每次他们第一句话都是&#xff1a;“老师&#xff0c;现在市面上最火的小程序模板是哪个&#xff1f;Top3的商家都用什么系统&#xff1f;”——这问…

作者头像 李华
网站建设 2026/10/9 5:12:09

Vulkan固定功能阶段详解:从顶点输入到颜色混合的管线配置

很多朋友学到 Vulkan 管线的可编程阶段&#xff0c;写完了顶点着色器和片元着色器&#xff0c;就以为万事大吉。结果在创建VkGraphicsPipelineCreateInfo的时候&#xff0c;突然冒出一大堆结构体要填&#xff1a;顶点输入、输入装配、光栅化、深度模板、颜色混合。这就是 Vulka…

作者头像 李华