第 5 课:时间与动画

返回教程目录 · 打开本课练习

动画是位置与时间的函数

Shader 中不必“移动一张图”才能做动画。只要颜色函数同时接受位置和时间,color = f(uv,time),每一帧给相同位置算出的颜色就会改变。本课从滚动条纹和颜色脉冲开始,逐步组合水流、雷达、心跳、电视雪花与点击涟漪。

JS 的渲染循环每帧接收 dt,以秒累加到 uTime。uTime 是 uniform:这一帧的每个顶点和片元读到同一个数。绘制时 GPU 根据它重新计算片元颜色。公共运行时会把异常大的单帧 dt 限到 0.05 秒,避免页面从后台回来时效果突然跳过很长一段;它是演示用的动画时间,不等同于严格的墙钟时间。实际项目若需要和音频或服务器时间同步,应选择相应时间源。

周期、相位与速度

sin(x) 的一个完整周期是 2π 弧度。sin(uTime * 2.0) 中的 2.0 是角速度,周期约为 2π/2 = 3.14 秒。若需要 2 秒一轮,可用角速度 2π/2 = π。第 2 题把 sin 的 -1–1 映射成 0–1,以此混合暗色和亮色。

滚动条纹把 uTime 加进空间相位:

float phase = fract(vUv.x * 7.0 + uTime * 0.25);
float stripe = step(0.5, phase);

vUv.x*7 是七个空间周期,每个周期有深浅两条,共十四条。+uTime 让保持相同相位的位置向左移动;换为减号则向右。移动速度取决于时间系数除以空间频率:这里约为 0.25/7 个 UV 单位每秒。只看时间系数容易误判实际位移速度。

把基础函数组合成常见效果

第 9 题在画布 pointerdown 时,JS 读取画布的 getBoundingClientRect(),把指针坐标转成 0–1 UV,Y 轴再翻转,传入 uClick,并把 uAge 清零。片元以 length(vUv-uClick) 求到点击点的距离,让波前半径随 uAge 增大,再用指数衰减让它消失。当前版本只保存最近一次点击;若要同时存在多圈,需要维护多组位置和起始时间,可用 uniform 数组、粒子系统或纹理缓冲区。

数据流与工程实践

Three.js 把平面 position、uv 送到顶点 Shader,顶点 Shader 输出位置与 vUv,光栅化阶段插值后片元 Shader 根据 vUv 和 uTime 求颜色。点击题另外从 JS 传入 uClick/uAge。这样 JS 不必逐像素改图片,GPU 并行处理片元;代价是高分辨率、大量数学运算和多层采样会增加片元负载。

渲染循环只更新已有 uniform,不要每帧创建几何体、材质或纹理。时间长期累加会损失浮点小数精度,尤其在移动端 mediump;需要长时间运行的周期效果可在 JS 里对时间取模,或传入已经归一化的相位。sin、fract、mod 的周期边界是否连续取决于最终颜色函数:硬条纹适合接缝,平滑背景则要让周期两端颜色匹配。

排查顺序

动画不动:先查看 main.js 是否按 dt 更新对应 uniform、变量名是否一致;再把 uTime 直接映射到颜色确认值在变。速度异常:检查毫秒与秒、角速度与周期、空间频率与位移速度。鼠标波纹位置不对:检查画布矩形、页面滚动和 Y 轴翻转。图案突然跳变:检查 fract/mod 接缝和页面从后台恢复时的 dt。

练习

难度 题目 可见验收
⭐ 时间驱动水平滚动条纹 条纹连续向左移动
⭐ 正弦颜色脉冲 颜色平滑明暗变化
⭐⭐ UV 扭曲与滚动水流 弯曲水纹流动
⭐⭐ 半径随时间呼吸的光环 圆环周期伸缩
⭐⭐ 极坐标雷达扫描 扇形持续旋转
⭐⭐⭐ 短促心跳与慢速余辉 短亮脉冲与衰减
⭐⭐⭐ 从中心扩散的波纹 同心亮纹向外移动
⭐⭐⭐⭐ 时间切片的电视雪花 灰度噪点逐帧闪烁
⭐⭐⭐⭐ 点击位置产生衰减涟漪 点击处扩散并渐隐