three.js 引用外部模型,并使用其绑定的动画
作者:互联网
<template>
<div id="container">
<!-- <img src="/models/yunlog.png" alt /> -->
<button @click="dispose('robot')">
模型切换
</button>
</div>
</template>
<script>
import * as Three from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { DoubleSide, GridHelper, Mesh, MeshBasicMaterial, PlaneBufferGeometry, AnimationMixer, Clock, Raycaster, Vector2 } from 'three';
export default { data() { return { // 场景的宽高获取容器宽高 sceneHeight: null, sceneWidth: null,
// 场景, 灯光, 摄像机, 控制器, 渲染器 scene: "", light: "", camera: "", controls: "", renderer: "", // 动画 animationMixer: null, clock: null, animationClipList: [], animationActionList: {},
// 可改变的变量 // 点击触发的动作 clickAction: 'Wave' }; },
mounted() { this.init(); // this.drawPlane(); this.drweSkybox() this.loadGltf();
// 监测点击事件 this.clickRender() },
methods: { //初始化three.js相关内容 init() { const container = document.getElementById("container"); // console.log("三维盒子", container, container.offsetHeight, container.offsetWidth)
// 创建场景对象 this.scene = new Three.Scene(); this.sceneHeight = container.offsetHeight this.sceneWidth = container.offsetWidth
// 创建灯光 this.scene.add(new Three.AmbientLight(0x999889)); //环境光 this.light = new Three.DirectionalLight(0xdfebff, 0.5); //从正上方(不是位置)照射过来的平行光,0.45的强度 this.light.position.set(1, 1, 0); this.light.position.multiplyScalar(0.5); this.scene.add(this.light);
//初始化相机 (视野大小, 长宽比, 近景, 远景) this.camera = new Three.PerspectiveCamera(70, this.sceneWidth / this.sceneHeight, 0.01, 100) this.camera.position.set(0, 8, 8);
this.renderer = new Three.WebGLRenderer({ alpha: true, antialias: true }); //初始化控制器 this.controls = new OrbitControls(this.camera, this.renderer.domElement); // this.controls.target.set(0, 0, 0);//------------------ // this.controls.minDistance = 3; // this.controls.maxDistance = 100; // this.controls.maxPolarAngle = Math.PI / 3; this.controls.update();
//渲染 this.renderer.setPixelRatio(window.devicePixelRatio); //为了兼容高清屏幕 this.renderer.setSize(this.sceneWidth, this.sceneHeight); this.renderer.outputEncoding = Three.sRGBEncoding;
container.appendChild(this.renderer.domElement); window.addEventListener("resize", this.onWindowResize, false); //添加窗口监听事件(resize-onresize即窗口或框架被重新调整大小) }, //窗口监听函数 onWindowResize() { // 模型比例调整 this.camera.aspect = this.sceneWidth / this.sceneHeight; this.camera.updateProjectionMatrix();
this.renderer.setSize(this.sceneWidth, this.sceneHeight); },
// 绘制平面 drawPlane() { const planeBufferGeometry = new PlaneBufferGeometry( 30, 30 ); const plane = new Mesh( planeBufferGeometry, new MeshBasicMaterial({ color: '#000', side: DoubleSide }) ) plane.name = 'plane' // 平面绕x轴旋转90度(弧度制) plane.rotation.x = -Math.PI / 2 this.scene.add( plane )
// 添加网格 this.scene.add(new GridHelper(30, 30)) },
// 绘制天空盒 drweSkybox() { let skyBox = new Three.BoxGeometry(100, 100, 100) // 创建立方体
let textureLoader = new Three.TextureLoader let right = require('../assets/skybox/right.jpg') let left = require('../assets/skybox/left.jpg') let top = require('../assets/skybox/top.jpg') let bottom = require('../assets/skybox/bottom.jpg') let back = require('../assets/skybox/back.jpg') let front = require('../assets/skybox/front.jpg')
let skyBoxMaterialList = [ new Three.MeshBasicMaterial({ map: textureLoader.load(right), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(left), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(top), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(bottom), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(front), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(back), side: DoubleSide }) ] let sky = new Three.Mesh(skyBox, skyBoxMaterialList) // 创建网格 this.scene.add(sky) },
//外部模型加载函数 loadGltf() { // 加载模型 var loader = new GLTFLoader() loader.load("static/RobotExpressive.glb", (data) => { // console.log(data) let mesh = data.scene; // mesh.position.set(0, 0, 0); mesh.name = 'robot' this.scene.add(mesh); // 将模型引入three
this.animationClipList = data.animations this.setAnimation()
this.render(); }); },
// 加载动画 setAnimation() { this.animationMixer = new AnimationMixer(this.scene) this.clock = new Clock()
console.log("动画列表", this.animationClipList) },
render() { requestAnimationFrame(() => { this.render() }); // 调用动画 this.animationMixer.update(this.clock.getDelta()) this.renderer.render(this.scene, this.camera); },
// 监测点击事件 clickRender() { this.renderer.domElement.addEventListener('click', event => { // 获取屏幕坐标 let { offsetX, offsetY } = event // 计算画布上的二维坐标 let x = ( offsetX / this.sceneWidth ) * 2 - 1 let y = - ( offsetY / this.sceneHeight ) * 2 + 1 let mousePoint = new Vector2( x, y )
// 获取点击元素 let raycaster = new Raycaster() // 按照this.camera摄像头下,mousePoint位置设置raycaster raycaster.setFromCamera( mousePoint, this.camera ) // 获取点击的元素 let intersect = raycaster.intersectObjects(this.scene.children, true) // 筛选去除点击的网格元素及平面元素 intersect = intersect.filter( intersect => !(intersect.object instanceof GridHelper) && intersect.object.name != "plane" )
console.log("点击元素", intersect) let hasClick = [] // 所有被点击的指定元素及其子元素组成数组,通过数组中有无数据判断是否点击 intersect.forEach((v) => { let click = this.isClick(v.object, 'robot') // console.log("比较结果", click) if( this.isClick(v.object, 'robot') ){ hasClick.push(v) } }) // console.log("比较结果", hasClick)
let action = this.getAnimationAction(this.clickAction) // 判断元素被点击,并且该点击动作还未触发,触发动作,若已触发动作停止动作 // if( hasClick.length > 0 && action.isRunning () ){ // action.stop() // } else if( hasClick.length > 0 ) { // action.play() // } action.play() action.weight = 0 console.log("权重", action.getEffectiveWeight () ) if( hasClick.length > 0 && action.getEffectiveWeight () ){ action.fadeOut(1) } else if( hasClick.length > 0 ) { action.fadeIn(1) } }) },
// 判断点击的元素是否是指定元素或其子元素 isClick(object, name) { if( object.name == name ){ return true }else if( object.parent ){ return this.isClick(object.parent, name) }else { return false } },
// 动画调用方法,当动作已使用混合器生成AnimationAction直接返回储存的AnimationAction,若还未生成,生成一个AnimationAction返回,并储存下次使用 getAnimationAction(name) { let animationActionList = this.animationActionList // 检索animationActionList中是否已储存AnimationAction for( let actionName in animationActionList ) { if( actionName == name ){ return animationActionList[name] } }
// 若还未储存AnimationAction let animationClip = this.animationClipList.find(animationClip => animationClip.name == name ) animationActionList[name] = this.animationMixer.clipAction(animationClip) return animationActionList[name] },
// 删除对象 dispose(name) { // console.log('删除测试', this.scene) let childrenList = this.scene.children let group = childrenList.find(v => v.name == name) group.visible = !group.visible } } }; </script>
<style scoped> #container { width: 600px; margin: 0 auto; height: 400px; overflow: hidden; } </style>
export default { data() { return { // 场景的宽高获取容器宽高 sceneHeight: null, sceneWidth: null,
// 场景, 灯光, 摄像机, 控制器, 渲染器 scene: "", light: "", camera: "", controls: "", renderer: "", // 动画 animationMixer: null, clock: null, animationClipList: [], animationActionList: {},
// 可改变的变量 // 点击触发的动作 clickAction: 'Wave' }; },
mounted() { this.init(); // this.drawPlane(); this.drweSkybox() this.loadGltf();
// 监测点击事件 this.clickRender() },
methods: { //初始化three.js相关内容 init() { const container = document.getElementById("container"); // console.log("三维盒子", container, container.offsetHeight, container.offsetWidth)
// 创建场景对象 this.scene = new Three.Scene(); this.sceneHeight = container.offsetHeight this.sceneWidth = container.offsetWidth
// 创建灯光 this.scene.add(new Three.AmbientLight(0x999889)); //环境光 this.light = new Three.DirectionalLight(0xdfebff, 0.5); //从正上方(不是位置)照射过来的平行光,0.45的强度 this.light.position.set(1, 1, 0); this.light.position.multiplyScalar(0.5); this.scene.add(this.light);
//初始化相机 (视野大小, 长宽比, 近景, 远景) this.camera = new Three.PerspectiveCamera(70, this.sceneWidth / this.sceneHeight, 0.01, 100) this.camera.position.set(0, 8, 8);
this.renderer = new Three.WebGLRenderer({ alpha: true, antialias: true }); //初始化控制器 this.controls = new OrbitControls(this.camera, this.renderer.domElement); // this.controls.target.set(0, 0, 0);//------------------ // this.controls.minDistance = 3; // this.controls.maxDistance = 100; // this.controls.maxPolarAngle = Math.PI / 3; this.controls.update();
//渲染 this.renderer.setPixelRatio(window.devicePixelRatio); //为了兼容高清屏幕 this.renderer.setSize(this.sceneWidth, this.sceneHeight); this.renderer.outputEncoding = Three.sRGBEncoding;
container.appendChild(this.renderer.domElement); window.addEventListener("resize", this.onWindowResize, false); //添加窗口监听事件(resize-onresize即窗口或框架被重新调整大小) }, //窗口监听函数 onWindowResize() { // 模型比例调整 this.camera.aspect = this.sceneWidth / this.sceneHeight; this.camera.updateProjectionMatrix();
this.renderer.setSize(this.sceneWidth, this.sceneHeight); },
// 绘制平面 drawPlane() { const planeBufferGeometry = new PlaneBufferGeometry( 30, 30 ); const plane = new Mesh( planeBufferGeometry, new MeshBasicMaterial({ color: '#000', side: DoubleSide }) ) plane.name = 'plane' // 平面绕x轴旋转90度(弧度制) plane.rotation.x = -Math.PI / 2 this.scene.add( plane )
// 添加网格 this.scene.add(new GridHelper(30, 30)) },
// 绘制天空盒 drweSkybox() { let skyBox = new Three.BoxGeometry(100, 100, 100) // 创建立方体
let textureLoader = new Three.TextureLoader let right = require('../assets/skybox/right.jpg') let left = require('../assets/skybox/left.jpg') let top = require('../assets/skybox/top.jpg') let bottom = require('../assets/skybox/bottom.jpg') let back = require('../assets/skybox/back.jpg') let front = require('../assets/skybox/front.jpg')
let skyBoxMaterialList = [ new Three.MeshBasicMaterial({ map: textureLoader.load(right), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(left), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(top), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(bottom), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(front), side: DoubleSide }), new Three.MeshBasicMaterial({ map: textureLoader.load(back), side: DoubleSide }) ] let sky = new Three.Mesh(skyBox, skyBoxMaterialList) // 创建网格 this.scene.add(sky) },
//外部模型加载函数 loadGltf() { // 加载模型 var loader = new GLTFLoader() loader.load("static/RobotExpressive.glb", (data) => { // console.log(data) let mesh = data.scene; // mesh.position.set(0, 0, 0); mesh.name = 'robot' this.scene.add(mesh); // 将模型引入three
this.animationClipList = data.animations this.setAnimation()
this.render(); }); },
// 加载动画 setAnimation() { this.animationMixer = new AnimationMixer(this.scene) this.clock = new Clock()
console.log("动画列表", this.animationClipList) },
render() { requestAnimationFrame(() => { this.render() }); // 调用动画 this.animationMixer.update(this.clock.getDelta()) this.renderer.render(this.scene, this.camera); },
// 监测点击事件 clickRender() { this.renderer.domElement.addEventListener('click', event => { // 获取屏幕坐标 let { offsetX, offsetY } = event // 计算画布上的二维坐标 let x = ( offsetX / this.sceneWidth ) * 2 - 1 let y = - ( offsetY / this.sceneHeight ) * 2 + 1 let mousePoint = new Vector2( x, y )
// 获取点击元素 let raycaster = new Raycaster() // 按照this.camera摄像头下,mousePoint位置设置raycaster raycaster.setFromCamera( mousePoint, this.camera ) // 获取点击的元素 let intersect = raycaster.intersectObjects(this.scene.children, true) // 筛选去除点击的网格元素及平面元素 intersect = intersect.filter( intersect => !(intersect.object instanceof GridHelper) && intersect.object.name != "plane" )
console.log("点击元素", intersect) let hasClick = [] // 所有被点击的指定元素及其子元素组成数组,通过数组中有无数据判断是否点击 intersect.forEach((v) => { let click = this.isClick(v.object, 'robot') // console.log("比较结果", click) if( this.isClick(v.object, 'robot') ){ hasClick.push(v) } }) // console.log("比较结果", hasClick)
let action = this.getAnimationAction(this.clickAction) // 判断元素被点击,并且该点击动作还未触发,触发动作,若已触发动作停止动作 // if( hasClick.length > 0 && action.isRunning () ){ // action.stop() // } else if( hasClick.length > 0 ) { // action.play() // } action.play() action.weight = 0 console.log("权重", action.getEffectiveWeight () ) if( hasClick.length > 0 && action.getEffectiveWeight () ){ action.fadeOut(1) } else if( hasClick.length > 0 ) { action.fadeIn(1) } }) },
// 判断点击的元素是否是指定元素或其子元素 isClick(object, name) { if( object.name == name ){ return true }else if( object.parent ){ return this.isClick(object.parent, name) }else { return false } },
// 动画调用方法,当动作已使用混合器生成AnimationAction直接返回储存的AnimationAction,若还未生成,生成一个AnimationAction返回,并储存下次使用 getAnimationAction(name) { let animationActionList = this.animationActionList // 检索animationActionList中是否已储存AnimationAction for( let actionName in animationActionList ) { if( actionName == name ){ return animationActionList[name] } }
// 若还未储存AnimationAction let animationClip = this.animationClipList.find(animationClip => animationClip.name == name ) animationActionList[name] = this.animationMixer.clipAction(animationClip) return animationActionList[name] },
// 删除对象 dispose(name) { // console.log('删除测试', this.scene) let childrenList = this.scene.children let group = childrenList.find(v => v.name == name) group.visible = !group.visible } } }; </script>
<style scoped> #container { width: 600px; margin: 0 auto; height: 400px; overflow: hidden; } </style>
标签:Three,name,绑定,three,js,let,renderer,new,scene 来源: https://www.cnblogs.com/yan122/p/15691682.html