Files
mantle-ai-trader/skills/web-shader-extractor/references/porting-strategy.md
2026-06-06 05:21:10 +00:00

5.7 KiB
Executable File
Raw Blame History

移植策略

框架选择

纯 2D CanvasgetContext('2d'),无 WebGL
├─ YES → Vanilla JS零依赖见下方 § 2D Canvas
└─ NO  → 纯 2D 全屏 shader / 后处理?
         ├─ YES → 原生 WebGL2零依赖
         └─ NO  → 涉及 3D / PBR / GPGPU / onBeforeCompile
                  ├─ YES → 保留原始框架CDN importmap
                  └─ 不确定 → 先用原始框架Phase 6 再评估

原生 WebGL 项目结构

<name>/
├── index.html        # <canvas>
├── js/
│   ├── main.js       # WebGL2 初始化 + 多 pass 渲染循环
│   └── shaders/      # .glsl.jsexport const fragmentShader
└── README.md
  • canvas.getContext('webgl2') + #version 300 esin/outtexture()
  • 多 pass 用 framebuffer + texture attachment
  • requestAnimationFrame 驱动

Three.js 项目结构

<name>/
├── index.html        # importmap CDN
├── js/
│   ├── main.js       # 场景/相机/渲染器 + RTT 管线
│   └── shaders/      # .glsl.js
└── README.md
  • RawShaderMaterial + glslVersion: THREE.GLSL3
  • WebGLRenderTarget 做多 pass
  • CDN importmap零安装

CDN 模板:

<script type="importmap">
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.183.0/build/three.module.js" } }
</script>

验证:curl -sI '<url>' | head -3

多层合成场景移植要点

从 Unicorn Studio / curtains.js 等 no-code 工具提取的场景通常有复杂的 FBO 链。以下是常见陷阱和正确做法:

1. 理解 parentLayer 与 effects 的父子关系

在 Unicorn Studio 中:

  • Element 层shape/text/imageeffects[] 数组存储子效果的 UUID
  • Effect 层parentLayer 字段指向父元素的 UUID
  • 子效果按 effects 数组顺序依次渲染,每个 pass 读取前一个 pass 的 FBO

移植时必须还原这个链式 FBO 结构,不能简单合并成一个 pass。

2. showBg=0 的透明背景

showBg=0 意味着 shader 在未命中几何体的区域输出 vec4(0)(完全透明)。 这不是"黑色",而是透明,后续合成时会显示下方图层。

// 正确showBg=0 → 透明
if (hit < 0.5) { fragColor = vec4(0.0); return; }

// 错误:输出黑色(会覆盖下方图层)
if (hit < 0.5) { fragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }

移植时必须用 alpha composite passfg + bg * (1 - fg.a))将结果叠加到下方图层。

3. 文字/图片元素的合成方式

Element 层text/image需要用 Canvas 2D 渲染后作为纹理上传 WebGL。 合成方式决定了视觉效果

// 错误alpha-over 覆盖(丢失背景纹理)
fragColor = mix(bg, vec4(txt.rgb, 1.0), txt.a);

// 错误additive饱和为白色丢失色彩变化
fragColor = vec4(bg.rgb + txt.rgb * txt.a, 1.0);

// 正确:亮度放大(保留背景噪声的色彩纹理变化)
fragColor = vec4(bg.rgb * mix(1.0, amplifyFactor, txt.a), 1.0);

原理:原始场景中文字元素叠加在噪声层上,经过 glyph dither 后, 字符颜色取自该位置的像素色。如果文字区域是平坦单色ASCII 字符就是单色的。 用亮度放大方式,噪声的色相比例完整保留(紫:青:暗 按相同系数放大), glyph dither 后的字符就带有噪声纹理的色彩变化。

4. 重复效果实例

同一种效果(如 noiseFill可能在场景中出现多次一次作为背景独立层 一次作为 shape group 的子效果。参数可能相同但在管线中位置不同, 子效果的输出会被后续效果(如 SDF 折射)处理,产生不同的视觉。

5. Glyph Atlas 兼容性

base64 内嵌的 PNG glyph atlas 在某些浏览器/WebGL 环境中 texImage2D 会报 INVALID_VALUE: bad image data。推荐用 Canvas 2D 动态生成。

6. 颜色空间一致性(最常见的视觉偏差来源)

Three.js / shaders.com 等工具全程在 linear 空间 工作。移植到原生 WebGL 时:

错误做法(每个 pass 独立 gamma
  Pass1: 输出 pow(linear, 1/2.2)    ← sRGB
  Pass2: 读入 sRGB + 计算高光(linear) + 输出 pow(result, 1/2.2)  ← 混乱!

正确做法(全程 linear最终一次 gamma
  Pass1~N: 全部输出 linear 值
  Final:   pow(linear, 1/2.2)        ← 唯一一次 sRGB 编码

hex 颜色定义 → pow(srgb, 2.2) 转 linear → 全程 linear 计算 → 最终 pow(linear, 1/2.2) 输出。

7. 参数精确对齐原则

绝对禁止手动调参来"补偿"视觉差异。所有公式乘数必须与原始代码完全一致。 如果效果不对,应排查颜色空间、噪声实现、时间基准等根因,而不是改乘数。 手动调参在当前配置下可能看起来更好,但会在其他参数组合下崩溃。

2D Canvas

每个效果一个文件,导出 create<Name>Effect(container){ destroy }。多效果用 main.js 管理切换。

性能要求

  • IntersectionObserver + visibilitychange — 不可见时停止 RAF
  • DPR 上限 Math.min(devicePixelRatio, 2)
  • 后处理用离屏 Canvas 缓存,静态内容仅 resize 时重建
  • 大量粒子数据用 Float32Array

通用规范

  • ES Moduleimport/export
  • minified 变量名替换为有意义名称
  • README 含效果说明、技术原理、可调参数

Phase 6简化评估

触发:移植完成后自行验证效果正确。提议而非自动执行 — 这是全流程唯一需要用户决策的步骤。

只用了 RawShaderMaterial + WebGLRenderTarget + fullscreen quad
├─ → 可简化为原生 WebGL2减少 ~600KB
用到 PBR / onBeforeCompile / 3D 场景?
├─ → 不简化
不确定?
└─ → 不提议