ThreeJS案例一——在场景中添加视频,使用人物动作以及用键盘控制在场景中行走的动画

这篇具有很好参考价值的文章主要介绍了ThreeJS案例一——在场景中添加视频,使用人物动作以及用键盘控制在场景中行走的动画。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

准备

首先我们需要两个模型,一个是场景模型,另一个是人物模型。
人物模型我这里用的Threejs官网中的给的模型,名称是Xbot.glb
threejs,ThreeJS,音视频,计算机外设

当然人物模型也可以自己去这个网站下载sketchfab,下载后给模型添加动画mixamo
下载模型动画

  1. 先让入你的模型

threejs,ThreeJS,音视频,计算机外设

  1. 选择正确的模型文件格式

threejs,ThreeJS,音视频,计算机外设

这里注意一下用Blander软件给模型添加动画的两种方式,具体写法的区别后面会说到

方式一:把每个单独的动画拆分出来
方式二:将所用到的动画统一放在一个时间戳中

加载场景

<!-- 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>


场景加载完后再放入人物模型:

    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);
      }
    );

这里需要将控制器给取消,并且将初始镜头删除,把镜头给到人物模型
到这里模型就全部引入完成

给场景模型中放入视频

        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;
          }
        });

注意:视频无法显示的原因,可能是添加材质的问题导致视频无法正常展示,我们这里只要设置uv就可以了
threejs,ThreeJS,音视频,计算机外设

threejs,ThreeJS,音视频,计算机外设

threejs,ThreeJS,音视频,计算机外设

关于视频出现倒过来的问题

uv模式下全选模型旋转合适的角度即可

人物行走效果

前面我们已经把镜头给到了人物模型中,接下来就可以用键盘控制人物进行前进。
这里说一下上面提到的的两种动画使用方式

1. 将所有的动画放在一个时间戳中设置动画AnimationMixer

如果用同一个时间线来加载动画,可以用到动画混合器AnimationMixer

  // 剪切人物动作
  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();

只获取前30帧为站立动画,后面的为站行走动画

2. 将每个动画单独存储成一个独立的动画元素

如果用单独的动画名称,直接获取所有的animations动画名称

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

threejs,ThreeJS,音视频,计算机外设

定义一个全局变量用来加载动画效果

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

思路:默认的动作是需要一个站立,用键盘控制时需要让模型自带的动画让模型动起来
这里就需要用到js中的键盘事件keydownkeyup

封装动画函数


 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"
      );
 
    }
  });

isWalk是用来控制长按事件在没松开之前只会触发一次,否则按住w会一直重复触发行走动画
在动画函数中加一个clock函数,其中clock.getDelta()方法获得两帧的时间间隔,此方法可以直接更新混合器相关的时间

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

通过鼠标旋转镜头

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

实现效果:
threejs,ThreeJS,音视频,计算机外设

完整代码:文章来源地址https://www.toymoban.com/news/detail-518724.html

/*
 * @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>


到了这里,关于ThreeJS案例一——在场景中添加视频,使用人物动作以及用键盘控制在场景中行走的动画的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Stable Diffusion 准确绘制人物动作及手脚细节(需ControlNet扩展)

    目前AI绘图还是比较像开盲盒,虽然能根据语义提供惊艳的图片,但是与设计师所构思的目标还是有一定距离。 那像我们这种没有绘画功底的程序员想要制作出心中的人物(尤其手脚)姿态,该怎样减少随机开盒的次数呢? 本文提供几种精确控制人物动作的方法。 安装及其

    2023年04月08日
    浏览(53)
  • 「Python|场景案例」如何获取音视频中声音片段的起止时间?

    本文主要介绍如何使用python的第三方库moviepy找出音视频中声音开始出现的位置以及声音结束的位置。 假设我们有一段音频,音频开始有一段无声片段,音频结束也有一段无声片段,我们需要知道开头无声片段的结束位置和结束无声片段的开始位置,或者换句话说, 我们需要

    2023年04月14日
    浏览(60)
  • 【2023】Unity(Unity Hub)、blender 安装 + 原神人物模型下载 + 使用Unity为模型添加动画

    目录 一、软件安装 1. Unity Hub 官网 安装 语言设置 其它设置 2. Unity 使用Unity Hub安装Unity 语言设置 3. plasticscm-cloud-windows 4. blender 官网  下载安装 语言设置 下载Cats Blender Plugin 插件 安装插件 二、原神人物模型下载、格式转换 1 .模之屋 官网 下载 2. pmx转fbx格式 三 、使用Unity为

    2024年02月08日
    浏览(92)
  • Vue成绩案例实现添加、删除、显示无数据、添加日期、总分均分以及数据本地化等功能

    ✅✅✅通过本次案例实现 添加、删除、显示无数据、添加日期、总分均分以及数据本地化等功能。 准备成绩案例模板,我们需要在这些模板上面进行功能操作。 🍻🍻🍻 添加添加思路: ①、获取科目 和 分数 ②、给添加按钮注册点击事件 ③、给list数组添加一个对象 ④、

    2024年02月16日
    浏览(61)
  • 进阶 vue自定义指令 vue中常用自定义指令以及应用场景,案例书写

    除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。 我们已经介绍了两种在 Vue 中重用代码的方式:组件和组合式函数。组件是主要的构建模块,而组合式函数则侧重于有状态的逻辑。另一方面,自定义指令主要是为了重用涉

    2024年02月15日
    浏览(41)
  • 安卓之文本转视频的应用场景以及技术优劣分析

            随着科技的进步,文本与视频这两种信息传递形式之间的界限正在逐渐模糊。特别是在安卓平台上,将文本转换为视频的功能已经成为一种重要的应用场景。本文将深入探讨这一功能的应用场景、涉及的关键技术,以及其优劣分析。 1.1、 内容创作与分享      

    2024年01月22日
    浏览(65)
  • pr视频叠加,即原视频右上角添加另外一个视频方法,以及pr导出视频步骤

    一、pr视频叠加,即原视频右上角添加另外一个视频方法 在使用pr制作视频时,我们希望在原视频的左上角或右上角同步播放另外一个视频,如下图所示: 具体方法为: 1、导入原视频,第一个放在v1位置,第二个放在v2位置,然后单独选中我们希望小视图播放的视频,点击左

    2024年02月16日
    浏览(207)
  • Threejs进阶之一:基于vite+vue3+threejs构建三维场景

    前面的章节我们都是通过HTML+JS的方式创建三维场景,从这一章节开始,我们后面将使用vite+vue3+threejs来构建三维场景。 打开vscode的终端管理器,输入如下命令 在弹出的选择框架提醒中,按上下键盘键,选择Vue,然后回车 选择JavaScript,回车 提示项目创建完成, 输入cd vue3-t

    2024年02月12日
    浏览(46)
  • ThreeJS-3D教学一:基础场景创建

    Three.js 是一个开源的 JS 3D 图形库,用于创建和展示高性能、交互式的 3D 图形场景。它建立在 WebGL 技术之上,并提供了丰富的功能和工具,使开发者可以轻松地构建令人惊叹的 3D 可视化效果。 Three.js 提供了一套完整的工具和 API,用于创建和管理 3D 场景、几何体、纹理、光照

    2024年02月07日
    浏览(56)
  • ThreeJs的场景实现鼠标拖动旋转控制

            前面一个章节中已经实现在场景中放置一个正方体,并添加灯光使得正方体可见。但是由于是静态的还不能证明是3D的,我们需要添加一些控制器,使得通过鼠标控制正方体可以动起来,实现真正的3D效果,由此引入OrbitControls组件,他实质是改变相机的位置,实现

    2024年02月07日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包