我之前发表了一篇展示3d模型的,那个插件比较简单,但是不能加载模型动画,有需要的可以去看看:
接下来要写的就是本文的重点,公司需要,不得不琢磨
ready player me 官网:
Ready Player Me - Playershttps://readyplayer.me/players
安装:npm i threejs (安装threejs)
安装:npm i element-resize-detector(安装一个vue监听元素宽高的插件,为了适配)
注意:element-resize-detector这个插件要在main.js里面全局引入一下(直接复制粘贴到你的main.js文件中就行,😏)
//安装完之后在vue2的main.js文件导入插件
import ElementResizeDetectorMaker from "element-resize-detector";
//全局挂载插件
Vue.prototype.$erd = ElementResizeDetectorMaker();
直接上代码,注释写的比较详细,不明白的可以看注释:
一、template部分:
<template>
<div class="content" style="background: white;">
<!-- container就是要展示模型的容器 -->
<div id="container" class="container"></div>
</div>
</template>
二、script部分(注释详细,请耐心阅读)
<script>
// 导入 Three.js以及相关需要用的部分
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default {
data() {
return {
// 初始化场景,相机,灯光,混合器等巴拉巴拉巴拉
scene: null,
light: null,
camera: null,
renderer: null,
model: null,
group: null,
mixer:null,
}
},
// 页面渲染完毕之后,启用方法
mounted() {
this.init() //初始化
this.loadGltf() //加载模型
},
methods: {
init() {
let vm = this //这里设置vm是this指向问题,一定要设置*******
let container = document.getElementById("container");//获取显示3D模型的DOM结构
const elementResizeDetectorMaker = require("element-resize-detector"); //导入vue监听指定元素变化的插件
let erd = elementResizeDetectorMaker(); //实例化插件
erd.listenTo(container, element => { //指定要监听的元素
//这里面是监听变化后操作的
const width = element.clientWidth;
const height = element.clientHeight;
console.log(width,height)
// 变化之后重新设置渲染器大小
vm.renderer.setSize(width, height);
// 变化之后重新设置相机纵横比
vm.camera.aspect = width / height;
vm.camera.updateProjectionMatrix();
});
// 初始化场景
this.scene = new THREE.Scene();
// 加载环境光,不加则什么都看不见
this.scene.add(new THREE.AmbientLight(0xffffff, 0.4));
this.light = new THREE.DirectionalLight(0xffffff, 1.0);
this.light.position.set(1, 1, 1).normalize();
this.scene.add(this.light); //场景加载灯光
// 加载网格
const grid = new THREE.GridHelper( 50, 10, 0x444444, 0x444444 );
grid.material.opacity = 0.3; //设置网格的透明度
grid.material.transparent = true; //设置网格是否可以透明
grid.rotation.y = Math.PI/2.0; //设置网格方向为模型y轴方向
grid.position.y = -1 //设置网格位置为模型中心位置向下一格
grid.rotateY(-60) //设置网格绕Y轴逆时针旋转
this.scene.add( grid ); //场景加载网格
/**
* 相机设置
*/
// 初始化透视相机
this.camera = new THREE.PerspectiveCamera(75,container.clientWidth / container.clientHeight, 0.1, 1000);
// 设置相机位置
this.camera.position.z = 2;
this.camera.position.y = 0.5;
this.camera.up = new THREE.Vector3(0, 1, 0);
// /**
// * 创建渲染器对象
// */
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }), // 开启抗锯齿和背景透明
this.renderer.setSize(container.clientWidth,container.clientHeight);
this.renderer.setPixelRatio(devicePixelRatio); //干啥的不清楚,反正开启之后画质直接起飞
// 相机的控件:加上它就能实现鼠标控制模型旋转、缩放
const controls = new OrbitControls(this.camera, this.renderer.domElement);
// 最后就是把所有加载到指定的dom结构上
container.appendChild(this.renderer.domElement);
},
loadGltf() {
let vm = this; //也是防止this指向问题
const loader = new GLTFLoader(); //模型加载器,有了它才能加载.glb等后缀文件并解析
// 开始加载模型文件(可替换自己的模型文件)
loader.load("https://models.readyplayer.me/64f1b1fefe61576b46f28fbb.glb?cacheControl=true&uat=1693561341962", function (gltf) {
// 加载完之后
vm.model = gltf.scene;
vm.model.position.set(0,-1,0) //设置模型初始的位置
vm.scene.add(vm.model) //在场景中加载这个模型
const fbxLoader = new FBXLoader() //这个是读取.fbx文件的,有了它才能读取.fbx的动画文件
// 加载动画文件(我这个动画文件是放在vue项目public中的,可替换成自己的.fbx文件)
fbxLoader.load('F_Standing_Idle_001.fbx', (animation)=>{
//加载动画混合器,将模型和读取.fbx文件得到的动画参数混合到一起
vm.mixer = new THREE.AnimationMixer(vm.model)
// 默认读取第一个动画
const animClip = animation.animations[0];
// 调用Test方法
let res = vm.Test(animClip)
const action = vm.mixer.clipAction(res);
// 开启动画
action.play()
})
// 执行动画
function animate() {
// 渲染器添加场景和相机
vm.renderer.render(vm.scene, vm.camera);
requestAnimationFrame(animate);
if (vm.mixer) {
vm.mixer.update(0.016);
}
}
animate();
}
);
},
// 这段是我从readyplayerme官方提供的react封装的方法上扣出来的
Test(value){
const { tracks } = value;
for (let i = 0; i < tracks.length; i+=1) {
const hasMixamoPrefix = tracks[i].name.includes('mixamorig');
if (hasMixamoPrefix) {
tracks[i].name = tracks[i].name.replace('mixamorig', '');
}
if (tracks[i].name.includes('.position')) {
for (let j = 0; j < tracks[i].values.length; j += 1) {
// Scale the bound size down to match the size of the model
// eslint-disable-next-line operator-assignment
tracks[i].values[j] = tracks[i].values[j] * 0.01;
}
}
return value;
}
}
}
}
</script>
三、style部分:
<style scoped>
*{
margin: 0;
padding: 0;
}
.content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 60%;
height: 700px;
background: white;
border: 1px solid black;
}
.container :hover {
cursor: pointer;
}
四、展示(元素宽度适配的哦,因为用了vue的插件,上边有介绍):
以上就是文本有价值的信息,我也不会threejs,就是四处查看文档,会了点皮毛,希望对你有帮助,看完有帮助记得点个赞,不为别的,只为了记录自己工作遇到的疑难杂症!