未验证 提交 a7caa144 编写于 作者: M Mr.doob 提交者: GitHub

Merge pull request #16006 from strazdinsg/dev

Add an example of multiple animated GLTF 3D objects in a single scene.
......@@ -4,6 +4,7 @@ var files = {
"webgl_animation_keyframes",
"webgl_animation_skinning_blending",
"webgl_animation_skinning_morph",
"webgl_animation_multiple",
"webgl_camera",
"webgl_camera_array",
"webgl_camera_cinematic",
......
<!DOCTYPE html>
<html lang="en">
<head>
<title>Multiple animated objects</title>
<meta charset="utf-8">
<meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
<style>
body {
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info"
style="position: absolute; left: 0; top: 0; width: 100%; background-color: white; border: 1px solid black; margin: 10px; padding: 10px;">
This demo shows how to load several instances of the same 3D model (same .GLTF file) into the
scene, position them at different locations and launch different animations for them.
To do it, some tricky cloning of SkinnedMesh, Skeleton and Bone objects is necessary (done by SkeletonUtils.clone().
Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
</div>
<script src="../build/three.js"></script>
<script src="js/WebGL.js"></script>
<script src="js/loaders/GLTFLoader.js"></script>
<script src="js/utils/SkeletonUtils.js"></script>
<script>
if (WEBGL.isWebGLAvailable() === false) {
document.body.appendChild(WEBGL.getWebGLErrorMessage());
}
//////////////////////////////
// Global objects
//////////////////////////////
let worldScene = null; // THREE.Scene where it all will be rendered
let renderer = null;
let camera = null;
let mixers = []; // All the AnimationMixer objects for all the animations in the scene
//////////////////////////////
//////////////////////////////
// Information about our 3D models and units
//////////////////////////////
// The names of the 3D models to load. One-per file.
// A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
// meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
// Models are from https://www.mixamo.com/
const MODELS = [
{name: "Soldier"},
{name: "Parrot"},
// {name: "RiflePunch"},
];
// Here we define instances of the models that we want to place in the scene, their position, scale and the animations
// that must be played.
const UNITS = [
{
modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
meshName: "vanguard_Mesh", // Name of the main mesh to animate
position: {x: 0, y: 0, z: 0}, // Where to put the unit in the scene
scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
animationName: "Idle" // Name of animation to run
},
{
modelName: "Soldier",
meshName: "vanguard_Mesh",
position: {x: 3, y: 0, z: 0},
scale: 2,
animationName: "Walk"
},
{
modelName: "Soldier",
meshName: "vanguard_Mesh",
position: {x: 1, y: 0, z: 0},
scale: 1,
animationName: "Run"
},
{
modelName: "Parrot",
meshName: "mesh_0",
position: {x: -4, y: 0, z: 0},
rotation: {x: 0, y: Math.PI, z: 0},
scale: 0.01,
animationName: "parrot_A_"
},
{
modelName: "Parrot",
meshName: "mesh_0",
position: {x: -2, y: 0, z: 0},
rotation: {x: 0, y: Math.PI / 2, z: 0},
scale: 0.02,
animationName: null
},
];
//////////////////////////////
// The main setup happens here
//////////////////////////////
let numLoadedModels = 0;
initScene();
initRenderer();
loadModels();
animate();
//////////////////////////////
//////////////////////////////
// Function implementations
//////////////////////////////
/**
* Function that starts loading process for the next model in the queue. The loading process is
* asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
* wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
*/
function loadModels() {
for (let i = 0; i < MODELS.length; ++i) {
const m = MODELS[i];
loadGltfModel(m, function (model) {
console.log("Done loading model", MODELS[i].name);
++numLoadedModels;
if (numLoadedModels === MODELS.length) {
console.log("All models loaded, time to instantiate units...");
instantiateUnits();
}
});
}
}
/**
* Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
* launch necessary animations
*/
function instantiateUnits() {
let numSuccess = 0;
for (let i = 0; i < UNITS.length; ++i) {
const u = UNITS[i];
const model = getModelByName(u.modelName);
if (model) {
const clonedScene = THREE.SkeletonUtils.clone(model.scene);
if (clonedScene) {
// Scene is cloned properly, let's find one mesh and launch animation for it
const clonedMesh = clonedScene.getObjectByName(u.meshName);
if (clonedMesh) {
const mixer = startAnimation(clonedMesh, model.animations, u.animationName);
if (mixer) {
// Save the animation mixer in the list, will need it in the animation loop
mixers.push(mixer);
numSuccess++;
}
}
// Different models can have different configurations of armatures and meshes. Therefore,
// We can't set position, scale or rotation to individual mesh objects. Instead we set
// it to the whole cloned scene and then add the whole scene to the game world
// Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
worldScene.add(clonedScene);
if (u.position) {
clonedScene.position.set(u.position.x, u.position.y, u.position.z);
}
if (u.scale) {
clonedScene.scale.set(u.scale, u.scale, u.scale);
}
if (u.rotation) {
clonedScene.rotation.x = u.rotation.x;
clonedScene.rotation.y = u.rotation.y;
clonedScene.rotation.z = u.rotation.z;
}
}
} else {
console.error("Can not find model", u.modelName);
}
}
console.log(`Successfully instantiated ${numSuccess} units`);
}
/**
* Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
* @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
* @param animations {Array} Array containing all the animations for this model
* @param animationName {string} Name of the animation to launch
* @return {THREE.AnimationMixer} Mixer to be used in the render loop
*/
function startAnimation(skinnedMesh, animations, animationName) {
let mixer = new THREE.AnimationMixer(skinnedMesh);
const clip = THREE.AnimationClip.findByName(animations, animationName);
if (clip) {
const action = mixer.clipAction(clip);
action.play();
}
return mixer;
}
/**
* Find a model object by name
* @param name
* @returns {object|null}
*/
function getModelByName(name) {
for (let i = 0; i < MODELS.length; ++i) {
if (MODELS[i].name === name) {
return MODELS[i];
}
}
return null;
}
/**
* Load a 3D model from a GLTF file. Use the GLTFLoader.
* @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
* @param onLoaded {function} A callback function that will be called when the model is loaded
*/
function loadGltfModel(model, onLoaded) {
const loader = new THREE.GLTFLoader();
const modelName = "models/gltf/" + model.name + ".glb";
loader.load(modelName, function (gltf) {
const scene = gltf.scene;
model.animations = gltf.animations;
model.scene = scene;
// Enable Shadows
gltf.scene.traverse(function (object) {
if (object.isMesh) {
object.castShadow = true;
}
});
onLoaded(model);
});
}
/**
* Render loop. Renders the next frame of all animations
*/
function animate() {
requestAnimationFrame(animate);
// Get the time elapsed since the last frame
const mixerUpdateDelta = clock.getDelta();
// Update all the animation frames
for (let i = 0; i < mixers.length; ++i) {
mixers[i].update(mixerUpdateDelta);
}
renderer.render(worldScene, camera);
}
//////////////////////////////
// General Three.JS stuff
//////////////////////////////
// This part is not anyhow related to the cloning of models, it's just setting up the scene.
/**
* Initialize ThreeJS scene renderer
*/
function initRenderer() {
const container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.gammaOutput = true;
renderer.gammaFactor = 2.2;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
}
/**
* Initialize ThreeJS Scene
*/
function initScene() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(3, 6, -10);
camera.lookAt(0, 1, 0);
clock = new THREE.Clock();
worldScene = new THREE.Scene();
worldScene.background = new THREE.Color(0xa0a0a0);
worldScene.fog = new THREE.Fog(0xa0a0a0, 10, 22);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 20, 0);
worldScene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(-3, 10, -10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 10;
dirLight.shadow.camera.bottom = -10;
dirLight.shadow.camera.left = -10;
dirLight.shadow.camera.right = 10;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 40;
worldScene.add(dirLight);
// ground
const groundMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(40, 40), new THREE.MeshPhongMaterial({
color: 0x999999,
depthWrite: false
}));
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.receiveShadow = true;
worldScene.add(groundMesh);
window.addEventListener('resize', onWindowResize, false);
}
/**
* A callback that will be called whenever the browser window is resized.
*/
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册