分享
Maath

作为一名 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 带来的巨大便利:

  • 不用再自己编写复杂数学计算;
  • 更少的代码,更自然的动画;
  • 更多的精力用于创意表达。