Перейти ко вступительной информации

Видео+код: #3/6 ThreeJS Анимация персонажа (запуск двух разных анимаций). Анимирование Мандалорца в Blender 3d

Статья создана: Видео+код: #3/6 ThreeJS Анимация персонажа (запуск двух разных анимаций). Анимирование Мандалорца в Blender 3d

Видео: #3/6 ThreeJS Анимация персонажа (запуск двух разных анимаций). Анимирование Мандалорца в Blender 3d

Файлы из урока 3/6 по Mando

3-6-jft-2-ani.7z

Внимание! Файлы взяты из последнего урока (все файлы присутствуют в архиве. Код — этого урока)
GitHUB

JS Код из видео (!на threejs-webpack-starter!)

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
//import * as dat from 'dat.gui'
// HDR map
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';

const models=Object.create({
    model:'/models/THREE_52_without_pre_keys.glb',
    hdr:'/hdr/kiara_1_dawn_4k.hdr', // HDR map
});

// Scene
const scene = new THREE.Scene()

const hdrEquirect = new RGBELoader().load(
    models.hdr,
    () => {
        hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;

        const sphere=new THREE.Mesh(
            new THREE.IcosahedronGeometry(8,4),
            new THREE.MeshBasicMaterial({
                map:hdrEquirect,
                side:THREE.DoubleSide,
            })
        );
        scene.add(sphere)
    }
);

const loader = new GLTFLoader(),
      dracoLoader = new DRACOLoader();
let mixer;//for animation
dracoLoader.setDecoderPath('/js/libs/draco/'); // v155 use a full url path
loader.setDRACOLoader(dracoLoader);

loader.load(
    models.model,// Manda full
    glb=>{
        //console.log(glb.scene);
        const sceneGlb=glb.scene;
        //console.log(sceneGlb.material);
        //sceneGlb.material.color=0x00ffff;//new THREE.MeshBasicMaterial({color:0x00ffff})
        //scene.add(glb);
        //for(const model_ of sceneGlb.children){
            /* if(model_.name==='Armature'){//Sketchfab_model_0|Sketchfab_model_0001
                const y=model_.children[0];
                //console.log(y);
                //y.material=new THREE.Material({color:0x000000});
                scene.add(y);
                console.log(model_);
            } */
        //    if(sceneGlb.children[0]){
        //        console.log(sceneGlb.children[0]);
        //    }
        //}

        sceneGlb.position.set(0,-1,0)
        scene.add(sceneGlb)
        //sceneGlb.scale.set(.01, .01, .01)

        mixer = new THREE.AnimationMixer(sceneGlb);
        mixer.timeScale=8;// DEF: 8
        mixer.clipAction((glb).animations[0]).play();
        // установим параметры, чтобы анимация останавливалась после полного проигрывания на последнем кадре
        mixer._actions[0].clampWhenFinished=true;
        mixer._actions[0].loop = THREE.LoopOnce;
        // проверим воспроизводится ли на данный момент 0 (первая) анимация (ходьба)
        // если уже НЕТ, то запускаем вторую анимацию
        let tt=setInterval(()=>{
            if(!mixer._actions[0].isRunning()){
                // console.log('!! anim finished___!!');
                clearInterval(tt);
                tt=null;
                // Воспроизводим вторую анимацию (цикличное дыхание)
                // Она почему-то с прерыванием. то дышит, то — нет. ниже попытался исправить, но не вышло быстро... попробую хотя бы сделать, чтобы после запуска второй он находился в том же месте...
                mixer.clipAction((glb).animations[1]).play();
                //mixer.setTime(50);
                // mixer._actions[1].clampWhenFinished=true;
                // mixer._actions[1].loop = THREE.LoopOnce;
                //sceneGlb.position.set(0,-1,.6)
            }
            // console.log(mixer._actions[0].isRunning());
        },500);
        /* setInterval(()=>{
            if(mixer._actions[1]){
                console.log('anim 2 is running');
                if(!mixer._actions[1].isRunning()){
                    mixer.setTime(50);
                    mixer._actions[1].play=true; ///  TRY!
                    mixer._actions[1].clampWhenFinished=true;
                    mixer._actions[1].loop = THREE.LoopOnce;
                    console.log('anim 2 REPLAYING');

                }else{
                    console.log('anim 2 playing');
                }
            }
            // if(!mixer._actions[1].isRunning()){
            //     console.log('!! anim 2 finished___!!');
            //     //mixer.setTime(50);
            //     // mixer._actions[1].clampWhenFinished=false;
            //     // mixer._actions[1].loop = THREE.LoopRepeat;
            //     //sceneGlb.position.set(1,0,1)
            // }
            // // JFT
            // else{
            //     console.log('anim 2 NOT FINISHED');
            // }
            // console.log(mixer._actions[0].isRunning());
        },500);
        // FIX~!~ animation start
        setTimeout(()=>mixer.setTime(0),0); */
//JFT
// console.log(mixer);

/* setTimeout(()=>{
    //mixer.timeScale = -1;
    mixer.play = false;
    console.log(mixer);
},1400); */
// \ JFT
        //console.log((glb).animations);

        let metall=sceneGlb.children[0]; // and his childrens
        console.log(metall.children)
        if(metall.children.length>0){
            for(const m_ of metall.children){//sceneGlb.children[0].children[0].children[0].children[0].children[0].children[0].children[4].material.envMapIntensity=.8
                //console.log(m_);
                if(m_.name==='02_armor')m_.material.envMap = hdrEquirect}
            }
        }
)

// Debug
//const gui = new dat.GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')


// JFT
/* const geometry = new THREE.BoxGeometry(3, 3, 3 );
const material = new THREE.MeshBasicMaterial()
material.color = new THREE.Color(0xffffff)
const cube = new THREE.Mesh(geometry,material)
scene.add(cube) */

// Lights

const pointLight = new THREE.PointLight(0xffffff, 35)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 5
scene.add(pointLight)

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 0
camera.position.y = 0
camera.position.z = 3
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas/* ,alpha: true */
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
//renderer.setClearColor(0x000000, 0);
/**
 * Animate
 */

const clock = new THREE.Clock()

const tick = () =>
{

    const elapsedTime = clock.getElapsedTime()

    // Update objects
    //sphere.rotation.y = .5 * elapsedTime

    // Update Orbital Controls
    // controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
    // animate sceneGLB
    if (mixer) mixer.update(clock.getDelta());
}

tick()

Расшифровка временных меток видео:

00:00-00:24 Каков будет результат урока?
00:24 Особенности 3D модели Мандалорца (Blender)
06:05 Оптимизация модели Мандалорца
07:23 (1) Приступим к коду ThreeJS
08:10 Напоминаю как импортировать GLB модель из Blender для ThreeJS
11:05 ThreeJS AnimationMixer — включаем анимации 3D модели на сцене WEBGL
15:52 ThreeJS управление скоростью анимации 3D модели
19:43 (задача) Как заставить анимацию остановиться (запретить цикличность)
23:34 Хак перемотки анимации в начало при старте сцены
23:59 (решение — способ) выводим в консоль сам 'mixer'
28:04 Решение (код), как остановить цикличность анимации
29:21 Итоги по коду сцены
33:48 Особенности подключения карты окружения (envMap) к модели
43:17 (2) Blender: особенности анимации персонажа, создание скелета
46:03 Особенности «веса» при создании скелета
50:36 Решил сначала показать код, а затем Blender'ить
51:27 Автоматическая генерация скелета персонажа
55:22 Добавление дополнительных костей для персонажа
59:20 Привязка скелета к персонажу в Blender
1:01:07 Особенности распределения автоматически добавленного «веса»
1:07:16 Особенности анимации конкретной кости в Blender
1:16:28 Особенности перехода от одной анимации к другой в ThreeJS (Blender)
1:20:23 Окружение персонажа на сцене ThreeJS (поля, горы) — карта для сферы
1:29:30 Смотрите другие видео. До встречи!
copyleft 2023. we use: GatsbyJS, React, Google Analytics. Source code