ThreeJS 官方案例学习(webgl_animation_skinning_additive_blending)
1.效果图
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e67eb03974784e1399be680a7c830153.jpeg#pic_center)
2.源码
<template>
<div>
<div id="container"></div>
</div>
</template>
<script>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import gsap from 'gsap';
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
let idleAction, walkAction, runAction;
const crossFadeControls = [];
let panelSettings, numAnimations;
let currentBaseAction = 'idle';
let baseActions = {
idle: { weight: 1 },
walk: { weight: 0 },
run: { weight: 0 }
}
let additiveActions = {
sneak_pose: { weight: 0 },
sad_pose: { weight: 0 },
agree: { weight: 0 },
headShake: { weight: 0 }
}
const allActions = [];
export default {
data() {
return {
container: null,
scene: null,
camera: null,
renderer: null,
controller: null,
stats: null,
mixer: null,
model: null,
skeleton: null,
settings: {},
actions: null,
panel: null,
clock: new THREE.Clock()
};
},
mounted() {
this.init()
window.addEventListener("resize", this.onWindowSize)
},
beforeUnmount() {
console.log('beforeUnmount===============');
this.container = null
this.scene = null
this.camera = null
this.renderer = null
this.controller = null
this.stats = null
this.mixer = null
this.model = null
},
methods: {
init() {
this.container = document.getElementById('container')
this.setScene()
this.setCamera()
this.setRenderer()
this.setController()
this.addHelper()
this.setLight()
this.setGltfLoader()
this.addStatus()
this.addGround()
},
setScene() {
this.scene = new THREE.Scene()
this.scene.background = new THREE.Color(0xa0a0a0);
this.scene.fog = new THREE.Fog(0xa0a0a0, 10, 50)
},
setCamera() {
this.camera = new THREE.PerspectiveCamera(40, this.container.clientWidth / this.container.clientHeight, 1, 100)
this.camera.position.set(-1, 2, 3);
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.camera.lookAt(0, 1, 0)
this.scene.add(this.camera)
},
setRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true,
})
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.shadowMap.enabled = true
this.container.appendChild(this.renderer.domElement);
},
setController() {
this.controller = new OrbitControls(this.camera, this.renderer.domElement);
this.controller.enableDamping = true;
this.controller.dampingFactor = 0.04;
this.controller.target.set(0, 1, 0);
},
addGround() {
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(100, 100),
new THREE.MeshPhongMaterial({
color: 0xcbcbcb,
depthWrite: false
})
)
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
this.scene.add(mesh)
},
setGltfLoader() {
let that = this
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
loader.load('/model/gltf/Xbot.glb', (gltf) => {
gltf.scene.traverse(function (object) {
if (object.isMesh) {
object.castShadow = true
}
})
that.model = gltf.scene
that.model.position.set(0, 0, 0)
that.scene.add(that.model)
that.skeleton = new THREE.SkeletonHelper(that.model)
that.skeleton.visible = false
that.scene.add(that.skeleton)
const animations = gltf.animations
that.mixer = new THREE.AnimationMixer(that.model)
numAnimations = animations.length
for (let i = 0; i < numAnimations; i++) {
let clip = animations[i]
const name = clip.name
if (baseActions[name]) {
const action = that.mixer.clipAction(clip)
that.activateAction(action);
baseActions[name].action = action
allActions.push(action)
} else if (additiveActions[name]) {
THREE.AnimationUtils.makeClipAdditive(clip)
if (clip.name.endsWith('_pose')) {
clip = new THREE.AnimationUtils.subclip(clip, clip.name, 2, 3, 30)
}
const action = that.mixer.clipAction(clip)
that.activateAction(action)
additiveActions[name].action = action
allActions.push(action)
}
}
that.createPanel()
that.animate();
}, undefined, (err => {
console.error(err)
}))
},
createPanel() {
const panel = new GUI({ width: 310 })
const folder0 = panel.addFolder('Visibility')
const folder1 = panel.addFolder('Base Actions');
const folder2 = panel.addFolder('Additive Action Weights');
const folder3 = panel.addFolder('General Speed');
panelSettings = {
'modify time scale': 1.0
};
this.settings = {
'show model': true,
'show skeleton': false,
}
folder0.add(this.settings, 'show model').onChange(this.showModel)
folder0.add(this.settings, 'show skeleton').onChange(this.showSkeletons)
const baseName = ['None', ...Object.keys(baseActions)]
for (let i = 0; i < baseName.length; i++) {
const name = baseName[i];
const settings = baseActions[name]
panelSettings[name] = () => {
const currentSettings = baseActions[currentBaseAction]
const currentAction = currentSettings ? currentSettings.action : null
const action = settings ? settings.action : null
if (currentAction !== action) {
this.prepareCrossFade(currentAction, action, 0.35);
}
}
crossFadeControls.push(folder1.add(panelSettings, name));
}
for (const name of Object.keys(additiveActions)) {
const settings = additiveActions[name]
panelSettings[name] = settings.weight
folder2.add(panelSettings, name, 0.0, 1.0, 0.01).listen().onChange((weight) => {
this.setWeight(settings.action, weight)
settings.weight = weight
})
}
folder3.add(panelSettings, 'modify time scale', 0.0, 1.0, 0.01).listen().onChange((speed) => {
this.mixer.timeSacle = speed
})
folder0.open();
folder1.open();
folder2.open();
folder3.open();
crossFadeControls.forEach(function (control) {
control.setInactive = function () {
control.domElement.classList.add('control-inactive');
};
control.setActive = function () {
control.domElement.classList.remove('control-inactive');
};
const settings = baseActions[control.property];
if (!settings || !settings.weight) {
control.setInactive();
}
});
},
showModel(visibility) {
this.model.visible = visibility
},
showSkeletons(visibility) {
this.skeleton.visible = visibility
},
activateAction(action) {
const clip = action.getClip();
const settings = baseActions[clip.name] || additiveActions[clip.name];
this.setWeight(action, settings.weight);
action.play();
},
setWeight(action, weight) {
action.enabled = true
action.setEffectiveTimeScale(1);
action.setEffectiveWeight(weight);
},
prepareCrossFade(startAction, endAction, duration) {
if (currentBaseAction === 'idle' || !startAction || !endAction) {
this.executeCrossFade(startAction, endAction, duration)
} else {
this.synchronizeCrossFade(startAction, endAction, duration)
}
if (endAction) {
const clip = endAction.getClip()
currentBaseAction = clip.name
} else {
currentBaseAction = 'None'
}
crossFadeControls.forEach(function (control) {
const name = control.property;
if (name === currentBaseAction) {
control.setActive();
} else {
control.setInactive();
}
});
},
executeCrossFade(startAction, endAction, duration) {
console.log('startAction :>> ', startAction);
if (endAction) {
this.setWeight(endAction, 1)
endAction.time = 0
if (startAction) {
startAction.crossFadeTo(endAction, duration, true)
} else {
endAction.fadeIn(duration);
}
} else {
startAction.fadeOut(duration);
}
},
synchronizeCrossFade(startAction, endAction, duration) {
this.mixer.addEventListener('loop', onLoopFinished)
let that = this
function onLoopFinished(event) {
if (event.action === startAction) {
that.mixer.removeEventListener('loop', onLoopFinished);
that.executeCrossFade(startAction, endAction, duration);
}
}
},
addHelper() {
let helper = new THREE.CameraHelper(this.camera);
let axisHelper = new THREE.AxesHelper(150);
this.scene.add(axisHelper)
let gridHelper = new THREE.GridHelper(100, 30, 0x2C2C2C, 0x888888);
},
setPMREMGenerator() {
const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
this.scene.environment = pmremGenerator.fromScene(new RoomEnvironment(this.renderer), 0.04).texture;
},
setLight() {
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
hemiLight.position.set(0, 20, 0);
this.scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(- 3, 10, - 10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 2;
dirLight.shadow.camera.bottom = - 2;
dirLight.shadow.camera.left = - 2;
dirLight.shadow.camera.right = 2;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 40;
this.scene.add(dirLight);
},
addStatus() {
this.stats = new Stats();
this.container.appendChild(this.stats.dom);
},
onWindowSize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio)
},
animate() {
requestAnimationFrame(this.animate);
for (let i = 0; i !== numAnimations; ++i) {
const action = allActions[i];
const clip = action.getClip();
const settings = baseActions[clip.name] || additiveActions[clip.name];
settings.weight = action.getEffectiveWeight();
}
const delta = this.clock.getDelta();
if (this.mixer) {
this.mixer.update(delta);
}
this.controller.update(delta);
this.stats.update();
this.renderer.render(this.scene, this.camera);
},
},
};
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
.control-inactive button {
color: #888;
}
</style>