作为一名 WebGL 开发者,你一定深有体会:无论是粒子动画、模型交互,还是流畅的相机运动,都离不开大量数学计算。这些重复的底层计算往往耗费大量精力。
今天向你推荐的,就是一款专为简化这些操作而诞生的工具:maath
。
它由知名的 pmndrs 团队(即 React Three Fiber 和 drei 等著名项目的开发者)维护,专门用于简化 WebGL 场景中的缓动动画、随机点生成和向量操作等任务。
项目地址:https://github.com/pmndrs/maath (opens in a new tab)
为什么使用 maath
?
maath
能够:
- 大幅提高开发效率:常见数学动画一行搞定;
- 降低代码维护成本:清晰、直观的 API 设计;
- 优化动画性能:内置高效的缓动与插值算法,性能优秀;
- 完美融合 React Three Fiber 生态:无缝适配 R3F 场景。
总之,它帮助你把精力聚焦于创意实现,而不是繁琐数学计算。
安装方式
yarn add maath
# 或 npm install maath
导入示例如下:
import { buffer, random, easing } from "maath";
// 或单项引入
import { inSphere } from "maath/random";
import { lerpBuffers } from "maath/buffer";
import { dampE } from "maath/easing";
演示
案例一:粒子随机分布在球体内
传统方式的痛点:
- 需要自己写随机算法;
- 性能优化麻烦,难以高效批量生成。
使用 maath 实现:
import * as THREE from "three";
import { random } from "maath";
const scene = new THREE.Scene();
const positions = random.inSphere(new Float32Array(3000 * 3), { radius: 5 });
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({ color: 0xffffff, size: 0.05 });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
效果优势:
- 简单高效,一行代码生成数千粒子;
- 性能优异,优化了内存与计算效率。
案例二:平滑动画插值
传统方式的痛点:
- 手动写插值循环,性能低下;
- 不易控制动画平滑度。
使用 maath 实现:
import { buffer, random } from "maath";
const positionsA = random.inSphere(new Float32Array(1000 * 3), { radius: 3 });
const positionsB = random.inBox(new Float32Array(1000 * 3), { side: 6 });
const interpolatedPositions = new Float32Array(1000 * 3);
let t = 0; // 插值进度
function animate() {
requestAnimationFrame(animate);
t += 0.005;
if (t > 1) t = 0;
buffer.lerp(positionsA, positionsB, interpolatedPositions, t);
// 更新几何体位置
geometry.attributes.position.needsUpdate = true;
renderer.render(scene, camera);
}
animate();
效果优势:
- 快速实现平滑插值动画;
- 高效且平滑,自然流畅,无需额外优化。
案例三:自然平滑的相机跟随
传统方式的痛点:
- 普通插值 (
lerp
) 在快速运动时易抖动; - 自己实现阻尼算法复杂。
使用 maath 实现:
import { easing } from "maath";
window.addEventListener("mousemove", (event) => {
const mouseX = (event.clientX / window.innerWidth - 0.5) * 2;
const mouseY = -(event.clientY / window.innerHeight - 0.5) * 2;
// 增加Z轴的微小变化,让移动更有层次感
cameraTarget.x = mouseX * 5; // 增加移动范围
cameraTarget.y = mouseY * 3; // Y轴移动相对保守
cameraTarget.z = 10 + mouseX * 2; // Z轴根据水平移动变化
});
function animateCamera(currentTime = 0) {
requestAnimationFrame(animateCamera);
// 计算帧间时间差,让动画更稳定
const deltaTime = (currentTime - lastTime) * 0.001; // 转换为秒
lastTime = currentTime;
// 使用更平滑的插值参数,并基于实际时间
const dampingFactor = Math.min(deltaTime * 8, 1); // 限制最大值防止跳跃
easing.damp3(camera.position, cameraTarget, 0.08, deltaTime || 0.016);
// 让摄像机始终看向场景中心,但添加轻微的偏移让视角更自然
const lookTarget = new THREE.Vector3(
cameraTarget.x * 0.1, // 视线跟随鼠标轻微偏移
cameraTarget.y * 0.1,
0
);
camera.lookAt(lookTarget);
renderer.render(scene, camera);
}
效果优势:
- 自然流畅的平滑移动;
- 极大提升用户体验,仅需几行代码实现。
案例四:坐标快速变换
传统方式的痛点:
- 手动重排数据易出错;
- 代码冗余。
使用 maath 实现:
import { buffer } from "maath";
const original = new Float32Array([1, 2, 3, 4, 5, 6]); // 两个顶点: [1,2,3], [4,5,6]
const rearranged = buffer.swizzleBuffer(original, "xzy");
// rearranged: [1,3,2, 4,6,5]
效果优势:
- 一行实现数据交换,降低出错概率;
- 提升开发效率与代码可读性。
最后
通过以上几个小案例,相信你已感受到 maath 带来的巨大便利:
- 不用再自己编写复杂数学计算;
- 更少的代码,更自然的动画;
- 更多的精力用于创意表达。