ThreeJS案例一 您所在的位置:网站首页 三维人物动画视频 ThreeJS案例一

ThreeJS案例一

2024-05-30 20:49| 来源: 网络整理| 查看: 265

准备

首先我们需要两个模型,一个是场景模型,另一个是人物模型。 人物模型我这里用的Threejs官网中的给的模型,名称是Xbot.glb。 请添加图片描述

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

先让入你的模型

请添加图片描述

选择正确的模型文件格式

请添加图片描述

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

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

加载场景 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`, importa.url).href, (gltf) => { scene.add(gltf.scene); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); if (mixer) { mixer.update(clock.getDelta()); } } }); .container { width: 100%; height: 100vh; position: relative; z-index: 1; }

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

new GLTFLoader().load( new URL(`../assets/Xbot.glb`, importa.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`, importa.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`, importa.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就可以了 请添加图片描述

请添加图片描述

请添加图片描述

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

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)

请添加图片描述

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

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

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

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

实现效果: 请添加图片描述

完整代码:

/* * @Author: Southern Wind * @Date: 2023-06-24 * @Last Modified by: Mr.Jia * @Last Modified time: 2023-06-24 16:30:24 */ 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`, importa.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`, importa.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`, importa.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`, importa.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()); } } }); .container { width: 100%; height: 100vh; position: relative; z-index: 1; }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有