准备
首先我们需要两个模型,一个是场景模型,另一个是人物模型。
人物模型我这里用的Threejs官网中的给的模型,名称是Xbot.glb
。
当然人物模型也可以自己去这个网站下载sketchfab,下载后给模型添加动画mixamo
下载模型动画
- 先让入你的模型
- 选择正确的模型文件格式
这里注意一下用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就可以了
关于视频出现倒过来的问题
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;
});
实现效果:
文章来源:https://www.toymoban.com/news/detail-518724.html
完整代码:文章来源地址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模板网!