mcpField
3
3d-underwater-god-rays

3d-underwater-god-rays

github.com/mengto创意与媒体7.1k313 次安装3 天前更新
代码仓库

关于 3d-underwater-god-rays

面向 3D 水下场景开发者的可复用 Three.js 着色器模块,用于渲染水下光束。它沿视线整合漂移涌浪与焦散,使光束随相机移动而相对场景物体滑动。

主要功能

  1. 01Three.js 水下光束着色器
  2. 02光束依涌浪和太阳方向定位
  3. 03整合焦散条纹
  4. 04半分辨率 HalfFloat 光线步进
  5. 05支持减少动态效果与页面可见性

适用场景

  1. 01为 Three.js 水下场景添加阳光光束
  2. 02相机移动时让光束相对鱼群移动
  3. 03通过演示控件调整光束效果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mengto/skills --skill 3d-underwater-god-rays

安装后在 Claude Code 中输入/3d-underwater-god-rays确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
assets/
underwater-light.mjs
demo/
build.mjs
PROMPT.md
demo/src/
ui.css
ui.html
ui.js
water.js

详细介绍

3D 水下上帝光芒

当摄像机位于水下、光线穿过水面照射下来时,请使用此方案。以下情况请改用同类方案:

  • **空气中的阳光光束,被树木和屋檐切割:**使用 3d-sky-rays。它通过屏幕空间对遮挡蒙版进行径向模糊,适合这种情况。它无法产生视差,也无法呈现涟漪,因此在水下只将它作为鱼影的低强度辅助效果(Blue Hour 将其设为 0.25)。
  • **从上方看到的海面,带有地平线:**使用 3d-ultra-realistic-water。
  • **明亮室内中穿过一扇窗户的自定义光束平面:**使用 3d-wood-lighting-scorecard。

来源:摘录自 Blue Hour,这是一项基于 Three.js iPhone Duo 研究的动态海洋壁纸。2,200 条用 Blender 制作的沙丁鱼组成的鱼群在玻璃设备后方游动。光线必须在完整的 38 s 摄像机推轨过程中,以及设备自身的屏幕上,呈现出阳光穿透水面的效果。

已验证的技术栈:Three.js r170、WebGL2、GLSL ShaderMaterials、一个 HalfFloat 渲染目标,以及手写的 ACES 渲染通道。未使用后期处理库。

实现机制

**沿着太阳方向采样一个漂移的涌浪场,并沿每条视线积分。**水中的每个点,其受光情况由该点沿垂直于太阳方向的位置处的涌浪值决定。因此,光束是在世界空间中平行排列的柱体。它们像铁轨汇聚于消失点那样汇聚到太阳处,并在摄像机移动时从鱼群旁滑过。在屏幕空间绘制相同的光束,则会让它们固定在镜头上。

复用现有效果

复制 assets/underwater-light.mjs。传入自己的 THREE 即可,该模块不会再导入另一份副本。演示在 Blue Hour 的渲染器中运行同一个模块。demo/build.mjs 会根据 Blue Hour 源代码重新构建该模块。

import { createUnderwaterLight, LIGHT_GLSL, WATER_GLSL } from './underwater-light.mjs';

// One uniforms object shared by the water, fish and particle materials.
const uniforms = {
  uSunDirection: { value: new THREE.Vector3(.25, .40, -1).normalize() },   // toward the sun
  uTime: { value: 0 },
  uCameraPosition: { value: camera.position },
  uCameraWorld: { value: camera.matrixWorld },
  uInverseProjection: { value: camera.projectionMatrixInverse },
};
const light = createUnderwaterLight(THREE, { uniforms });   // before creating the materials below
light.setSize(innerWidth * dpr, innerHeight * dpr);          // drawing-buffer pixels, again on resize

// Water: a full-screen quad. Declare uSunDirection, uCameraPosition, uTime,
// uShafts and uShaftTexel, then add ${LIGHT_GLSL}${WATER_GLSL} and in main():
//   vec3 sunRay = ray;
//   color += surfaceUnderside(ray, sunRay);     // also bends sunRay through the ripples
//   ...draw your sun with sunRay...
//   color += underwaterShafts(vUv, ray);
// Fish: declare uSunDirection, then add ${LIGHT_GLSL} and multiply the sun key
// by underwaterDapple(worldPosition, uTime).

// Every frame, in this order:
uniforms.uTime.value = time;
camera.updateMatrixWorld();
light.render(renderer);          // the march, into its own half-resolution target
// then the water, the fish, and one tone map at the end

实际使用的参数

图层常量
涌浪沿 (.86, .51)、(−.42, .91)、(.13, −.99)、(−.97, −.24) 方向的 4 个正弦波,频率分别为 ×1、1.23、0.87、1.41;漂移速度为 0.29、0.24、0.19、0.15 rad/s;域扭曲为 0.62
沿太阳方向缩放 1.3,因此主周期约为 4.8 个世界单位;鱼群所在位置处,画面宽约 22 个单位
光束聚焦smoothstep(−0.05, 1, swell)²,并乘以 exp(−0.012 · 沿太阳方向到水面的距离) 进行淡出
焦散网格再次使用涌浪,频率为 ×2.6,速度为 1.35 倍,`(1 −
射线步进26 步,t = 4 + 50·f²,权重为 exp(−0.06t)·(0.08 + f),再除以权重总和;使用稳定的逐像素抖动(交错梯度噪声),而非时间噪声
太阳混合total = mix(total, 0.16, μ^28),其中 μ = ray·sun
相位0.7 + 1.1μ^5 + 0.9μ^40;地平线以下的光线再乘以 mix(0.45, 1, smoothstep(−0.45, 0.35, ray.y))
增益(0.16, 0.40, 0.47) × 3.6,线性 HDR,仅在最后进行色调映射
缓冲区绘制缓冲区的 0.5 倍大小,HalfFloat,单采样(射线步进效果较柔和,MSAA 没有收益),使用半个纹素偏移的 4 抽头帐篷滤波进行上采样
水面底侧平面位于 y = 12;网络为 `(1 −
鱼群太阳主光 × 0.62 + 1.5 · shaftLight
画面其余部分太阳光晕减半至 (0.043, 0.095, 0.123)·μ^48;屏幕空间光线通道的强度从 0.82 降至 0.25

规则及其避免的问题

这些结论来自 Blue Hour 画面的 A/B 渲染:相同的摄像机、相同的静止鱼群,每次只改一项。

  • 将涌浪塑造成稀疏的波峰,而不是波脊。 1 − |swell| 会形成一张由薄片组成的网,光线穿过多层薄片后会将它们平均成一片平淡的雾。在静止画面中,波脊网让水面的平均亮度提高了 20%,并将像素列之间的对比度从 0.247 降至 0.118。演示中的 Ridge web 开关可以展示这一效果。
  • 从 4 个单位外开始步进。 从 1.5 个单位处开始时,掠过摄像机上方的光束会用一道明亮的楔形光填满四分之一画面。用 exp(−0.06t) 对采样加权:较近的采样模糊得最少,因此能保住光束边缘。
  • 当光线指向太阳时,将光束融合进太阳的辉光。 沿着正上方的一列观察,积分结果只是一个涌浪值。在摄像机位于光束之间的位置,这会在太阳上留下一个暗洞。mix(total, 0.16, μ^28) 可以填上它。
  • 在步进过程中积分焦散网。 如果改为以全分辨率采样 4 次,焦散网看起来就像是绘制在水面上的水池底部图案。在半分辨率的 26 步步进中,它会变成沿每道光束延伸的细条纹。演示中的 Painted on screen 开关可以展示这一效果。
  • 调低画面中已有的均匀辉光。 Blue Hour 的日冕和屏幕空间光线处理让画面顶部均匀变亮。以最终发布时附近的增益来看,首次加入的光束在合成画面中几乎看不见。只有将日冕减半、把径向光线处理从 0.82 降到 0.25 后,光束才清晰可见,同时光束之间仍保持暗色。
  • 在合成后的页面上判断增益,而不是看原始画布。 增益为 2.2 时,画布上的效果看起来正好。但在页面中,画布的不透明度为 82%,下方还有一层使画面变暗的 CSS 遮罩,同一画面几乎什么都看不见。3.6 才能在页面上保留效果。
  • 将太阳抬到顶部边缘以上,让光束铺满整个画面。 太阳位于 21° 时仍在画面中:汇聚点会呈现为一团辉光,光束也比较短。角度介于 30° 和 44° 之间时,太阳会越过顶部边缘,光束则会向下铺满整个画面。演示中的 Sun 滑块可以调整角度。
  • 每个着色器中,每个 uniform 只声明一次。 LIGHT_GLSL 会声明自己的 uniform(uSurfaceY、uShaftScale、uShaftFloor 和 uCompare),并要求宿主先声明 uSunDirection。重复声明会导致编译错误,材质也会悄无声息地不绘制任何内容。

开销

在机器负载较高时(平均负载约为 60),无头 Chrome 在 ANGLE Metal 上以 2048×1440 分辨率渲染完整的 Blue Hour 画面需要 6–10 ms。该画面包含 2,200 条实例化沙丁鱼、水体、1024×720 分辨率的步进、屏幕空间光线处理以及色调映射。跳过步进后,在 5 轮交替测试(每轮 20 帧)中,中位耗时的变化小于每轮运行间的噪声(约 ±2 ms),因此步进不是这里的瓶颈。如果它出现在你的性能分析结果中,请降低缓冲区缩放比例或步数。光束属于低频效果,因此这两种做法主要会降低柔和度,但在依赖这些做法之前,先测量实际效果。

生命周期与无障碍

  • 以 30 fps 推进模拟,每步最长不超过 0.1 s。Blue Hour 会将水族箱渲染到固定的 2048×1440 画布(宽度低于 800 px 时为 1600×1125),再以覆盖方式适配,因此步进开销不会随屏幕大小变化。
  • 在 prefers-reduced-motion: reduce 下,将画面停留在 t = 0,仅在控件变化时重新渲染。抖动在每个像素上都保持稳定,因此静止画面不会爬动。
  • 在 document.hidden 时停止,并在 visibilitychange 时重置帧时钟,避免恢复后的第一帧积分整个暂停期间的时间。

验证

  • 环绕摄像机旋转:光柱相对于鱼移动,但仍汇聚于太阳。如果它们随镜头移动,则说明它们绘制在屏幕空间中。
  • 开启 脊状光网 后,水面会变成雾状。切回稀疏峰值模式后,可以看到清晰的光束,光束之间是暗色水面。
  • 开启 绘制在屏幕上 后,池底光网会覆盖水面。在光束行进路径内,细微的条纹会沿着每束光延伸。
  • 直视太阳时,不会出现黑洞。
  • 将太阳从 21° 扫到 44°:汇聚点会移出画面顶部,光束则会延伸至画面各处。
  • 鱼穿过光束时会变亮,游出光束后又会变暗。
  • 减少动态效果时会保持构图完整的静止画面;控件仍会重新渲染画面,并且在 1440×900 和 390×844 分辨率下控制台没有错误。

评论

支持 Markdown