Three.js 3D模型爆炸分解

你是否曾想展示一些很酷的发动机的 3D 模型? 这是一种可行的方法,使用一些非常基本的数学:一个简单的分解视图💣

为此,我们将使用 React-Three-Fiber,但它可以与原生 Three.js 配合使用(它与 r3f 生态系统没有深度绑定,只是对我来说迭代速度更快)。

1、准备3D模型

首先,自己制作或者从网上下载一个要实现爆炸分解效果的3D模型,如果需要转换格式,可以使用NSDT 3DConvert这个在线3D格式转换工具

打开你最喜欢的建模工具(我用blender),将模型(如果需要)拆分成几个部分。

例如,我正在使用这个很酷的资产,需要做一些工作才能使用它。

首先选中模型:

进入编辑模式:

选择属于要分离的元素一部分的三角形:

按 Ctrl + L选中所有邻接的三角形。 确保选中了你想要的部分,或者手动选择所有三角形。

按 F3 键,显示“快速操作”面板,然后输入“Sepa”。 选择第一个选项。

恭喜,你现在有了两个独立的网格。 请务必以易于理解的方式重命名。 重复这个过程直到你得到所有单独的部件。

返回对象模式(按 TAB),选择所有元素(按 A),按 F3 并键入 center,然后选择选项“原点到质量中心(体积)”。 它基本上会将每个网格的中心设置为其质心(物质的某个平均点)。

导出到 GLB 就可以了。

接下来就是变得有趣的地方。 让我们通过令人难以置信的绘图来看看如何实现某种自动分解视图效果。 先理论,后实施。

2、3D模型爆炸分解效果的原理

下面是二维的钻头:

最朴素的思维方式就是以世界中心(0,0,0)作为我们分解图的参考。 对于钻头来说,考虑到形状,它不会那么漂亮。 我们希望中心点更高,以便位于同一旋转轴上的旋转部分向前移动。 但无论如何,无论你的中心点是什么,原理都是一样的。

你会想要找到位移的方向。 “什么给我们指明了方向?”,你会问。 一个向量。

公式很简单: 向量BA= A点 - B点

因此,在我们的上下文中,我们会说 位移向量 = 工件的质心 - 中心 , 这样我们可以将此向量归一化,使其成为单位向量(这意味着长度为 1)。 这样我们就可以更容易地控制沿每个向量的位移强度。

我们需要一个标准来移动钻头的零件。一个简单的标准是零件中心与爆炸中心之间的距离。 离中心越远,我们就越希望在爆炸分解视图中离开。 我们将其称为我们在上一步中计算的向量的大小。

在钻头上,正如我之前所说,我们将采用较高的(Y 轴)点作为中心,因此旋转元件保持沿同一轴。

来吧,让我们实现这个。

3、3D模型爆炸分解效果的实现💥

使用 drei 库中的 useGLTF 加载模型,如果你使用的是 原生Three.js ,则使用 GLTFLoader:

 export const Drill = () => {
  const gltf = useGLTF("drill-corrected.glb");
  return <primitive object={gltf.scene}></primitive>
}

遍历场景来计算每个位移。 如果你想像我一样冻结它,可以丢弃主要对象。

export const Drill = () => {
  const gltf = useGLTF("drill-corrected.glb");

  const mainObject = gltf.scene.getObjectByName("Main_") as Mesh;
  const targetMap: Map<string, Vector3> = new Map<string, Vector3>();
  const explosionCenter = new Vector3(0, 0.15, 0); // Higher epicenter for the drill
  const explosionFactor = 0.8; // Move uniformly along the direction for every part.

  useEffect(() => {
    gltf.scene.traverse((object) => {
      if (object.uuid !== mainObject.uuid) { // Discard the main object so it is frozen.
        const vector = object.position.clone().sub(explosionCenter).normalize();

        const displacement = object.position
          .clone()
          .add(
            vector.multiplyScalar(
              object.position.distanceTo(explosionCenter) * explosionFactor
            )
          );
        targetMap.set(object.name, displacement); // Store it in a dictionnary for later use.
      }
    });
  }, []);


  return (
    <>
      <primitive object={gltf.scene}></primitive>
    </>
  );
};

object.position.distanceTo(explosionCenter) 是计算距中心的距离,正如我之前解释的那样。 爆炸因子是均匀控制各部分膨胀的一种方法。 你可以根据需要调整它。

const vector = object.position.clone().sub(explosionCenter).normalize(); 是我们需要计算的围绕中心扩展的方向。 正如你所记得的,我们将其标准化,每个部分都有一个单位向量(长度为 1),这样我们就可以通过爆炸因子(即大小)来统一控制。

我将所有这些存储在地图中,它将充当每个部分的字典。 我想稍后使用这些位移值来制作爆炸动画,例如使用 GSAP 动画库。

来吧,你的效果就出来了。 现在,额外的,让我们用两种不同的技术来制作动画。我们将使用 GSAP,但你可以使用自己最喜欢的动画库。

5、简单的3D模型爆炸分解动画

让我们添加动画:

gltf.scene.traverse((object) => {
        if (object.uuid !== mainObject.uuid) {
          const displacement = targetMap.get(object.name) as Vector3;
          const tl = gsap.timeline().to(object.position, {
            x: displacement.x,
            y: displacement.y,
            z: displacement.z,
            duration: 5,
          });
        }
      });

对于每个部分,减去之前丢弃的部分,我们将取回贴图中的位移并通过 GSAP 应用它。 我们将对react用户使用useEffect,我们需要等待计算完成后才能搜索贴图。 因此,我将添加一个触发状态,一旦第一个代码设置了它,它将触发第二个 useEffect 和我们的动画。

const [trigger, setTrigger] = useState(false);

  useEffect(() => {
    gltf.scene.traverse((object) => {
      if (object.uuid !== mainObject.uuid) {
        const vector = object.position.clone().sub(explosionCenter).normalize();

        const displacement = object.position
          .clone()
          .add(
            vector.multiplyScalar(
              object.position.distanceTo(explosionCenter) * explosionFactor
            )
          );
        targetMap.set(object.name, displacement);
      }
    });
    setTrigger(true);
  }, []);

  useEffect(() => {
    if (!trigger) {
      gltf.scene.traverse((object) => {
        if (object.uuid !== mainObject.uuid) {
          const displacement = targetMap.get(object.name) as Vector3;
          const tl = gsap.timeline().to(object.position, {
            x: displacement.x,
            y: displacement.y,
            z: displacement.z,
            duration: 5,
          });
        }
      });
    }
  }, [trigger]);

通过 gsap.timeline().to(),我们使用其位移来对循环当前对象的位置进行动画处理。

6、基于滚动的3D模型爆炸分解动画

使用ScrollTrigger插件和scrub属性,我们可以做一个很酷的网站,使用页面的滚动来触发扩展。

将scrub属性设置为true,将动画直接绑定到滚动。 使用一个值为动画添加了一些惯性会更漂亮。 基于上节的动画,只需要使用 gsap.registerPlugin(ScrollTrigger); 添加 ScrollTrigger 插件即可。

然后将动画更改为:

      let scrollTriggerParams = {
        toggleActions: "play none none reverse",
        scrub: 1.5,
      };
      gltf.scene.traverse((object) => {
        if (object.uuid !== mainObject.uuid) {
          const displacement = targetMap.get(object.name) as Vector3;
          gsap
            .timeline({
              scrollTrigger: {
                ...scrollTriggerParams,
              },
            })
            .to(object.position, {
              x: displacement.x,
              y: displacement.y,
              z: displacement.z,
            });
        }
      });

给页面一些滚动空间,将画布设置为 CSS 中的固定位置,添加一些高度为 100vh 的占位符部分。

然后当滚动时,你将看到模型展开。

7、结束语

我希望这个简短的教程对你有用。 玩得开心!


原文链接:Exploded view of a 3D model using React-Three-Fiber

BimAnt翻译整理,转载请标明出处