第 14 课:综合实战——魔法阵
要解决的问题
游戏技能、加载反馈和互动装置常用环、刻度、符文与光晕组成可读的视觉层级。本课用一张平面、一个 ShaderMaterial 组合这些层。输入是时间、阵心、扰动强度和一次性触发值;输出是随时间变化的像素颜色。先保证圆环稳定,再逐层增加运动与交互。
从 UV 到极坐标
平面顶点自带 0–1 UV。顶点 Shader 将它写入 vUv;光栅化时,三角形三个顶点的值在片元位置插值。片元 Shader 用 p = (vUv - uCenter) * 2.0 把阵心移到原点。length(p) 是离阵心的半径,atan(p.y,p.x) 是角度。此处 p 不是世界坐标;画布若不是正方形,图案会在显示上拉伸,所以练习使用方形舞台。
圆环不是一条无限细的数学曲线,而是满足 abs(radius - 0.42) < width 的窄带。用 smoothstep 在边界混合颜色,fwidth(radius) 按像素导数估计过渡宽度,减少锯齿。将半径 0.42 改小,圆环向中心收缩;将宽度增大,线条变粗。只有最后一步写入 gl_FragColor;片元 Shader 的多个遮罩先各自计算,再加权组合。
符文、光晕与扰动
把角度除以一整圈 2π,再乘以 12 并取 fract,就得到 12 个重复扇区内各自的 0–1 局部坐标。对局部坐标设阈值会形成间断刻度;angle + uTime * 0.35 使刻度匀速转动。fract 在扇区边界不连续,所以不要用它直接做需要连续导数的噪声坐标。
光晕采用 exp(-k * abs(radius - target)),离环越远越暗;正弦让强度在 0–1 内循环。这里的正弦控制的是亮度,不应改变基础线条是否存在。噪声扰动通过邻近格点的随机值双线性平滑插值得到连续场,再以小振幅加到半径上。逐片元独立的 random() 会造成碎裂闪点;连续噪声更适合流动边缘。扰动强度为 0 时应回到正圆,这也是检查公式的最简单办法。
交互与声音的数据流
JavaScript 从 PointerEvent 得到画布内坐标,把 Y 轴反转后写入 uCenter。按下并拖动时调用 setPointerCapture,指针短暂移出画布仍能继续拖动;pointerup 和 pointercancel 结束拖动。片元 Shader 每帧用新的中心计算半径和角度。第 6 题用点击按钮启动 Web Audio;浏览器要求音频上下文由用户手势唤醒。点击时还将 uBurst 设为 1,渲染循环按秒逐渐衰减。振荡器每次短暂创建并在播放后断开,页面离开时关闭音频上下文。
常规实践与排查
这一类屏幕效果适合单平面 Shader:层数增加通常不会增加 draw call,但过宽、过亮的半透明区域仍会增加片元工作量。需要跟随三维物体时,可以把平面放到物体局部空间或另用后处理叠加;如果需要被场景遮挡,则要决定深度测试和透明混合策略。阵心超出可见区域时画面会像“消失”,先输出阵心距离灰度并检查 UV;音效无声时先确认用户点击、设备音量和 AudioContext.resume() 状态。
练习与验收
| 难度 | 题目 | 可观察验收 |
|---|---|---|
| ⭐ | 抗锯齿圆环 | 中心完整圆环,边缘平滑;改半径后大小改变 |
| ⭐⭐ | 极坐标旋转符文 | 12 个等间距刻度围绕中心转动 |
| ⭐⭐ | 脉冲光晕 | 约 2 秒一轮的明暗变化,基础圆环持续可见 |
| ⭐⭐⭐ | 噪声扰动 | 外缘连续起伏;强度归零后恢复正圆 |
| ⭐⭐⭐ | 指针驱动中心 | 鼠标和触摸拖动阵心,松开后保留位置 |
| ⭐⭐⭐⭐ | 完整魔法阵与音效 | 多层同时可见;可拖动;按钮每次点击触发短音与光晕增强 |