webgl 本身不支持直接写动画,需手动用 requestanimationframe 驱动渲染循环,并在 javascript 中计算时间、运动参数,再通过 uniform 传入着色器;three.js 非必须但极大提升开发效率,适合快速原型与轻量展示,裸写仅适用于极致性能控制场景。

WebGL 能不能直接写动画?别这么想
不能。WebGL 本身只是浏览器里的底层图形 API,不提供任何动画逻辑、时间控制或补间功能——它连“下一帧该画啥”都要你手动算好。所谓“网页 3D 动画”,其实是你用 requestAnimationFrame 驱动 WebGL 渲染循环,再配合矩阵运算、着色器更新、纹理切换等一整套手动调度。
常见错误现象:drawArrays 调了一次就停了;模型转了半圈卡住;uniform 值没随时间更新,看起来完全静止。
- 必须自己维护时间戳(
performance.now()或requestAnimationFrame回调参数) - 所有运动逻辑(旋转角度、位移偏移、缩放系数)得在 JS 里算,再传给 GLSL 的
uniform - 每帧都得调
clear→useProgram→bindBuffer→uniform*→drawArrays,少一步就黑屏或错位
Three.js 是不是必须的?看你要不要省三个月
不是必须,但几乎等于必须。纯 WebGL 写一个带光照、阴影、相机控制、响应式缩放的旋转立方体,代码量轻松破 300 行;而 Three.js 里 20 行就能跑起来,且默认兼容各种显卡和 WebGL 版本。
使用场景:快速验证 3D 效果、产品页轻量展示、内部工具原型——直接上 Three.js;需要极致性能控制(比如万级粒子实时变形)、或嵌入已有 WebGL 框架时,才考虑裸写。
立即学习“前端免费学习笔记(深入)”;
-
THREE.MeshStandardMaterial自带 PBR 光照,裸 WebGL 得手写 Phong/Blinn 着色器 -
THREE.Clock自动管理 delta 时间,避免requestAnimationFrame时间抖动导致动画变速 - 注意
renderer.setPixelRatio(window.devicePixelRatio),否则高分屏下模型模糊——这个裸 WebGL 容易漏
动画卡顿?先查这三件事
90% 的“WebGL 动画卡”跟 GPU 无关,是 CPU 或内存被拖垮了。
常见错误现象:console 里频繁报 WARNING: Too many active WebGL contexts;滚动页面时动画突然掉帧;模型越多,帧率越线性下降。
- 每次创建新
WebGLRenderingContext(比如反复 newTHREE.WebGLRenderer)都会吃掉一个上下文限额(通常 16 个),超限后旧上下文被强制销毁,触发重编译着色器——巨卡 - 每帧都 new
THREE.Vector3或THREE.Matrix4,会触发 V8 频繁 GC;改用.set()+.copy()复用对象 - 纹理没压缩(比如传了 4096×4096 PNG),加载慢、上传 GPU 慢、显存爆满;优先用
KTX2+DRACO压缩模型
Shader 里写动画,和 JS 里写有啥区别?
本质区别是控制粒度:JS 控制“帧级”,Shader 控制“像素级”。你在 JS 里改一个 uniform Float uTime,顶点着色器就能让每个顶点按正弦波起伏,片元着色器能让表面随时间流动噪点——这种并行动画,JS 根本算不过来。
但坑也在这儿:Shader 里没法读 dom 尺寸、没法发网络请求、没法调用 date.now(),所有动态输入都得靠 JS 推过去。
-
uTime别直接传performance.now()毫秒值,除以 1000 变成秒,再模math.PI * 2防溢出 - 想实现“鼠标 hover 时起伏”,得把
mouseX/mouseY作为uniform vec2传入,不能在 Shader 里调document.querySelector - GLSL 里
sin()/cos()是高效指令,但pow(x, 0.5)比sqrt(x)慢 3 倍以上——移动端尤其明显
WebGL 动画真正的复杂点不在“怎么动”,而在“动的时候,其他东西还在同时发生”:窗口 resize、用户交互、纹理流式加载、多模型遮挡剔除……这些事一旦堆在一起,调试线索就断在 JS 和 Shader 的交界处。最容易被忽略的,是把 uniform 当全局变量用——它不会自动同步,每次 draw 前都得显式 gl.uniform1f。