Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

바스키아

FBXLoader (Three.js) 사용하기 본문

JS/Three.js

FBXLoader (Three.js) 사용하기

바스키아1 2019. 8. 27. 10:09

구글링 검색시 FBXLoader 한국어검색 블로그가 없넹...ㅎ  내가 최초인가 ㅎ

 

Three.js사용도 처음이지만 FBX파일을 불러오는것은...이번생엔 처음이라...ㅎㅎ

 

그전에 .glb 파일을 불러와서 사용을 하던 예제가 있었다 GLTFLoader를 이용하여 파일을 불러오는데....

 

이 예제를 살펴보고 가것다.

 

우선 Three.js Document를 살펴보자(습관화 해야한다...)

 

 

glTF (GL Transmission Format) is an open format specification for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) or binary (.glb) format. External files store textures (.jpg, .png) and additional binary data (.bin). A glTF asset may deliver one or more scenes, including meshes, materials, textures, skins, skeletons, morph targets, animations, lights, and/or cameras.

-> 

JSON (.gltf) 또는 이진 (.glb) 형식으로 제공 될 수 있습니다. 외부 파일은 텍스처 (.jpg, .png) 및 추가 이진 데이터 (.bin)를 저장합니다. glTF 애셋은 메시, 머티리얼, 텍스처, 스킨, 스켈레톤, 모프 타겟, 애니메이션, 라이트 및 / 또는 카메라를 포함한 하나 이상의 장면을 전달할 수 있습니다.

 

결국 .jlb파일을 사용하려면 이 로더 사용하여 가져와라! 라는 말이고 저 gLTF는 변환시켜주는 포멧이다 (GL은 Graphics Library 의 약자로 풀면 그래픽 라이브러를 변형시켜주는 포멧 이라는말이 아닐까 ㅎㅎ)

 

예시문이 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Instantiate a loader
var loader = new THREE.GLTFLoader();
 
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
loader.setDRACOLoader( new THREE.DRACOLoader() );
 
// Optional: Pre-fetch Draco WASM/JS module, to save time while parsing.
THREE.DRACOLoader.getDecoderModule();
 
// Load a glTF resource
    // resource URL
    'models/gltf/duck/duck.gltf',
    // called when the resource is loaded
    function ( gltf ) {
 
 
        gltf.animations; // Array<THREE.AnimationClip>
        gltf.scene; // THREE.Scene
        gltf.scenes; // Array<THREE.Scene>
        gltf.cameras; // Array<THREE.Camera>
        gltf.asset; // Object
 
    },
    // called while loading is progressing
    function ( xhr ) {
 
        console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
 
    },
    // called when loading has errors
    function ( error ) {
 
        console.log( 'An error happened' );
 
    }
);

로더 객체를 불러와 loader.load()를 이용해 scene에 넣어주어 가시화를 시키는걸 볼수 있다.

 

리액트에서 해볼까??

-> 

import {GLTF, GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";

*참고로 three.js에서 npm install방법 알아서 설치하세용~ 그래야 저것들도 import 할수있을테니 ㅎ

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let glbURL:string = require("../model/WorkerAtRoof.glb");
//glb 파일 경로에서 가져오고
 
let obj:GLTFLoader = new GLTFLoader();
//객체 만들어준다.
 
let modelLoadFunction:(gltf: GLTF) => void = ( object  : GLTF ) =>{
                        let obj : Object3D =  object.scene.children[0];
                        obj.position.x = positionX;
                        obj.position.y = positionZ;
                        obj.position.z = positionY;
                    };
//(사이클만 이해 하시길...ㅎ)
 
//load에 필요한 3요소가 준비되었으니
                        console.log("error");
   

이러면 끄읕.

파일 경로와 인스턴스객체(loader) 에 야무지게 함수를 설정하고 .load()에 example대로 해주면 된다는.....

 

three.js에서 example보면 아주 까리한 예제 및 결과도 많다...ㅎㅎ

 

존멋.... 게임개발자인가보다...

 

그럼 이제....glb 에서 fbx파일로 넘어가야하는데....

fbx loader는 document에서는 못찾고 example에 있더라.....

(나같은 컴맹이를 위해서)

이렇게 검색해봐 요놈누르면 괴상망칙한넘이 90년대 찌르기 댄스를 시전하고있어.....

극 ....to the 혐........ (가만히좀 있어...제발)

오른쪽 하단에 < > 코드 모양을 누르면 이 example이 어떻게 fbx를 호출했는지 알수 있어.

 

예제를 보면....

import { FBXLoader } from './jsm/loaders/FBXLoader.js';

이놈을 불러오네? 나도 똑같이 ... 

import {FBXLoader} from "three/examples/jsm/loaders/FBXLoader";

임포트 해주고 다음 

 

   let fbxURL:string = require("../model/bakedanimo.fbx"); //fbx가 있는 파일위치
   let fbxLoader = new FBXLoader(); // 인스턴스 생성해주고
   //fbx를 로더해보자
   fbxLoader.load(fbxURL,function(object:any){
            scene.add(object);
        });
   

간단한 부분 in Gee Dragon? (인지용?)

 

그런데 이 fbx파일이 동적인 파일이라면 action 을 하게끔 로직을 추가해주어야하는데.....

아니 그럼 대체 fbx가 뭔데?

--> 오토데스크 회사에서 만든 3d 머시기란다... 그냥 3d 파일이라고만 알아도 충분하다.

 

fbx자체가 view로보면 마구 움직이는데 이걸 로드하는것 까진 성공했다구...하지만 움직이질 않아....

 

공식도큐먼트에는 나와있는게 없어 마구 돌아다님...그래서 얼추 찾은곳이 CodeSandBox

우리의 선생3대장 (구글 , StackOverFlow, 그리고 CodeSandBox)

 

저모자!! 옷 물수!!! 저건 우리나라 캐릭터임이 틀림없다!!!!!!!!!!

반가워 한국 코더!!

 

우선 답답해하시는 분들을 위해 코드 대충 훑어보시오

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<script>
        <!-- if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); -->
 
            var container, stats, controls;
            var camera, scene, renderer, light;
            var clock = new THREE.Clock();
            var mixers = [];
            init();
            animate();
            function init() {
 
                container = document.createElement( 'div' );
                document.body.appendChild( container );
 
                camera = new THREE.PerspectiveCamera( 45window.innerWidth / window.innerHeight, 12000 );
                camera.position.set( 100200300 );
 
                controls = new THREE.OrbitControls( camera );
                controls.target.set( 01000 );
                controls.update();
 
                scene = new THREE.Scene();
                scene.background = new THREE.Color( 0xa0a0a0 );
                scene.fog = new THREE.Fog( 0xa0a0a02001000 );
 
                light = new THREE.HemisphereLight( 0xffffff0x444444 );
                light.position.set( 02000 );
                scene.add( light );
 
                light = new THREE.DirectionalLight( 0xffffff );
                light.position.set( 0200100 );
                light.castShadow = true;
                light.shadow.camera.top = 180;
                light.shadow.camera.bottom = -100;
                light.shadow.camera.left = -120;
                light.shadow.camera.right = 120;
                scene.add( light );
 
 
                // ground
                var mesh = new THREE.Mesh( new THREE.PlaneGeometry( 20002000 ), 
new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
                mesh.rotation.x = - Math.PI / 2;
                mesh.receiveShadow = true;
                scene.add( mesh );
 
                var grid = new THREE.GridHelper( 2000200x0000000x000000 );
                grid.material.opacity = 0.2;
                grid.material.transparent = true;
                scene.add( grid );
 
                // model
                var loader = new THREE.FBXLoader();
                loader.load( 'assets/models/fbx/turtle_with_ani.fbx'function ( object ) {
                    object.mixer = new THREE.AnimationMixer( object );
                    mixers.push( object.mixer );
 
                    var action = object.mixer.clipAction( object.animations[ 0 ] );
                    action.play();
 
                    object.traverse( function ( child ) {
 
                        if ( child.isMesh ) {
 
                            child.castShadow = true;
                            child.receiveShadow = true;
 
                        }
                        /* max to webgl 회전 보정 */
                        if ( child.isGroup ) {
                            child.rotation.x = Math.PI;
                            child.rotation.y = Math.PI;
                            child.rotation.z = Math.PI;
                        }
 
                    } );
 
                    scene.add( object );
 
                } );
 
                renderer = new THREE.WebGLRenderer();
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                renderer.shadowMap.enabled = true;
                container.appendChild( renderer.domElement );
 
                window.addEventListener'resize', onWindowResize, false );
 
                // stats
                stats = new Stats();
                container.appendChild( stats.dom );
 
            }
 
            function onWindowResize() {
 
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
 
                renderer.setSize( window.innerWidth, window.innerHeight );
 
            }
 
            //
 
            function animate() {
 
                requestAnimationFrame( animate );
 
                if ( mixers.length > 0 ) {
 
                    for ( var i = 0; i < mixers.length; i ++ ) {
 
                        mixers[ i ].update( clock.getDelta() );
 
                    }
 
                }
 
                renderer.render( scene, camera );
                stats.update();
 
            }
    </script>
 

개길다.

 

var clock = new THREE.Clock();

Clock

Object for keeping track of time. This uses performance.now if it is available, otherwise it reverts to the less accurate Date.now.

Constructor

Clock( autoStart : Boolean )

autoStart — (optional) whether to automatically start the clock. Default is true.

 

프로퍼티를 제외하고보면 객체의 시간을 추적하는 용도라고한다. 생성자의 타입은 boolean 

 

init(), animate() 를 즉시 실행하지만 난 react 니까 componenetDidMount()안에서 해결하겠어 ㅎ

 

나머지 ground 제조하다가 이제 다시 아까 하던 FBXLoader를 이용해 모델 불러오는데 이부분이 중요하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var loader = new THREE.FBXLoader();
                    object.mixer = new THREE.AnimationMixer( object );
                    mixers.push( object.mixer );
 
                    var action = object.mixer.clipAction( object.animations[ 0 ] );
                    action.play();
 
                    object.traverse( function ( child ) {
 
                        if ( child.isMesh ) {
 
                            child.castShadow = true;
                            child.receiveShadow = true;
 
                        }
                        /* max to webgl 회전 보정 */
                        if ( child.isGroup ) {
                            child.rotation.x = Math.PI;
                            child.rotation.y = Math.PI;
                            child.rotation.z = Math.PI;
                        }
 
                    } );
 
                    scene.add( object );
 
                } );
 
 

AnimationMixer이건뭐지..

object.mixer = new THREE.AnimationMixer( object );

 

AnimationMixer

The AnimationMixer is a player for animations on a particular object in the scene. When multiple objects in the scene are animated independently, one AnimationMixer may be used for each object.
--> 애니메이션 플레이어라고 한다. 

장면의 여러 오브젝트가 독립적으로 애니메이션 될 때 각 오브젝트에 하나의 AnimationMixer를 사용할 수 있으니 앞으로 쓸일이 많겠군 ㅎ

이 믹서에 오브젝트를 담겠구만 AnimationMixer(Object) 처럼 ㅎ


For an overview of the different elements of the three.js animation system see the "Animation System" article in the "Next Steps" section of the manual.

Constructor

AnimationMixer( rootObject : Object3D )

rootObject - the object whose animations shall be played by this mixer.

하고 도큐먼트 밑을 보면 다양한 메소드들이 존재한다.

 

그리고 object를 담은 mixer에 clipAction 메서드를 사용한다..

도큐먼트를 봐보자

-->

.clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction

Returns an AnimationAction for the passed clip, optionally using a root object different from the mixer's default root. The first parameter can be either an AnimationClip object or the name of an AnimationClip.
-->

선택적으로 믹서의 기본 루트와 다른 루트 객체를 사용하여 전달 된 클립에 대한 AnimationAction을 반환합니다

여기서 말하는 clip이 어떤 의미인지 잘 모르겠다...사실...


If an action fitting the clip and root parameters doesn't yet exist, it will be created by this method. Calling this method several times with the same clip and root parameters always returns the same clip instance.

->클립 및 루트 매개 변수에 맞는 조치가 아직 없으면이 메소드에 의해 작성됩니다. 동일한 클립 및 루트 매개 변수를 사용하여이 메서드를 여러 번 호출하면 항상 동일한 클립 인스턴스가 반환됩니다.

 

대충 의미는 이해가 가지만 확실히 잘 잡지 못하겠다.... 댓글로 누가 알려주면 좋겠으나... 내블로그는 인기가 없기에...쓰바씌바

 

object.traverse() 이건 또 무슨뜻인고 구글링해보니...

 

It is basically the iterator through your loaded object. You can pass the function to the traverse() function which will be called for every child of the object being traversed. If you call traverse() on scene. you traverse through the complete scene graph.

 

->객체를 통한 반복자 iterator 라고하네요 즉 자식노드까지 object에 대한 내용을 돌릴수있으니 action을 보여줘야하는 입장에서 이해가 된다.  하지만 도큐먼트 설명이 없으니 아쉽긴하지만 체크

 

그리고 scene.add(object)를 하면 화면에 나타나게 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function animate() {
 
                requestAnimationFrame( animate );
 
                if ( mixers.length > 0 ) {
 
                    for ( var i = 0; i < mixers.length; i ++ ) {
 
                        mixers[ i ].update( clock.getDelta() );
 
                    }
 
                }
 
                renderer.render( scene, camera );
                stats.update();
 
            }

requestAnimationFrame 이건뭔가하니 (MDN에서 발췌)

 

window.requestAnimationFrame()은 브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트하는 함수를 호출하게 합니다. 이 메소드는 리페인트 이전에 실행할 콜백을 인자로 받습니다.

 

https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame 를 참조하세용(예문도 있어요 ㅎ)

그래서 애니메이션할 오브젝트를 담은mixer를 있는길이만큼 반복자 돌려서 업데이트 시키는데 초반에 clock를 부른 이유가 여기있다!! 그럼 clock에 getDelta()를 봐보자

 

.getDelta () : Float

Get the seconds passed since the time oldTime was set and sets oldTime to the current time.
If autoStart is true and the clock is not running, also starts the clock.

 

->oldTime이 설정된 이후 경과 된 초를 가져오고 oldTime을 현재 시간으로 설정한다. 자동 시작이 true이고 시계가 실행 중이 아니면 시계도 시작한다.

 

**requestAnimationFrame는 0~1 사이로 프레임을 와리가리 해주는데 Float인 당신이 0.00012 이렇게 컨트롤 할순 없지 않는가? ㅎㅎ 반복자, 흘러가는 시간 가져오기등 다 필요한 요소이더라

 

renderer.render(scene, camera); 애니메이션 이 변하는 프레임마다 렌더해주고

 

하면 끝!

만약 안되는거 있음 댓글달아줘!! 아무도 없겠지만 ㅜ