Babylon.js
Say Hello to My First Babylon.js

사실 Babylon.js는 21년도 겨울에 페이스북의 리액트 코리아라는 그룹에서 우연히 D3 스터디 모집글을 보게 되면서 WebGL이라는 개념을 새롭게 접하게 되고, 그 후 관심이 더욱 생기게 되면서 알게된 JS라이브러리이다. 메타버스에 대한 관심이 지속되고 증가하는 추세인만큼 이를 제대로 활용할 줄 안다면 프론트엔드 개발자로서 상당히 특색있는 경쟁력을 갖출 수 있다는 장점이 있다.
겨울 단기 인턴을 구하던 중, 3D 애니메이션 스타트업인 플라스크 에 지원하게 되면서 Babylon.js 과제가 주어졌는데, 과제를 수행하면서 더욱 Babylon.js에 대한 관심이 올라간듯
Say Hello to Your First World !
과제 덕분에 Babylon.js를 사용해보게 되다니, 신선했다.
주어진 과제는 다음 기능들을 구현해야 했다.
- 주어진 glb 파일을 로드하여 Scene에 추가
- BABYLON.GUI를 활용하여 애니메이션 조작 기능을 구현
- (선택) 로드한 모델의 모든 Bone에 Box Mesh 를 붙이고, 애니메이션 재생 시 Box가 Bone을 따라 움직이도록 구현
위 과제를 보고 처음엔 혹시나 누군가 Babylon.js에 대해 써놓은 글이 있을까 싶어 간략히 구글링을 해보았지만, 역시나 한글로 친절히 써진 babylon.js에 대한 정보는 찾기 어려웠다.
결국 답은 Babylon.js 공식문서였는데, 다행히도 공식문서가 꽤 친절해서 주어진 과제는 무리없이 해결할 수 있었던 것 같다.
사실 babylon.js의 모든 부분이 내겐 새롭게 접해보는 내용들이었기 때문에 과제를 하는 동안엔 코드를 작성하면서도 완벽하게 이해하면서 코드를 작성하지는 못했기 때문에, refactor의 필요성을 느껴 포스팅을 하며 다시금 코드를 정리해보려 한다.
💡 Babylon.js 에 대해서 지식이 아직 너무도 부족합니다. 아래의 정리한 내용은 정확한 정보가 아닐 수 있습니다. 부족하거나 틀린 내용이 있다면 알려주시면 감사하겠습니다. 🙇♂️
Getting Started
babylon.js의 기본 중의 기본은 Scene과 Camera, 그리고 Light를 사용하는 것이다. 즉 장면과 카메라 그리고 광원을 준비해논 상테에서 3D 요소를 추가해야 한다.
const createScene = function() { const scene = new BABYLON.Scene(engine); scene.clearColor = new BABYLON.Color3.Black; const alpha = Math.PI/4; const beta = Math.PI/3; const radius = 8; const target = new BABYLON.Vector3(0, 0, 0); const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene); camera.attachControl(canvas, true); const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0)); return scene; };
위의 코드처럼 scene, camera, light 를 준비하게 되면 3D 요소를 추가할 수 있다.
glb 파일을 로드하여 Scene에 추가하기
기본적으로 바빌론의 BABYLON.SceneLoader 내장 메소드를 사용하여 glb 파일을 로드하는 것 같았다. 또한 SceneLoader 내에도 다양하게 scene을 추가할 수 있는 메소드들이 있는 듯 하다. 나는 3번 기능 구현을 위해서 SceneLoader.ImportMesh를 사용했다.
BABYLON.SceneLoader.ImportMesh(null, "<https://res.cloudinary.com/ucamtu/image/upload/v1640938377/babylon>", "hiphopdancing_aoy7oc.glb", scene, function (newMeshes, particleSystems, skeletons, animationGroups) { /* ... do something with the meshes and skeletons */ }
여기서 첫 번째 매개변수는 null로 설정하여 모든 mesh와 skeleton을 로드할 수 있다. 나는 3번째 기능 구현을 위해서는 mesh와 skeleton을 사용해야 할 것 같아서 null로 설정함.
두번째 매개변수와 세번째 매개변수는 Cloudinary를 사용하여 받아온 url 값과 glb 파일명을 사용했다.
위의 ImportMesh 함수 내에서 glb로부터 로드된 모델을 이용하여 애니메이션의 동작을 구현할 수 있다. 또한 매개변수로 받는 scene을 활용하여 defaultCameraOrLight을 사용할 수도 있다.
scene.createDefaultCameraOrLight(true, true, true); scene.activeCamera.alpha += Math.PI;
Camera 인스턴스는 alpha, beta, radius 등 갖춰주어야 할 매개변수들이 있다. 내가 구현하고자 하는 애니메이션은 로드된 모델의 전면부를 보고 있어야 하기 때문에 카메라의 alpha값을 설정해주었다.
장면에 GUI 추가하기
Babylon.GUI는 DynamicTexture를 사용하여 사용자 인터페이스를 생성한다. 따라서 Babylon.GUI를 사용하여 유저와 상호 작용하기 위해서는 우선적으로 DynamicTexture가 필요하다.
Babylon.GUI는 두 가지의 모드가 있다.
- Fullscreen mode
- Texture mode
Fullscreen mode
풀스크린 모드에서 Babylon.GUI는 전체 화면을 덮고 항상 렌더링 해상도에 맞게 조정된다. 또한 Scean 에서 GUI를 클릭하거나 터치를 하게 된다면 우선적으로 GUI가 클릭되개끔 작동함. 그리고 한 Scene당 하나의 fullscreen mode GUI만 허용한다.
AdvancedDynamicTexture를 Fullscreen mode에서 사용하려면, 간단히 다음 코드를 추가하고 실행시키면 된다.
const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI( "ui" );
기본적으로 렌더링 해상도와 텍스처 크기 사이의 비율은 1이지만 advancedTexture.renderScale 를 사용하여 다른 값으로 강제 설정할 수 있고 이러한 설정을 하는 것은 (예를 들면) 더 선명한 텍스트를 원할 때 유용할 수 있다.
이번 과제 수행을 하면서 나는 Fullscreen mode를 사용하여 GUI를 사용했다. Texture mode는 Babylon.js Document에도 친절한 설명이 나와있으니 여기서는 다루지 않겠다.
StackPannel
StackPanel은 자식 요소를 가로 또는 세로 방향으로 지정할 수 있는 한 줄로 배열하는 레이아웃 패널이다. 기본적으로 StackPanel은 선언된 순서대로 위에서 아래로 항목을 수직으로 쌓는다.
Alignments
horizontalAlignment, verticalAlignment 를 사용하여 GUI 요소의 위치를 지정할 수 있다.
만약 우측 중앙에 버튼을 위치시키고 싶다면 다음과 같은 코드를 작성하여 구현할 수 있다.
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui"); const panel = new BABYLON.GUI.StackPanel(); panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; const playButton = BABYLON.GUI.Button.CreateSimpleButton("play", "Play animation"); playButton.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
아직 버튼 만들기에 대해서 다루기 전이지만, 코드가 직관적이라 이해하기는 어렵지 않다.
버튼 만들기
기본적으로 사용할 수 있는 세 가지 종류의 버튼이 있다.
1. ImageButton
이미지 버튼은 이미지와 텍스트로 구성된 버튼아다. 간단히 다음 코드를 추가하여 이미지 버튼을 만들 수 있다.
const button = BABYLON.GUI.Button.CreateImageButton( "but", "Click Me", "textures/grass.png" );
2. ImageWithCenterTextButton
백그라운드를 이미지를 넣어주고 버튼의 텍스트 내용은 중앙에 위치하고 있는 버튼이다. 다음과 같은 코드가 사용된다.
const button = BABYLON.GUI.Button.CreateImageWithCenterTextButton( "but", "Click Me", "textures/grass.png" );
3. SimpleButton
텍스트만 포함하고 있는 간단한 버튼을 만들려면 다음과 같이 코드를 작성하자.
const button = BABYLON.GUI.Button.CreateSimpleButton("but", "Click Me");
추가적으로 ImageOnlyButton도 있긴 하다.
나는 간단한 버튼을 구현하면 됐기 때문에 심플한 버튼을 만들었다.
Babylon.js는 버튼 외에도 다양한 GUI 요소를 만들 수 있는 방법들을 제공한다. 이러한 방법들을 통해 체크박스나 슬라이더 등을 만들 수 있다.
다음으로는 버튼에 이벤트를 어떻게 연동할 수 있는지에 대해 알아보자.
Events
나는 클릭 시 이벤트가 발생하여야 하기 때문에, onPointerClickObservable 이벤트를 사용했다. 물론 이 외에도 다양한 이벤트가 존재한다. 또한 구현하고자 하는 이벤트를 선택했다면 만들어져있는 요소에 attachControl를 통해 붙혀주면 된다. 다음 코드를 통해 좀 더 자세히 이해해보자.
const animationGroup = animationGroups[0]; const playButton = BABYLON.GUI.Button.CreateSimpleButton( "play", "Play animation" ); playButton.onPointerClickObservable.add(function () { animationGroup.play(true); }); playButton.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; playButton.width = 0.2; playButton.height = "45px"; playButton.color = "white"; playButton.background = "blue"; playButton.padding = "15px"; panel.addControl(playButton);
Styling
GUI에 대한 스타일링은 우리가 CSS를 일반적으로 사용하는 것처럼 사용할 수 있다. 이에 대한 자세한 정보는 Babylon.js의 Document에 정말 친절히 설명되어 있으니 참고하자. 또한 Babylon.js v3.3부터는 컨트롤 간에 스타일 구성을 공유하는 데 사용할 스타일 객체를 만들어서 스타일링이 가능하다고 한다. 다음 코드를 참고하자.
const style = advancedTexture.createStyle(); style.fontSize = 24; style.fontStyle = "italic"; style.fontFamily = "Verdana";
이렇게 스타일 객체에 원하는 스타일을 추가해 준 뒤,
textControl.style = style;
원하는 GUI요소에 추가해줄 수 있다.
GUI & 버튼 만들기 최종 코드
// UI const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui"); const panel = new BABYLON.GUI.StackPanel(); panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; ui.addControl(panel); const playButton = BABYLON.GUI.Button.CreateSimpleButton( "play", "Play animation" ); playButton.onPointerClickObservable.add(function () { animationGroup.play(true); }); playButton.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; playButton.width = 0.2; playButton.height = "45px"; playButton.color = "white"; playButton.background = "blue"; playButton.padding = "15px"; panel.addControl(playButton); const pauseButton = BABYLON.GUI.Button.CreateSimpleButton( "stop", "Pause animation" ); pauseButton.onPointerClickObservable.add(function () { animationGroup.pause(); }); pauseButton.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; pauseButton.width = 0.2; pauseButton.height = "45px"; pauseButton.color = "white"; pauseButton.background = "red"; pauseButton.padding = "10px"; panel.addControl(pauseButton);
Bones to Box Mesh 🥸
이 부분은 좀 더 상세하게 다뤄보고 싶어서 새로운 포스팅으로 정리할 예정.
# BabylonJs# WebGL