Three.js 水下体积光(Underwater God Rays)实战指南:从漂移波场机制到 Blue Hour 鱼群演示
【免费下载链接】SkillsAgent skills for designers and builders using Codex, Claude, Cursor, and other AI coding agents项目地址https://gitcode.com/gh_mirrors/skills48/Skills点击查看免费下载本文以 Skills 仓库中的3d-underwater-god-rays技能为骨架完整讲解如何在 Three.js 场景中实现阳光透过水面的体积光柱God Rays只用一块共享 uniforms 对象、一个半分辨率 HalfFloat 离屏目标与一段手写 GLSL就能得到会聚在太阳方向、随相机视差滑动、光束内部携带波纹焦散、且能同步为鱼群打上斑驳光影的完整水下光照方案。读完本文你将掌握该技能的可复用模块 APIcreateUnderwaterLight/LIGHT_GLSL/WATER_GLSL、全部落地参数、8 条经过 A/B 渲染验证的调参规则以及它在 Blue Hour 沙丁鱼群演示中的集成方式与性能数据。什么时候用它使用场景与姊妹技能的选择这个技能针对的场景很明确相机在水下光从水面透下来。适用面包括水下场景、从下方仰视的海洋、水族馆、珊瑚礁、海带森林、潜水与沉船场景、鱼群掠影、穿过水体的阳光束/光柱、焦散caustics以及背景上的水波涟漪。同一仓库下还有三个方向不同、容易混淆的姊妹技能SKILL.md 给出了清晰的取舍标准空气中的阳光束被树木和屋檐切割用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 秒相机推拉camera dolly期间、以及设备自带屏幕上都被读作阳光穿过海水。验证技术栈为Three.js r170、WebGL2、GLSLShaderMaterial、一个 HalfFloat 渲染目标、手写 ACES 色调映射全程不依赖任何后处理库。核心机制把一个漂移波场采样到太阳方向再沿每条视线积分技能的机制只有一句话但它是全部后续参数与规则的总纲SKILL.md沿着太阳方向采样一个漂移的 swell涌浪场并沿每条视线方向对它做积分。水体中每个点由该点处垂直于太阳方向测得的 swell 值来照亮。由此自然推导出三个关键性质光柱是世界中的平行柱每个采样点只关心它横跨太阳方向的位置所以所有光束在世界上互相平行它们像铁轨汇聚于灭点一样汇聚在太阳上投影后视觉上向太阳收束相机移动时它们从鱼群旁滑过因为光柱锚定在世界坐标若改用屏幕空间绘制光柱就会粘在镜头上、不再视差。这一单一漂移波场 沿视线积分的设计让光柱、表面波纹、鱼身上的光斑三者共享同一个波场从而同步运动、互相一致——这是本技能与把焦散贴在水底/水面上式做法最本质的区别。复用现成效果模块 API 与集成代码核心实现是单文件模块 assets/underwater-light.mjs。它不自己importThree.js而是由你传入自己的THREE避免出现第二份 Three 副本演示 demo/index.html 运行的就是同一份模块demo/build.mjs 负责从 Blue Hour 源码重建它。SKILL.md 给出的最小集成代码如下uniforms 对象由水、鱼、粒子材质共享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从 createUnderwaterLight 的实现可以看到模块做了哪些约束与兜底必需的 uniforms 校验模块要求传入的 uniforms 对象必须已经包含uSunDirection、uTime、uCameraPosition、uCameraWorld即camera.matrixWorld和uInverseProjection即camera.projectionMatrixInverse五个字段缺失任何一个都会抛错REQUIRED 列表模块自动补充自己的 uniformsuSurfaceY、uShaftScale、uShaftFloor、uShaftGain、uShaftColor、uCompare两个 A/B 失败模式开关、uShafts目标纹理和uShaftTexel全部写进你传入的同一个对象因此任何读取该光照的材质看到的都是同一套值Object.assign 段渲染目标一个 HalfFloat、RGBA、线性过滤、无深度缓冲的单采样WebGLRenderTargetL154-L157保证 HDR 光柱在最终唯一的色调映射之前保持大于 1 的数值setSize接收的是绘制缓冲像素CSS 尺寸 × pixelRatio内部按resolution: 0.5缩放为 1×1 以上的整数宽高并写回uShaftTexelL181-L187render保存/恢复外部渲染目标把全屏正交 quad 渲染进自己的目标L188-L194dispose释放渲染目标、材质与几何体。源码级拆解从波场到光柱的每一步把 LIGHT_GLSL 展开看光路模型由四个可组合的函数构成swell(p, t)一个经过**域扭曲domain warp**的四正弦叠加波场。四个正弦分别沿方向 (.86, .51)、(−.42, .91)、(.13, −.99)、(−.97, −.24)频率倍率为 ×1、1.23、0.87、1.41漂移速度 0.29、0.24、0.19、0.15 rad/s域扭曲强度 0.62——扭曲保证不会出现笔直的波脊acrossSun(p)以太阳方向为轴构造ex/ey基返回世界点横跨太阳方向的二维坐标并乘以uShaftScale——这是光柱是世界中的平行柱的数学来源L40-L43shaftFocusswell 值经过smoothstep(uShaftFloor, 1., s)后平方稀疏峰值再乘exp(−depth · 0.012)沿太阳方向按到水面距离衰减L45-L50shaftWeb把同一波场放大 ×2.6、提速 ×1.35 后取(1 − |s|)^5形成更细、更快的焦散网络L52-L54shaftLight focus × (0.45 0.9 · web)焦散作为光柱内部的亮度调制underwaterDapple0.62 1.5 · shaftLight供鱼材质把太阳键光乘以它鱼游进光束就亮、游出就暗L57。WATER_GLSL 则提供水面下侧与最终合成surfaceRipple三个余弦 一个低频项的精细干涉波纹作为水面下侧的法向扰动L65-L69surfaceUnderside(ray, inout sunRay)在 y12 的水面平面上用1 − |swell|的 8 次/10 次方叠加出焦散网络配exp(−0.028d)雾衰减并用0.018 × 波纹斜率弯曲 sunRay——这正是太阳透过涟漪会晃动的实现细节随fwidth(s)淡出颜色为 (0.098, 0.221, 0.26)L73-L87underwaterShafts对半分辨率目标做4 点 tent 滤波上采样4 个偏移取平均隐藏逐像素抖动uCompare.y .5时切换为失败模式 B——在屏幕空间按全分辨率采样 4 次焦散用来在演示里展示焦散被画成泳池地板的对比效果L89-L99。真正的体积步进在 SHAFT_FRAGMENT 中用uInverseProjection × uCameraWorld重建世界空间视线26 步从 4 到 54 单位按t 4 50·f²分布近密远疏每步权重exp(−0.06t)·(0.08 f)再除以权重和近处采样模糊最少、因此承载最多能量用稳定的逐像素抖动interleaved gradient noise 的fract(52.9829189 · fract(dot(gl_FragCoord, …)))替代时间抖动静帧不爬行视线指向太阳时以mix(total, 0.16, μ^28)并入太阳辉光相位用0.7 1.1μ^5 0.9μ^40水平线以下的射线再乘mix(0.45, 1, smoothstep(−0.45, 0.35, ray.y))。落地参数表Blue Hour 实际交付的数字SKILL.md 以表格形式给出了所有上线常量它们来自 Blue Hour 的真实帧是可直接照抄的起点层常量Swell4 条正弦沿 (.86, .51)、(−.42, .91)、(.13, −.99)、(−.97, −.24)倍率 ×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 − |s|)^5光 focus × (0.45 0.9·web)步进26 步t 4 50·f²权重exp(−0.06t)·(0.08 f)除以权重和稳定逐像素抖动interleaved gradient noise替代时间噪声太阳混合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 无收益用 ½ texel 偏移的 4 点 tent 上采样水面下侧平面 y12网络(1 − |swell(1.15s, 1.6t)|)^8·0.75 (1 − |swell(2.19s, 2.1t)|)^10·0.55雾exp(−0.028d)细节按1 − smoothstep(0.35, 1.6, 0.9·|fwidth(s)|)淡出颜色 (0.098, 0.221, 0.26)太阳光线按 0.018 × 波纹斜率弯曲鱼太阳键光 ×0.62 1.5 · shaftLight画面其余部分太阳冕减半为 (0.043, 0.095, 0.123)·μ^48屏幕空间射线 pass 从 0.82 降到 0.25以上大部分参数在 DEFAULTS 中以可覆盖默认值的形式存在surfaceY: 12、shaftScale: 1.3、shaftFloor: -0.05、gain: 3.6、color: [0.16, 0.40, 0.47]、resolution: 0.5传入createUnderwaterLight(THREE, { … })的 options 即可覆盖。八条规则每条对应一个它预防的失败这些规则来自 Blue Hour 帧的A/B 渲染同一相机、同一批冻结的鱼、一次只改一个变量SKILL.md。把波场塑造成稀疏峰值而不是波脊。1 − |swell|会形成一张薄片织网一条射线穿过许多薄片后被平均成扁平雾气。在冻结帧上波脊网把水面平均亮度抬高了 20%并把像素列之间的对比度从 0.247 砍半到 0.118。演示的Ridge web开关就是它。步进从 4 单位外开始。若从 1.5 单位起步一条从相机头顶掠过的光柱会用一块亮楔填满四分之一画面。用exp(−0.06t)加权近处采样模糊最少因此能保住光束边缘。当视线指向太阳时把光柱并入太阳辉光。垂直望向一条光柱积分退化为单个 swell 值相机恰好坐在两柱之间时太阳上会留一个黑洞。mix(total, 0.16, μ^28)把它补上。把焦散网络积分进步进循环内。若改在全分辨率下采样 4 次焦散会读作画在水面上的池底。放进 26 步半分辨率步进后它变成沿每束光延伸的细条纹。演示的Painted on screen开关就是它。压低画面里既有的均匀辉光。Blue Hour 的冕与屏幕空间射线 pass 把画面上方照得很均匀初版光柱在后来交付的增益下几乎显不出来只有把冕减半、把径向 pass 从 0.82 砍到 0.25 之后光柱才真正承载光线、缝隙保持黑暗。在合成后的页面上判断增益而不是在裸 canvas 上。增益 2.2 在 canvas 上看着正好但在页面上canvas 以 82% 不透明度放在压暗的 CSS 遮罩之下同一帧几乎什么都看不见。3.6 才能穿过页面存活。把太阳抬到画面上边缘之上得到满幅光束。21° 时太阳在画面内汇聚点只是辉光、光束很短30°–44° 时它移出上边缘光束扇形铺满整帧。演示的Sun滑块就是扫这个角度。每个 uniform 每个着色器只声明一次。LIGHT_GLSL自己声明uSurfaceY、uShaftScale、uShaftFloor、uCompare并期望宿主在它之前声明uSunDirection重复声明一次就是编译错误而材质会悄无声息地什么都不画。这 8 条规则在源码中的对应物清晰可见第 1/2 条对应shaftFloor与smoothstep的参数选择及步进区间/权重第 3 条对应 L136 的mix(total, .16, pow(mu, 28.))第 4 条对应uCompare.y分支L129 与 L93-L98演示的 UI 通过uCompare.x / uCompare.y两个开关实时切换见 demo/src/ui.js。性能成本SKILL.md 记录了在重型机器负载load average 约 60下、ANGLE Metal 上的 headless Chrome 实测渲染整个 Blue Hour 帧耗时6–10 ms2048×1440该帧已包含 2,200 条实例化沙丁鱼、水面、1024×720 的步进、屏幕空间射线 pass 与色调映射。跳过步进对中位数的影响小于 5 轮交替各 20 帧的逐次噪声约 ±2 ms说明步进并不是这里的瓶颈。如果它出现在你的性能剖析里就降低缓冲倍率或步数——光柱是低频信号这两者主要只牺牲柔和度但请先实测再依赖这个结论。生命周期与可访问性演示 demo/src/ui.js 完整实现了 SKILL.md 规定的三件事以 30 fps 步进模拟每步上限 0.1 s。Blue Hour 把水族馆渲染进固定 2048×1440 canvas800px 以下为 1600×1125再 cover-fit因此步进成本不随屏幕尺寸变化L130-L131prefers-reduced-motion: reduce下保持 t 0 的静止帧只在控件变化时重渲染因为抖动是稳定的逐像素抖动静止帧不会蠕动L6、L54-L62document.hidden时停止并在visibilitychange时重置帧时钟避免返回的第一帧把整个暂停期积分进去L125。演示的交互控件详见 demo/src/ui.html全部是可访问的真实控件Shaft patternSparse peaks / Ridge web与Caustic ripplesInside the march / Painted on screen两组玻璃药丸按钮加一个Sun范围滑块12°–58°默认 21°带实时角度读数控件隐藏按钮H 键与aria-live状态播报区也一应俱全。演示如何构建与路由demo/build.mjs 展示了一个值得借鉴的技能独立模块工程模式从 Blue Hour 参考页提取Three.js r170含 MIT 声明内联进单文件 HTMLfile://直接打开即可运行、零网络请求把 assets/underwater-light.mjs 转为 classic scriptwindow.UnderwaterLight内联——因为file://页面无法加载 ES module通过replaceOnce在唯一 seam 处把 Blue Hour 渲染器路由到模块用underwaterLight.render(renderer)替换原有 shaft pass、用underwaterLight.setSize(...)替换原尺寸逻辑、用underwaterDapple(vWorld,uTime)替换鱼的 dapple 计算并把light与sunDirection暴露到返回 API 供 UI 控制。这也印证了 SKILL.md 的承诺demo 运行的就是技能随附的同一个模块——模块本身零改动只在外围接线。验证清单SKILL.md 给出一组可操作的验收项适合在集成后逐条核对环绕相机光柱相对鱼群滑动且仍汇聚在太阳上若它们跟着镜头动就是被画在屏幕空间了。打开Ridge web水面变成雾气切回稀疏峰值能看到清晰光束、柱间是暗水。打开Painted on screen水面覆盖一层池底网纹步进内则是沿每束光的细条纹。直视太阳没有黑洞。太阳从 21° 扫到 44°汇聚点移出上边缘光束拉长铺满画面。一条鱼穿过光束时变亮游出后变暗。减少动态reduced motion保持合成静止帧控件仍能重渲染它且 1440×900 与 390×844 下控制台干净。直接复用与二次创作如果只想快速接入拷贝 assets/underwater-light.mjs 即可它不 import 第二份 Three.js。若要按自己的场景重做演示demo/PROMPT.md 提供了最小提示词与重混提示词两种范本最小提示词浓缩了全部机制要点漂移波场横跨太阳方向采样、半分辨率 HalfFloat 目标、26 步 4→54 加权步进、稀疏峰值、步进内焦散、并入太阳辉光、水面下侧与鱼群光斑、压低既有辉光重混提示词则示范如何在保持机制与预算不变的前提下换成新场景浅热带潟湖、正午 62° 太阳、绿海龟与吸盘鱼、暖化光柱颜色、光束落沙成焦散池。技能元数据agents/openai.yaml中的默认提示词也值得照抄它一句话总结了完整调用方式可作为在 Codex、Claude、Cursor 等 Agent 中触发本技能的入口。赞分享【免费下载链接】SkillsAgent skills for designers and builders using Codex, Claude, Cursor, and other AI coding agents项目地址https://gitcode.com/gh_mirrors/skills48/Skills点击查看免费下载相关推荐Three.js 水下体积光效实战用单一漂移涌浪场实现 Underwater God Rays含 Blue Hour 沙丁鱼群演示Three.js 水下体积光效实战用单一漂移涌浪场实现 Underwater God Rays含 Blue Hour 沙丁鱼群演示 导读 本文围绕 SkiTiXL LightRaysFx 算子深度解析God Rays 体积光效的算法原理、参数详解与实战调优TiXL LightRaysFx 算子深度解析God Rays 体积光效的算法原理、参数详解与实战调优 LightRaysFx 是 TiXL 实时图形引擎开音视频图形学桌面应用Three.js 3D水面效果实战从零构建动态水体场景的完整指南Three.js 3D水面效果实战从零构建动态水体场景的完整指南 想要在网页中创建令人惊叹的3D水面效果吗ThreeJS water项目为你提供了完美的解决图形学3D渲染创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考