ThreeJS Case 1 - Add video to the scene, use character actions and use the keyboard to control the animation of walking in the scene

Prepare

First we need two models, one is the scene model and the other is the character model.
The character model I use here is the model given in the Threejs official website, and the name is Xbot.glb.
Please add image description

Of course, you can also go to this website to download sketchfab for character models . After downloading, add animation to the model using mixamo
to download the model animation.

  1. Enter your model first

Please add image description

  1. Choose the right model file format

Please add image description

Here, pay attention to the two ways to add animation to the model using Blander software. The difference in specific writing methods will be discussed later.

Method 1: Split each individual animation.
Method 2: Unify all animations used in a timestamp.

Load scene

<!-- author: Mr.J -->
<!-- date: 2023-04-12 11:43:45 -->
<!-- description: Vue3+JS代码块模板 -->
<template>
  <div class="container" ref="container">
  </div>
</template>

<script setup>
import * as THREE from "three";
// 轨道
import {
      
       OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import {
      
       GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import {
      
       ref, reactive, onMounted } from "vue";
// 三个必备的参数
let scene,
  camera,
  renderer,
  controls,

onMounted(() => {
      
      
  // 外层需要获取到dom元素以及浏览器宽高,来对画布设置长宽
  // clientWidth等同于container.value.clientWidth
  let container = document.querySelector(".container");
  const {
      
       clientWidth, clientHeight } = container;
  console.log(clientHeight);

  init();
  animate();
  // 首先需要获取场景,这里公共方法放在init函数中
  function init() {
      
      
    scene = new THREE.Scene();
    // 给相机设置一个背景
    scene.background = new THREE.Color(0.2, 0.2, 0.2);
    // 透视投影相机PerspectiveCamera
    // 支持的参数:fov, aspect, near, far
    camera = new THREE.PerspectiveCamera(
      75,
      clientWidth / clientHeight,
      0.01,
      100
    );
    // 相机坐标
    camera.position.set(10, 10, 10);
    // 相机观察目标
    camera.lookAt(scene.position);
    // 渲染器
    renderer = new THREE.WebGLRenderer();
    // 渲染多大的地方
    renderer.setSize(clientWidth, clientHeight);
    container.appendChild(renderer.domElement);
    controls = new OrbitControls(camera, renderer.domElement);
    // 环境光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambientLight);

    // 方向光
    const directionLight = new THREE.DirectionalLight(0xffffff, 0.2);
    scene.add(directionLight);

    addBox();
  }

  function addBox() {
      
      
    new GLTFLoader().load(
      new URL(`../assets/changjing.glb`, import.meta.url).href,
      (gltf) => {
      
      
        scene.add(gltf.scene);
  }
  
  function animate() {
      
      
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    if (mixer) {
      
      
      mixer.update(clock.getDelta());
    }
  }
});
</script>

<style>
.container {
      
      
  width: 100%;
  height: 100vh;
  position: relative;
  z-index: 1;
}
</style>


After the scene is loaded, add the character model:

    new GLTFLoader().load(
      new URL(`../assets/Xbot.glb`, import.meta.url).href,
      (gltf) => {
    
    
        playerMesh = gltf.scene;
        scene.add(playerMesh);
        // 模型的位置
        playerMesh.position.set(13, 0.18, 0);
        // 模型初始面朝哪里的位置
        playerMesh.rotateY(-Math.PI / 2);
        // 镜头给到模型
        playerMesh.add(camera);
        // 相机初始位置
        camera.position.set(0, 2, -3);
        // 相机的位置在人物的后方,这样可以形成第三方视角
        camera.lookAt(new THREE.Vector3(0, 0, 1));
        // 给人物背后添加一个点光源,用来照亮万物
        const pointLight = new THREE.PointLight(0xffffff, 0.8);
        // 光源加载场景中
        scene.add(pointLight);
        // 在人物场景中添加这个点光源
        playerMesh.add(pointLight);
        // 设置点光源初始位置
        pointLight.position.set(0, 1.5, -2);
        console.log(gltf.animations);
      }
    );

Here you need to cancel the controller, delete the initial lens, and give the lens to the character model.
At this point, the model is all introduced.

Add video to the scene model

        gltf.scene.traverse((child) => {
    
    
          console.log("name:", child.name);
          if (child.name == "电影幕布" || child.name == "曲面展屏" || child.name == "立方体" ) {
    
    
            const video = document.createElement("video");
            video.src = new URL(
              `../assets/4a9d0b86dedea8b4cd31ac59f44e841f.mp4`,
              import.meta.url
            ).href;
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({
    
    
              map: videoTexture,
            });
            child.material = videoMaterial;
          }
          if (child.name == "2023"  || child.name == "支架") {
    
    
            const video = document.createElement("video");
            video.src = new URL(
              `../assets/c36c0c2d80c4084a519f608d969ae686.mp4`,
              import.meta.url
            ).href;
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({
    
    
              map: videoTexture,
            });
            child.material = videoMaterial;
          }
        });

Note: The reason why the video cannot be displayed may be that the problem of adding materials prevents the video from being displayed normally. We only need to set the uv here.
Please add image description

Please add image description

Please add image description

Regarding the problem of the video appearing upside down

In uv mode, select all models and rotate them to the appropriate angle.

Character walking effect

We have already given the camera to the character model before, and then we can use the keyboard to control the character to move forward.
Let’s talk about the two ways to use animation mentioned above.

1. Put all animations in one timestamp and animate themAnimationMixer

If you use the same timeline to load animations, you can use the animation mixerAnimationMixer

  // 剪切人物动作
  playerMixer = new THREE.AnimationMixer(gltf.scene);

  const clipIdle = THREE.AnimationUtils.subclip(gltf.animations[0],'idle',0,30);
  actionIdle = playerMixer.clipAction(clipIdle);
  // actionWalk.play();

  const clipWalk = THREE.AnimationUtils.subclip(gltf.animations[0],'walk',31,281);
  actionWalk = playerMixer.clipAction(clipWalk);

  // 默认站立
  actionIdle.play();

Only the first 30 frames are obtained as standing animation, and the following ones are standing and walking animation.

2. Store each animation as an independent animation element

If you use separate animation names, get all animationsanimation names directly

 animations = gltf.animations;
 console.log(animations)

Please add image description

Define a global variable to load animation effects

mixer = startAnimation(
  playerMesh, // 就是gltf.scene
  animations, // 动画数组
  "idle" // animationName,这里是"idle"(站立)
);

Idea: The default action requires a standing person. When using keyboard control, you need to use the animation that comes with the model to make the model move. Here you need to use keyboard events and
keydownkeyup

encapsulated animation functions in js.


 function startAnimation(skinnedMesh, animations, animationName) {
    
    
    const m_mixer = new THREE.AnimationMixer(skinnedMesh);
    const clip = THREE.AnimationClip.findByName(animations, animationName);
    if (clip) {
    
    
      const action = m_mixer.clipAction(clip);
      action.play();
    }
    return m_mixer;
  }
  let isWalk = false;
  window.addEventListener("keydown", (e) => {
    
    
    // 前进
    if (e.key == "w") {
    
    
      playerMesh.translateZ(0.1);
      if (!isWalk) {
    
    
        console.log(e.key);
        isWalk = true;

        mixer = startAnimation(
          playerMesh,
          animations,
          "walk" // animationName,这里是"Run"
        );
      }
    }
  });
    window.addEventListener("keyup", (e) => {
    
    
    console.log(e.key);
    if (e.key == "w"  ) {
    
    
      isWalk = false;
      mixer = startAnimation(
        playerMesh,
        animations,
        "idle" // animationName,这里是"Run"
      );
 
    }
  });

isWalkIt is used to control that the long press event will only be triggered once before being released. Otherwise, the wwalking animation will be repeatedly triggered by pressing and holding
. Add a clock function to the animation function, in which clock.getDelta()the method obtains the time interval between two frames. This method can directly update the mixture. device related time

  let clock = new THREE.Clock();
  function animate() {
    
    
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    if (mixer) {
    
    
      mixer.update(clock.getDelta());
    }
  }

Rotate camera with mouse

  window.addEventListener("mousemove", (e) => {
    
    
    if (prePos) {
    
    
      playerMesh.rotateY((prePos - e.clientX) * 0.01);
    }
    prePos = e.clientX;
  });

Realization effect:
Please add image description

Complete code:

/*
 * @Author: Southern Wind
 * @Date: 2023-06-24 
 * @Last Modified by: Mr.Jia
 * @Last Modified time: 2023-06-24 16:30:24
 */

<template>
  <div class="container" ref="container">
  </div>
</template>

<script setup>
import * as THREE from "three";
// 轨道控制器
import {
      
       OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// GLTF加载
import {
      
       GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import {
      
       ref, reactive, onMounted } from "vue";
// 全局变量
let scene, camera, renderer, playerMesh, prePos, mixer, animations;

onMounted(() => {
      
      
  // 外层需要获取到dom元素以及浏览器宽高,来对画布设置长宽
  // clientWidth等同于container.value.clientWidth
  let container = document.querySelector(".container");
  const {
      
       clientWidth, clientHeight } = container;
  console.log(clientHeight);

  init();
  animate();
  // 首先需要获取场景,这里公共方法放在init函数中
  function init() {
      
      
    scene = new THREE.Scene();
    // 给相机设置一个背景
    scene.background = new THREE.Color(0.2, 0.2, 0.2);
    // 透视投影相机PerspectiveCamera
    // 支持的参数:fov, aspect, near, far
    camera = new THREE.PerspectiveCamera(
      75,
      clientWidth / clientHeight,
      0.01,
      100
    );
    // 相机坐标
    // camera.position.set(10, 10, 10);
    // 相机观察目标
    camera.lookAt(scene.position);
    // 渲染器
    renderer = new THREE.WebGLRenderer();
    // 渲染多大的地方
    renderer.setSize(clientWidth, clientHeight);
    container.appendChild(renderer.domElement);
    // controls = new OrbitControls(camera, renderer.domElement);
    // 环境光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambientLight);

    // 方向光
    const directionLight = new THREE.DirectionalLight(0xffffff, 0.2);
    scene.add(directionLight);
    addBox();
  }

  function addBox() {
      
      
    new GLTFLoader().load(
      new URL(`../assets/changjing.glb`, import.meta.url).href,
      (gltf) => {
      
      
        scene.add(gltf.scene);
        gltf.scene.traverse((child) => {
      
      
          console.log("name:", child.name);
          if (
            child.name == "电影幕布" ||
            child.name == "曲面展屏" ||
            child.name == "立方体"
          ) {
      
      
            const video = document.createElement("video");
            video.src = new URL(
              `../assets/4a9d0b86dedea8b4cd31ac59f44e841f.mp4`,
              import.meta.url
            ).href;
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({
      
      
              map: videoTexture,
            });
            child.material = videoMaterial;
          }
          if (child.name == "2023" || child.name == "支架") {
      
      
            const video = document.createElement("video");
            video.src = new URL(
              `../assets/c36c0c2d80c4084a519f608d969ae686.mp4`,
              import.meta.url
            ).href;
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({
      
      
              map: videoTexture,
            });
            child.material = videoMaterial;
          }
        });
      }
    );
    new GLTFLoader().load(
      new URL(`../assets/Xbot.glb`, import.meta.url).href,
      (gltf) => {
      
      
        playerMesh = gltf.scene;
        scene.add(playerMesh);
        playerMesh.position.set(13, 0.18, 0);
        playerMesh.rotateY(-Math.PI / 2);
        playerMesh.add(camera);
        camera.position.set(0, 2, -3);
        camera.lookAt(new THREE.Vector3(0, 0, 1));
        const pointLight = new THREE.PointLight(0xffffff, 0.8);
        scene.add(pointLight);
        playerMesh.add(pointLight);
        pointLight.position.set(0, 1.5, -2);
        console.log(gltf.animations);
        animations = gltf.animations;

        mixer = startAnimation(
          playerMesh,
          animations,
          "idle" // animationName,这里是"Run"
        );
      }
    );
  }
  let isWalk = false;
  window.addEventListener("keydown", (e) => {
      
      
    // 前进
    if (e.key == "w") {
      
      
      playerMesh.translateZ(0.1);
      if (!isWalk) {
      
      
        console.log(e.key);
        isWalk = true;

        mixer = startAnimation(
          playerMesh,
          animations,
          "walk" // animationName,这里是"Run"
        );
      }
    }
  });
  window.addEventListener("keydown", (e) => {
      
      
    // 后退
    if (e.key == "s") {
      
      
      playerMesh.translateZ(-0.1);

      if (!isWalk) {
      
      
        console.log(e.key);
        isWalk = true;

        mixer = startAnimation(
          playerMesh,
          animations,
          "walk" // animationName,这里是"Run"
        );
      }
    }
  });
  window.addEventListener("keydown", (e) => {
      
      
    // 左
    if (e.key == "a") {
      
      
      playerMesh.translateX(0.1);
      if (!isWalk) {
      
      
        console.log(e.key);
        isWalk = true;

        mixer = startAnimation(
          playerMesh,
          animations,
          "walk" // animationName,这里是"Run"
        );
      }
    }
  });
  window.addEventListener("keydown", (e) => {
      
      
    // 右
    if (e.key == "d") {
      
      
      playerMesh.translateX(-0.1);
      playerMesh.rotateY(-Math.PI / 32);
      if (!isWalk) {
      
      
        console.log(e.key);
        isWalk = true;

        mixer = startAnimation(
          playerMesh,
          animations,
          "walk" // animationName,这里是"Run"
        );
      }
    }
  });
  let clock = new THREE.Clock();
  function startAnimation(skinnedMesh, animations, animationName) {
      
      
    const m_mixer = new THREE.AnimationMixer(skinnedMesh);
    const clip = THREE.AnimationClip.findByName(animations, animationName);
    if (clip) {
      
      
      const action = m_mixer.clipAction(clip);
      action.play();
    }
    return m_mixer;
  }
  window.addEventListener("mousemove", (e) => {
      
      
    if (prePos) {
      
      
      playerMesh.rotateY((prePos - e.clientX) * 0.01);
    }
    prePos = e.clientX;
  });
  window.addEventListener("keyup", (e) => {
      
      
    console.log(e.key);
    if (e.key == "w" || e.key == "s" || e.key == "d" || e.key == "a") {
      
      
      isWalk = false;
      mixer = startAnimation(
        playerMesh,
        animations,
        "idle" // animationName,这里是"Run"
      );
    }
  });
  function animate() {
      
      
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    if (mixer) {
      
      
      mixer.update(clock.getDelta());
    }
  }
});
</script>

<style>
.container {
      
      
  width: 100%;
  height: 100vh;
  position: relative;
  z-index: 1;
}
</style>


Guess you like

Origin blog.csdn.net/nanchen_J/article/details/131363416