包括嘴巴说话,眨眼及手势动作都可以进行控制。
完整代码如下:
点击查看全文
<!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>
网友回复
DLNA与UPnP的区别和不同?
苏超自建抢票app,通过先预约再抽签化解高并发抢票?
python如何让给电脑在局域网中伪装成电视接收手机的投屏图片视频播放?
如何结合python+js如何自己的视频编码与加密播放直播?
python如何在电脑上通过局域网将本地视频或m3u8视频投屏电视播放?
腾讯视频爱奇艺优酷vip电影电视剧视频如何通过python绕过vip收费直接观看?
有没有可免费观看全球电视台直播m3u8地址url的合集?
有没有实现观影自由的免vip影视苹果 CMS V10 API的可用url?
python如何实时检测电脑usb插入检测报警?
如何判断真人操作的鼠标移动直线轨迹与机器操作的轨迹?