vue3结合three.js实现3D带有交互的动画

three.js引入
npm install three
安装轨道控件插件:
npm install three-orbit-controls
安装渲染器插件:
npm i --save three-css2drender
vue文件中引用:
import * as Three from 'three'
在页面中创建场景
//创建一个三维场景
const scene = new THREE.Scene();
创建一个透视相机
//创建一个透视相机,窗口宽度,窗口高度
const width = window.innerWidth,
  height = 400;
const camera = new THREE.PerspectiveCamera(38, width / height, 0.25, 100);
//设置相机位置
camera.position.set(-5, 3, 10);
//设置相机方向
camera.lookAt(new THREE.Vector3(0, 2, 0));
初始化渲染器
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearAlpha(0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, height);
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(renderer.domElement);

window.addEventListener("resize", onWindowResize);
初始动画混合器
// 创建动画混个器
let mixer = new THREE.AnimationMixer(scene);
// 创建时钟对象 该对象用于跟踪时间
let clock = new THREE.Clock();

动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。

参数:rootObject 混合器播放的动画所属的对象。就是包含动画模型的场景对象。
常用参数和属性:
.time 全局的混合器时间。
.clipAction(AnimationClip) 返回所传入的剪辑参数的AnimationAction对象。AnimationAction用来调度存储在AnimationClip中的动画。
AnimationClip 动画剪辑,是一个可重用的关键帧轨道集,它代表动画。
.getRoot() 返回混合器的根对象。
.update() 推进混合器时间并更新动画。在渲染函数中调用更新动画。

创建光源,用于视觉可见
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 20, 0);
 scene.add(hemiLight);

 const dirLight = new THREE.DirectionalLight(0xffffff);
 dirLight.position.set(0, 20, 10);
 scene.add(dirLight);
创建加载glb文件模型
const loader = new GLTFLoader();
loader.load(
   "RobotExpressive.glb",
   function (gltf) {
     // 存储模型含有动画集合
     model = gltf.scene;
     scene.add(model);
     createGUI(model, gltf.animations);
   },
   undefined,
   function (e) {
     console.error(e);
   }
 );

最后加上动画循环渲染

const animate = () => {
const dt = clock.getDelta();
	if (mixer) mixer.update(dt);
	 requestAnimationFrame(animate);
	 renderer.render(scene, camera);
	 stats.update();
};

完整代码

<template>
   <div id="my-three"></div>
</template>
<script>
import {
  defineComponent,
  getCurrentInstance,
  onMounted,
  onUnmounted,
  computed,
  reactive,
  toRefs,
} from "vue";

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

export default defineComponent({
  name: "index",
  setup() {
    const ctx = getCurrentInstance();
    let state = reactive({
      currentIndex:0,
    });
    //创建一个三维场景
    const scene = new THREE.Scene();
    //创建一个透视相机,窗口宽度,窗口高度
    const width = window.innerWidth,
      height = 400;
    const camera = new THREE.PerspectiveCamera(38, width / height, 0.25, 100);
    //设置相机位置
    camera.position.set(-5, 3, 10);
    //设置相机方向
    camera.lookAt(new THREE.Vector3(0, 2, 0));

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    let mixer = new THREE.AnimationMixer(scene);
    let stats = new Stats();
    let clock = new THREE.Clock();
    let actions = {},
      activeAction,
      previousAction;
    // 默认动画
    const api = {state:'Walking'}
    //控制器
    // const Controls = new OrbitControls(camera, renderer.domElement);
    onMounted(() => {
      onLoad();
    });
    const onLoad = () => {
      let model, container;
      container = document.getElementById("my-three");

      // scene.background = new THREE.Color(0xe0e0e0);
      scene.fog = new THREE.Fog(0xe0e0e0, 20, 100);

      // lights

      const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
      hemiLight.position.set(0, 20, 0);
      scene.add(hemiLight);

      const dirLight = new THREE.DirectionalLight(0xffffff);
      dirLight.position.set(0, 20, 10);
      scene.add(dirLight);

      // model
      const loader = new GLTFLoader();
      loader.load(
        "RobotExpressive.glb",
        function (gltf) {
          // 存储模型含有动画集合
          model = gltf.scene;
          scene.add(model);
          createGUI(model, gltf.animations);
        },
        undefined,
        function (e) {
          console.error(e);
        }
      );

      renderer.setClearAlpha(0);
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, height);
      renderer.outputEncoding = THREE.sRGBEncoding;
      container.appendChild(renderer.domElement);

      window.addEventListener("resize", onWindowResize);
      container.appendChild(stats.dom);
      animate();

      // 点击动画
      renderer.domElement.addEventListener("click", (event) => {
        const { offsetX, offsetY } = event;
        const x = (offsetX / window.innerWidth) * 2 - 1;
        const y = -(offsetY / window.innerHeight) * 2 + 1;
        const mousePoint = new THREE.Vector2(x, y);

        const raycaster = new THREE.Raycaster();
        raycaster.setFromCamera(mousePoint, camera);
        const instersects = raycaster.intersectObjects(scene.children);

        instersects.forEach((item) => {
          console.log(item.object.name);
          if (
            item.object.name == "Head_2" ||
            item.object.name == "Head_3" ||
            item.object.name == "Head_4"
          ) {
            // console.log(instersects);
            // action.stop();
            const states = [
              "Idle",
              "Dance",
              "Death",
              "Sitting",
              "Standing",
              "Jump",
              "Yes",
              "No",
              "Wave",
              "Punch",
              "ThumbsUp",
            ];
            if(state.currentIndex<states.length - 1){
              state.currentIndex = state.currentIndex+1
            }else{
              state.currentIndex  = 0
            }
            fadeToAction(states[state.currentIndex], 0.2);
            mixer.addEventListener("finished", restoreState);
          }
        });
      });
    };
    const createGUI = (model, animations) => {
      const states = [
        "Idle",
        "Walking",
        "Running",
        "Dance",
        "Death",
        "Sitting",
        "Standing",
      ];
      const emotes = ["Jump", "Yes", "No", "Wave", "Punch", "ThumbsUp"];
      for (let i = 0; i < animations.length; i++) {
        const clip = animations[i];
        const action = mixer.clipAction(clip);
        actions[clip.name] = action;
        if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) {
          action.clampWhenFinished = true;
          action.loop = THREE.LoopOnce;
        }
      }
      activeAction = actions["Walking"];
      activeAction.play();
    };
    const fadeToAction = (name, duration) => {
      previousAction = activeAction;
      activeAction = actions[name];

      if (previousAction !== activeAction) {
        previousAction.fadeOut(duration);
      }

      activeAction
        .reset()
        .setEffectiveTimeScale(1)
        .setEffectiveWeight(1)
        .fadeIn(duration)
        .play();
    };
    const restoreState = () => {
      mixer.removeEventListener("finished", restoreState);

      fadeToAction(api.state, 0.2);
    };

    const animate = () => {
      const dt = clock.getDelta();

      if (mixer) mixer.update(dt);

      requestAnimationFrame(animate);

      renderer.render(scene, camera);

      stats.update();
    };
    const onWindowResize = () => {
      camera.aspect = window.innerWidth / height;
      camera.updateProjectionMatrix();

      renderer.setSize(window.innerWidth, height);
    };
    onUnmounted(() => {
      scene.traverse((e) => {
        if (e.BufferGeometry) e.BufferGeometry.dispose();
        if (e.material) {
          if (Array.isArray(e.material)) {
            e.material.forEach((m) => {
              m.dispose();
            });
          } else {
            e.material.dispose();
          }
        }
        if (e.isMesh) {
          e.remove();
        }
      });
      scene.remove();
      renderer.dispose();
      renderer.content = null;
      window.removeEventListener("resize", onWindowResize, false);
    });

    return {
      ...toRefs(state),
      onLoad,
      createGUI,
    };
  },
});
</script>
<style lang="less">
@import "./index.less";
</style>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值