计算机图形学之形变动画(Morph Target Animation):一场顶点的奇幻变身秀

215 阅读4分钟

想象一下,你正坐在电影院里,屏幕上的超级英雄下一秒就从普通人模样变成了身披战甲的战神,或者怪物从可爱小动物形态扭曲成狰狞巨兽。这炫酷的视觉效果背后,正是计算机图形学中形变动画(Morph Target Animation)在 “捣鬼”,今天就让我们一起揭开它神秘的面纱,看看这些顶点是如何在关键时刻完成华丽变身的!

在计算机的世界里,图形都是由一个个顶点组成的,就像用乐高积木搭建模型,每一块积木就是一个顶点。而形变动画,本质上就是基于关键帧的顶点变形动画。关键帧,就好比是动画片里的分镜,记录着角色在重要时刻的样子。在形变动画中,我们通过设定几个关键帧上图形顶点的位置,让计算机在这些关键帧之间自动计算并生成过渡帧,从而实现平滑的变形效果。

举个更贴近生活的例子,把图形想象成一团橡皮泥。你先把橡皮泥捏成圆形(这就是起始关键帧),再捏成方形(这是结束关键帧)。为了让橡皮泥从圆形变成方形,你可以在中间加几个过渡形状,比如椭圆形、菱形。计算机做的事情和这个差不多,只不过它操作的是图形的顶点。它会按照一定的规则,计算每个顶点在不同过渡帧中的位置,让图形看起来是自然变形的。

那么在 JavaScript 中,我们要怎么实现这个神奇的效果呢?首先,我们需要准备好图形的初始状态和目标状态,也就是起始关键帧和结束关键帧对应的顶点数据。假设我们有一个简单的三角形,用数组来存储它的顶点坐标:

// 初始三角形顶点坐标
const startTriangle = [
  { x: 0, y: 0 },
  { x: 100, y: 0 },
  { x: 50, y: 100 }
];
// 目标三角形顶点坐标(变形后的样子)
const endTriangle = [
  { x: 50, y: 50 },
  { x: 150, y: 50 },
  { x: 100, y: 150 }
];

接下来,我们要定义一个函数来计算过渡帧的顶点坐标。这里我们引入一个 “变形进度” 的概念,它是一个介于 0 到 1 之间的数值,0 表示完全是初始状态,1 表示完全变成了目标状态。通过这个进度值,我们可以对每个顶点的坐标进行线性插值,也就是按照一定比例去混合初始坐标和目标坐标。

function interpolateVertices(startVertices, endVertices, progress) {
  const interpolatedVertices = [];
  for (let i = 0; i < startVertices.length; i++) {
    const startVertex = startVertices[i];
    const endVertex = endVertices[i];
    interpolatedVertices.push({
      x: startVertex.x + (endVertex.x - startVertex.x) * progress,
      y: startVertex.y + (endVertex.y - startVertex.y) * progress
    });
  }
  return interpolatedVertices;
}

最后,我们可以在动画循环中不断更新变形进度,计算出每一帧的顶点坐标,并将其绘制到画布上,就能看到三角形慢慢变形的效果啦。这里我们借助 HTML5 的元素来进行绘制:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Morph Target Animation</title>
</head>
<body>
  <canvas id="myCanvas" width="400" height="400"></canvas>
  <script>
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');
    const startTriangle = [
      { x: 0, y: 0 },
      { x: 100, y: 0 },
      { x: 50, y: 100 }
    ];
    const endTriangle = [
      { x: 50, y: 50 },
      { x: 150, y: 50 },
      { x: 100, y: 150 }
    ];
    function interpolateVertices(startVertices, endVertices, progress) {
      const interpolatedVertices = [];
      for (let i = 0; i < startVertices.length; i++) {
        const startVertex = startVertices[i];
        const endVertex = endVertices[i];
        interpolatedVertices.push({
          x: startVertex.x + (endVertex.x - startVertex.x) * progress,
          y: startVertex.y + (endVertex.y - startVertex.y) * progress
        });
      }
      return interpolatedVertices;
    }
    let progress = 0;
    function animate() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      const interpolatedVertices = interpolateVertices(startTriangle, endTriangle, progress);
      ctx.beginPath();
      ctx.moveTo(interpolatedVertices[0].x, interpolatedVertices[0].y);
      for (let i = 1; i < interpolatedVertices.length; i++) {
        ctx.lineTo(interpolatedVertices[i].x, interpolatedVertices[i].y);
      }
      ctx.closePath();
      ctx.stroke();
      progress += 0.01;
      if (progress > 1) {
        progress = 0;
      }
      requestAnimationFrame(animate);
    }
    animate();
  </script>
</body>
</html>

在这个简单的示例中,我们只是对三角形进行了形变动画,但实际上,在复杂的 3D 图形中,形变动画可以应用于角色的表情变化、物体的形态转换等场景。而且,除了线性插值,还有很多更复杂、效果更逼真的插值算法,就像是给顶点的变身加上了特效滤镜,让它们的变形更加丝滑流畅。

希望通过这篇文章,你对计算机图形学中的形变动画有了更清晰的认识,也能动手用 JavaScript 实现属于自己的变形效果。快去发挥你的创意,让那些顶点在屏幕上跳起炫酷的变身舞蹈吧!

以上文章展示了形变动画的基础实现。你若想了解更多进阶内容,比如复杂模型的变形,或不同插值算法的效果,欢迎随时和我说。