包括嘴巴说话,眨眼及手势动作都可以进行控制。
完整代码如下:
点击查看全文
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <style> body { margin: 0; } canvas { display: block; } </style> </head> <body> <script type="importmap"> { "imports": { "three": "//repo.bfw.wiki/bfwrepo/js/module/three/build/164/three.module.js", "three/addons/": "//repo.bfw.wiki/bfwrepo/js/module/three/examples/164/jsm/", "@pixiv/three-vrm": "//repo.bfw.wiki/bfwrepo/js/three-vrm.module.3.0.0.js" } } </script> <script type="module"> import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm'; // renderer const renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setPixelRatio( window.devicePixelRatio ); document.body.appendChild( renderer.domElement ); // camera const camera = new THREE.PerspectiveCamera( 30.0, window.innerWidth / window.innerHeight, 0.1, 20.0 ); camera.position.set( 0.0, 1.0, 5.0 ); // camera controls const controls = new OrbitControls( camera, renderer.domElement ); controls.screenSpacePanning = true; controls.target.set( 0.0, 1.0, 0.0 ); controls.update(); // scene const scene = new THREE.Scene(); // light const light = new THREE.DirectionalLight( 0xffffff, Math.PI ); light.position.set( 1.0, 1.0, 1.0 ).normalize(); scene.add( light ); // gltf and vrm let currentVrm = undefined; let currentMixer = undefined; const loader = new GLTFLoader(); loader.crossOrigin = 'anonymous'; loader.register( ( parser ) => { return new VRMLoaderPlugin( parser ); } ); loader.load( '//repo.bfw.wiki/bfwrepo/threemodel/girl.vrm', ( gltf ) => { const vrm = gltf.userData.vrm; // calling these functions greatly improves the performance VRMUtils.removeUnnecessaryVertices( gltf.scene ); VRMUtils.removeUnnecessaryJoints( gltf.scene ); // Disable frustum culling vrm.scene.traverse( ( obj ) => { obj.frustumCulled = false; } ); scene.add( vrm.scene ); currentVrm = vrm; prepareAnimation( vrm ); console.log( vrm ); }, ( progress ) => console.log( 'Loading model...', 100.0 * ( progress.loaded / progress.total ), '%' ), ( error ) => console.error( error ) ); // animation function prepareAnimation( vrm ) { currentMixer = new THREE.AnimationMixer(vrm.scene); // 创建嘴巴说话的动画轨道 const mouthOpenTrack = new THREE.NumberKeyframeTrack( vrm.expressionManager.getExpressionTrackName('aa'), [0.0, 0.5, 1.0], [0.0, 1.0, 0.0] ); // 调整眨眼动画轨道的时间值以降低眨眼速度 const blinkTrack = new THREE.NumberKeyframeTrack( vrm.expressionManager.getExpressionTrackName('blink'), [0.0, 6.0, 6.0], [0.0, 1.0, 0.0] ); // 创建左上臂动画(略微放下) const leftUpperArmTrack = new THREE.QuaternionKeyframeTrack( vrm.humanoid.getNormalizedBoneNode('leftUpperArm').name + '.quaternion', [0.0], [ ...new THREE.Quaternion().setFromEuler(new THREE.Euler(6, -4, -4)).toArray() ] ); // 创建右上臂动画(略微放下) const rightUpperArmTrack = new THREE.QuaternionKeyframeTrack( vrm.humanoid.getNormalizedBoneNode('rightUpperArm').name + '.quaternion', [0.0], [ ...new THREE.Quaternion().setFromEuler(new THREE.Euler(6, 10, 10)).toArray() ] ); // 创建左小臂演讲手势动画 const leftLowerArmGesture = new THREE.QuaternionKeyframeTrack( vrm.humanoid.getNormalizedBoneNode('leftLowerArm').name + '.quaternion', [0.0, 5.0, 5.0], [ ...new THREE.Quaternion().setFromEuler(new THREE.Euler(11, -21, 0)).toArray(), ...new THREE.Quaternion().setFromEuler(new THREE.Euler(11,-21, 1)).toArray(), ...new THREE.Quaternion().setFromEuler(new THREE.Euler(11, -21, 0)).toArray() ] ); // 创建右小臂演讲手势动画 const rightLowerArmGesture = new THREE.QuaternionKeyframeTrack( vrm.humanoid.getNormalizedBoneNode('rightLowerArm').name + '.quaternion', [0.0, 1.0, 2.0], [ ...new THREE.Quaternion().setFromEuler(new THREE.Euler(-0.5, 0, 0)).toArray(), ...new THREE.Quaternion().setFromEuler(new THREE.Euler(-0.8, 0, 0)).toArray(), ...new THREE.Quaternion().setFromEuler(new THREE.Euler(-0.5, 0, 0)).toArray() ] ); // 创建一个新的动画剪辑,包含所有动画轨道 const clip = new THREE.AnimationClip('Animation', 2.0, [ mouthOpenTrack, blinkTrack, leftUpperArmTrack, rightUpperArmTrack, leftLowerArmGesture, rightLowerArmGesture ]); const action = currentMixer.clipAction(clip); action.play(); } // helpers const gridHelper = new THREE.GridHelper( 10, 10 ); scene.add( gridHelper ); const axesHelper = new THREE.AxesHelper( 5 ); scene.add( axesHelper ); // animate const clock = new THREE.Clock(); function animate() { requestAnimationFrame( animate ); const deltaTime = clock.getDelta(); if ( currentVrm ) { currentVrm.update( deltaTime ); } if ( currentMixer ) { currentMixer.update( deltaTime ); } renderer.render( scene, camera ); } animate(); </script> </body> </html>
网友回复