Never give up

Three js - GLTF example 본문

Three js

Three js - GLTF example

대기만성 개발자 2023. 5. 7. 22:14
반응형

GLTF는 크로노스 그룹에서 만든 표준으로

 

모델, 애니메이션, 바이너리, 텍스쳐 등을 지정해주는 파일 포맷으로 json 형태로 되어있는 파일입니다

(링크 : https://ko.wikipedia.org/wiki/GlTF)

 

크로노스 그룹에서 샘플로 만들어놓은것을 토대로 한번 예제를 만들어봤습니다

(샘플 : https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0)

 

use_gltf.ts

import { useEffect, useRef } from "react";
import * as Three from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

interface AnimationType {
    survey?: Three.AnimationAction,
    walk?: Three.AnimationAction,
    run?: Three.AnimationAction;
}

export const useGLTF = () => {
    const ref = useRef<HTMLDivElement>(null);

    const width = window.innerWidth;
    const height = window.innerHeight;

    const scene = new Three.Scene();

    const gtlfLoader = new GLTFLoader();

    let camera: Three.PerspectiveCamera;

    let light: Three.AmbientLight;

    let renderer: Three.WebGLRenderer;

    const clock = new Three.Clock();

    let mixer: Three.AnimationMixer;

    let animations: AnimationType;

    let fox: Three.Group;

    useEffect(() => {
        window.onresize = handleResize;

        window.onkeydown = handleKeydown;

        window.onkeyup = handleKeyup;
    }, []);

    useEffect(() => {
        if (ref.current !== null) {
            setRenderer();

            ref.current.appendChild(renderer.domElement);

            setCamaera();

            new OrbitControls(camera, ref.current);

            setLight();

            loadGTLF();

            animate();
        }
    }, [ref]);

    // renderer setting
    const setRenderer = () => {
        renderer = new Three.WebGLRenderer();

        renderer.setSize(width, height);
    };

    // camera setting
    const setCamaera = () => {
        camera = new Three.PerspectiveCamera(75, width / height, 0.1, 1000);

        camera.position.z = 5;
    };

    // light setting
    const setLight = () => {
        light = new Three.AmbientLight(0xffffff, 5);
        scene.add(light);
    };

    // gltf load, animation setting
    const loadGTLF = () => {
        gtlfLoader.load('/assets/gltf/Fox.gltf',
            (gltf) => {
                console.log({ gltf });

                fox = gltf.scene;

                fox.scale.set(0.01, 0.01, 0.01);

                scene.add(fox);

                mixer = new Three.AnimationMixer(fox);

                animations = {
                    survey: mixer.clipAction(gltf.animations[0]),
                    walk: mixer.clipAction(gltf.animations[1]),
                    run: mixer.clipAction(gltf.animations[2])
                };
            },
            (progress) => {
                console.log({ progress });
            },
            (error) => {
                console.log({ error });
            });
    };

    // frame update
    const animate = () => {
        mixer?.update(clock.getDelta());

        requestAnimationFrame(animate);
        renderer.render(scene, camera);
    };

    // 브라우저 사이즈 조정시 크기 재조정
    const handleResize = () => {
        if (ref !== null) {
            const width = ref.current!.clientWidth;
            const height = ref.current!.clientHeight;

            renderer.setSize(width, height);
            camera.aspect = width / height;

            camera.updateProjectionMatrix();
        };
    };

    // 이벤트 시작
    const handleKeydown = async (e: globalThis.KeyboardEvent) => {
        switch (e.key) {
            case 'ArrowUp':
                if (!animations.walk?.isRunning()) {
                    animations.walk?.play();
                }
                break;
            case 'Shift':
                if (!animations.run?.isRunning()) {
                    animations.run?.play();
                }
                break;
            case ' ':
                if (!animations.survey?.isRunning()) {
                    animations.survey?.play();
                }
                break;
            case 'ArrowLeft':
                fox.rotation.y -= 0.05;
                break;
            case 'ArrowRight':
                fox.rotation.y += 0.05;
                break;
        }
    };

    // 이벤트 중단
    const handleKeyup = (e: globalThis.KeyboardEvent) => {
        if (e.key === 'ArrowUp' || e.key === 'Shift' || e.key === ' ') {
            Object.values(animations).forEach((value: Three.AnimationAction) => {
                value.stop();
            });
        }
    };

    return { ref };
};

먼저 loadGTLF 함수를 보면 다운받은 gltf파일을 로드 합니다

(gltf json에 보면 bin, texture을 가져오는 부분도 있으니 꼭 같이 다운받으셔야 됩니다)

 

그리고 각각 콜백을 선언해주는데

 

불러오는데 시간이 많이 걸리는 gltf는 progress부분을 이용해서 로딩을 넣어주면 좋을거 같습니다

 

대략 코드는 다음과 같이 쓰고 화면에 바인딩 해주면 될거 같습니다

(progress) => {
	setPercent(progress.loaded / progress.total * 100)
}

 

가장 중요한 부분인 gltf 부분을 조금 살펴보면

 

scene에 add해주는 부분은 당연하게도 화면에 보여주는 부분이고

 

animation mixer에 등록해주는 부분은 gltf에 정의된 animation들을 불러오고

 

애니메이션이 실행되는동안 화면을 업데이트 해주는데 필요한 부분입니다

(animate함수 부분에 보면 update 하는부분이 있는데 여기입니다)

 

그 후 keydown, keyup eventlistener에 특정 키가 입력되었을 때

 

각각 애니메이션을 실행 및 리셋을 하는 부분을 넣어놨는데

 

이 부분은 나중에 조금 더 손을 봐야될거 같습니다

(예를들어 arrow up할 때 model의 좌표 이동)

 

< 키 입력에 반응하는 애니메이션 >

 

반응형

'Three js' 카테고리의 다른 글

Three js - Fiber example  (0) 2023.06.03
Three js - mouse event(feat. Raycaster)  (2) 2023.04.28
Three js - gauge example  (0) 2023.04.08
Three js - tutorial  (0) 2023.04.08
Comments