包括嘴巴说话,眨眼及手势动作都可以进行控制。

完整代码如下:
点击查看全文
<!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>
网友回复


