我在 React Canvas 中实现了液态玻璃镜头效果
Source: Dev.to
Introduction
我最近在 react-canvas 中实现了一个 液态玻璃镜头 后处理效果,目标有三:
- 镜头中心拥有清晰的放大感
- 边缘拥有强烈的玻璃式畸变并伴随细微的色散
- 不使用第二套渲染栈,完全复用现有的 CanvasKit 流程
GitHub 仓库:
Live demo:
Rendering Strategy
最直观的做法——添加一个独立的 canvas 或 WebGL 层——会带来高同步成本(滚动、缩放、相机状态必须保持一致),并可能导致交互拾取冲突。
因此,我选择 在同一 CanvasKit 流程中进行全屏后处理:
- 将完整场景渲染到离屏 surface。
- 对离屏图像应用 SkSL
RuntimeEffect。 - 在一次绘制中把处理后的结果绘制到主 canvas。
Benefits
- 场景逻辑完全复用;不需要双渲染树。
- 效果控制集中在着色器代码和 uniform 中。
- 与现有渲染架构高度兼容。
Lens Components
当前的液态玻璃镜头由四个部分组成:
- 中心放大 – 镜头内部的清晰放大。
- 边缘畸变 – 畸变集中在外环附近。
- 色散(RGB 偏移) – 边缘的细微颜色晕染,增强玻璃感。
- 边缘高光 + 阴影 – 赋予镜头真实的物理存在感。
Key Optimization
中心应 不 产生畸变;畸变应集中在外部约 20 % 的环形区域。
引入归一化半径 t,并按如下方式使用:
// GLSL / SkSL snippet
float rim = smoothstep(0.8, 1.0, t); // edge weight
// Near‑zero distortion weight at the center (t < 0.8)
// Gradually increasing distortion and dispersion as t → 1
这种做法模拟了真实镜头的边缘折射,而不是让整个区域晃动。
Rendering Loop
镜头是一个纯 uniform 驱动的后处理效果。虽然可以在每帧强制重绘,但这会不必要地消耗资源。
- 在指针移动时,通过
requestCanvasRepaint(canvas)主动唤醒渲染。 shouldContinueRepaint仅在镜头仍在追踪目标点时继续重绘。- 一旦镜头稳定,连续重绘会自动停止。
结果:移动时平滑,停止后不再空转。
Common Pitfalls
- 坐标映射:使用
getBoundingClientRect()加上后备存储比例来转换指针坐标。 - 边缘采样:在着色器中使用
clamp(0..1)限制 UV 采样,避免出现黑边或越界伪影。
这些细节直接影响最终的打磨程度。
Takeaways
- 决定 不在何处应用 效果往往和决定 在何处应用 同样重要。
- 对于镜头效果,稳定的中心加上富有表现力的边缘比全区域畸变更自然。
- 一旦架构正确(统一的后处理管线),迭代调优的成本会大幅降低。
如果你正在 Canvas/Skia 场景中构建玻璃、景深或色彩分级等效果,建议从 “离屏场景 + RuntimeEffect” 的思路入手。它在后续扩展时的可扩展性更好。