xiaxiangx 阅读(20) 评论(0)

Animation 体验一

动画效果

animation2

其中涉及到 skeletion、clipAction、GUI

Skeletion

在建模软件中可导出 skeletion,这里安利一个可以创建动画的网站 ,以上模型均从该网站创建下载,注意,在下载时需要勾选 Skeletion

mixamo

加载好模型后,就可以使用 helper 方法创建骨架,并将其添加至场景中

// 加载骨架,obj 为加载好后的模型
skeletion = new THREE.SkeletonHelper(obj);
// 设置可见性为 false
skeletion.visible = false;
// 添加到场景中
scene.add(skeletion);

clipAction

使用 API 最好的方法是去官网搜索

AnimationAction – three.js docs (threejs.org)

首先获取到跳舞动作的 action

clipAction = mixer.clipAction(obj.animations[0]);

点击暂停播放按钮进行动作切换,动作切换的 api 十分简单

// 如果动作正在进行就停止,否指播放
if (clipAction.isRunning()) {
    clipAction.stop();
    } else {
    clipAction.play();
}

GUI

import { GUI } from "three/examples/jsm/libs/dat.gui.module";

该 UI 库能够绑定 js 中的变量和 UI 上显示的变量

使用方法如下

  1. 创建相应面板
const panel = new GUI({ width: 200 });
// domElement 可以获取到 dom 元素
panel.domElement.style.marginTop = "10px";

// 分别创建两个父文件夹并打开
const folder1 = panel.addFolder("visibility");
const folder2 = panel.addFolder("pause/continue");
folder1.open();
folder2.open();

image-20211010142530196

  1. 绑定对象中的数据
setting = {
    "show model": true,
    "show skeleton": false,
    "pause/continue": pauseContinue,
};

// folder.add() 第一个参数为对象,第二个参数为属性名,第三个参数代表发生变化时触发的回调函数

// 使用 setting 中的 show model 属性,由于值时 Boolean 类型,因此回调函数的参数也是相同类型
folder1.add(setting, "show model").onChange((visible) => {
    model.visible = visible;
});
// 直接绑定 skeletion 对象上的 visible 属性,界面上显示的就是 skeletion 对象上真实的 visible
folder1.add(skeletion, "visible").onChange((visible) => {
    skeletion.visible = visible;
});
// 相当于添加了一个点击按钮,触发 pauseContinue 方法
folder2.add(setting, "pause/continue");

function pauseContinue() {
    if (clipAction.isRunning()) {
        clipAction.stop();
    } else {
        clipAction.play();
    }
}

源代码

import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { GUI } from "three/examples/jsm/libs/dat.gui.module";
import Stats from "three/examples/jsm/libs/stats.module";

export function List2() {
  const content = useRef();

  // 场景
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0xa0a0a0);
  scene.fog = new THREE.Fog(0xa0a0a0, 10, 500);
  const pointLight = new THREE.PointLight(0xffffff, 0.6);
  pointLight.position.set(150, 150, 150);
  scene.add(pointLight);
  scene.add(new THREE.AmbientLight(0xffffff, 2));

  // 骨架
  let skeletion, model, clipAction;
  let setting;

  // 添加平面
  const mesh = new THREE.Mesh(
    new THREE.PlaneGeometry(1000, 1000),
    new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false })
  );
  mesh.rotation.x = -Math.PI / 2;
  mesh.receiveShadow = true;
  scene.add(mesh);
  const clock = new THREE.Clock();

  let mixer;

  useEffect(() => {
    const canvas = content.current;
    const renderer = new THREE.WebGLRenderer({ antialias: true, canvas });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);

    const stats = new Stats();
    canvas.appendChild(stats.dom);

    const camera = new THREE.PerspectiveCamera(
      40,
      canvas.clientWidth / canvas.clientHeight,
      1,
      1000
    );
    camera.position.set(200, 200, 200);

    const controls = new OrbitControls(camera, canvas);
    controls.update();

    const loader = new FBXLoader();
    loader.load("/RumbaDancing.fbx", (obj) => {
      scene.add(obj);
      model = obj;

      skeletion = new THREE.SkeletonHelper(obj);
      skeletion.visible = false;
      scene.add(skeletion);

      mixer = new THREE.AnimationMixer(obj);
      clipAction = mixer.clipAction(obj.animations[0]);
      clipAction.play();

      createPanel();
    });

    animate();

    function createPanel() {
      const panel = new GUI({ width: 200 });
      panel.domElement.style.marginTop = "10px";

      const folder1 = panel.addFolder("visibility");
      const folder2 = panel.addFolder("pause/continue");
      folder1.open();
      folder2.open();

      setting = {
        "show model": true,
        "show skeleton": false,
        "pause/continue": pauseContinue,
      };

      folder1.add(setting, "show model").onChange((visible) => {
        model.visible = visible;
      });
      folder1.add(skeletion, "visible").onChange((visible) => {
        skeletion.visible = visible;
      });
      folder2.add(setting, "pause/continue");

      function pauseContinue() {
        if (clipAction.isRunning()) {
          clipAction.stop();
        } else {
          clipAction.play();
        }
      }
    }

    function animate() {
      requestAnimationFrame(animate);

      const delta = clock.getDelta();

      if (mixer) mixer.update(delta);

      controls.update();

      stats.update();

      renderer.render(scene, camera);
    }
  });
  return <canvas ref={content} />;
}