리액트에서 바빌론을 실행하는 예제는 ms에 없어서 babylon.js 공식문서에서 찾아보았다.
바빌론 공식문서에 있는 예제를 한번 따라 만들어 보자.
https://doc.babylonjs.com/communityExtensions/Babylon.js+ExternalLibraries/BabylonJS_and_ReactJS
Vite를 이용하여 리액트 시작하기
맨 처음 프로젝트를 만들 때 이전까지는 webpack에서 구성하거나 CRA를 사용하였는데,
이번에는 vite로 한 번 구현해 보았다. 웹팩에서 직접 만드는 것과는 다르게 vite는 매우 간단하게 구성할 수 있었다.
프로젝트를 넣을 폴더를 만든 뒤 해당 폴더에서
npm create vite@latest my-vite
를 입력하면 몇가지 옵션을 선택할 수 있게 한다.
여기서 나는 리액트에서 바빌론을 사용하고, Typescript를 이용할 것이기 때문에 아래 사진과 같이 설정했다.
여기서 SWC는 Rust로 작성된 JavaScript 컴파일러로 바벨보다 더 좋은 성능으로 코드를 트랜스파일 할 수 있다고 한다.
이후 필요한 모듈들을 다운로드 한다.
npm install
만들어진 프로젝트의 소스폴더에서
App.tsx, main.tsx를 제외한 css파일은 삭제하였고, 내용도 비워서 빈 페이지로 시작할 것이다.
바빌론 엔진이 적용될 캔버스 생성
바빌론은 3D 렌더링 엔진으로 canvas html요소에 엔진을 적용시킬 수 있다.
src아래에 components 폴더를 생성하고 SceneComponent.tsx 컴포넌트를 생성하자
리턴되는 태그를 canvas로 변경하고, 해당 canvas html엘리먼트에 바빌론 엔진을 적용하기 위해 useRef로 해당 요소를 가져온다.
ref를 가져올 때 HTMLCanvasElement를 제네릭으로 적어주지 않으면 MutableRefObject가 되기 때문에 반드시 적어준다.
import React, {useRef} from "react";
function SceneComponent() {
const reactCanvas = useRef<HTMLCanvasElement>(null);
return <canvas ref={reactCanvas} />;
}
export default SceneComponent;
이후 useEffect를 이용하여 바빌론엔진을 해당 컴포넌트가 렌더링 될 때 적용시켜 보자.
이전에 작성한 자습서 보기와 같이 보면서 제작하니 쉽게 이해할 수 있었다.
캔버스에 바빌론 엔진 적용하고 장면(Scene) 만들기
첫 번째로 사용할 바빌론 엔진을 가져오고, 같이 사용할 장면을 만드는 Scene도 함께 가져온다.
가져온 바빌론 엔진에 canvas ref의 current를 넣어주면 요소에 바빌론 엔진을 적용할 수 있다.
이후 메시들이 떠다닐 공간인 장면을 생성한다.
import React, {useRef, useEffect} from "react";
import {Engine, Scene} from "@babylonjs/core";
function SceneComponent() {
const reactCanvas = useRef<HTMLCanvasElement>(null);
useEffect(()=>{
const {current : canvas} = reactCanvas;
const engine = new Engine(canvas);
const scene = new Scene(engine);
}, [])
return <canvas ref={reactCanvas} />;
}
export default SceneComponent;
캔버스 화면 그리기
화면을 표시하려면 프레임마다 계속 화면을 갱신해 주는 코드를 넣어야 한다.
engine의 runRenderLoop 메서드를 이용하여 화면을 매 프레임 그릴 수 있다.
그리고 컴포넌트가 사라질 때 엔진을 제거하도록 클린업 함수를 추가하였다.
import React, {useRef, useEffect} from "react";
import {Engine, Scene} from "@babylonjs/core";
function SceneComponent() {
const reactCanvas = useRef<HTMLCanvasElement>(null);
useEffect(()=>{
const {current : canvas} = reactCanvas;
const engine = new Engine(canvas);
const scene = new Scene(engine);
engine.runRenderLoop(() => {
if (typeof onRender === "function") onRender(scene);
scene.render();
});
//화면을 리사이즈 하는 함수도 추가하였다. 이후 클린업 할 때 같이 사라진다.
const resize = () => {
scene.getEngine().resize();
};
if (window) {
window.addEventListener("resize", resize);
}
return () => {
scene.getEngine().dispose();
if (window) {
window.removeEventListener("resize", resize)
}
};
}, [])
return <canvas ref={reactCanvas} />;
}
export default SceneComponent;
렌더 이후 동작할 함수 실행부 만들기
useEffect에 실행부 코드를 추가하였다. 실행하는 함수는 프롭스로 받아올 onScreanReady이다. 이 함수는 이후 설명할 예정이다.
메시를 추가하고 화면이 그려졌을 때 캔버스 내부에서 행해질 모든 동작을 여기서 실행시킨다.
onRender또한 props에서 받아온다.
props의 type들은 babylon-hook에서 선언되어 가져와 사용한다.
import React, {useRef, useEffect} from "react";
import {Engine, Scene} from "@babylonjs/core";
import { BabylonjsProps } from "babylonjs-hook"; //프롭스의 타입
function SceneComponent({ onSceneReady, onRender } : BabylonjsProps) {
const reactCanvas = useRef<HTMLCanvasElement>(null);
useEffect(()=>{
const {current : canvas} = reactCanvas;
const engine = new Engine(canvas);
const scene = new Scene(engine);
//화면이 준비된 후 화면에 들어갈 모든 메시와 동작들을 실행시키는 부분
//함수의 구현의 props에서 받아오며 아래에서 구현한다.
if (scene.isReady()) {
onSceneReady(scene);
} else {
scene.onReadyObservable.addOnce((scene) => onSceneReady(scene));
}
engine.runRenderLoop(() => {
if (typeof onRender === "function") onRender(scene);
scene.render();
});
//화면을 리사이즈 하는 함수도 추가하였다. 이후 클린업 할 때 같이 해제된다.
const resize = () => {
scene.getEngine().resize();
};
if (window) {
window.addEventListener("resize", resize);
}
return () => {
scene.getEngine().dispose();
if (window) {
window.removeEventListener("resize", resize)
}
};
}, [])
return <canvas ref={reactCanvas} />;
}
export default SceneComponent;
작동부 함수 제작
App.tsx에서 생성한 SceneComponent.tsx를 가져와서 집어넣고, props로 줄 onSceneReady 함수와 onRender 함수를 만들자.
BabylonjsProps에서 정의된 두 함수는 매개변수로 scene을 받는다. BabylonjsProps를 클릭하고 f12를 눌러서 확인할 수 있다.
export declare type BabylonjsProps = {
antialias?: boolean;
engineOptions?: EngineOptions;
adaptToDeviceRatio?: boolean;
renderChildrenWhenReady?: boolean;
sceneOptions?: SceneOptions;
onSceneReady: (scene: Scene) => void;
/**
* Automatically trigger engine resize when the canvas resizes (default: true)
*/
observeCanvasResize?: boolean;
onRender?: (scene: Scene) => void;
children?: React.ReactNode;
};
onScreenReady 함수는 캔버스가 준비되었을 때 맨 처음 실행되는 함수이다.
해당 부분에서 카메라와 광원을 세팅하고 박스와 바닥을 생성한다.
onRender 함수는 렌더 될 때 마다 실행되는 함수이며, 박스가 회전하게 끔 구현하였다.
자세한 내용은 주석에 작성했다.
import {
HemisphericLight,
ArcRotateCamera,
MeshBuilder,
Vector3,
Scene,
Mesh,} from "@babylonjs/core";
import SceneComponent from "./components/SceneComponent";
export default function App() {
let box: Mesh;
//화면이 준비되었을 경우 맨 처음 실행되는 함수
const onSceneReady = (scene: Scene) => {
// ArcRotateCamera로 대상을 중심으로 회전하는 카메라를 생성
// 세부 속성은 자습서에 작성한 바와 동일하다.
const camera = new ArcRotateCamera(
"camera1",
(3 * Math.PI) / 2,
Math.PI / 50,
550 * 0.015,
Vector3.Zero(),
scene
);
const canvas = scene.getEngine().getRenderingCanvas();
camera.attachControl(canvas, true);
//반구형 광원을 세팅
const light = new HemisphericLight("light", new Vector3(0, 1, 0), scene);
light.intensity = 0.7;
//박스를 2의 크기로 생성하고 위치를 1로 만들었다.
//박스의 정 가운데 부분이 원점이므로 바닥위에 붙어있게 하기 위함.
box = MeshBuilder.CreateBox("box", { size: 2 }, scene);
box.position.y = 1;
//바닥을 생성
MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene); };
//이후 계속 렌더링 될 때 마다 실행될 함수. 박스를 계속 회전 시킴
const onRender = (scene: Scene) => {
if (box !== undefined) {
const deltaTimeInMillis = scene.getEngine().getDeltaTime();
const rpm = 10;
//박스를 y축을 기준으로 계산 값만큼 계속 회전시킨다.
box.rotation.y += (rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000);
}
};
return (
<div>
<SceneComponent onSceneReady={onSceneReady} onRender={onRender} />
</div>
);
}
이것으로 리액트에서 바빌론엔진을 이용해서 3D렌더를 구현해 보았다.
blender에서 만든 오브젝트를 가져올 수도 있다고 하니, 만들어진 오브젝트를 붙여 넣어 화려하게 꾸밀 수 도 있을 것 같다.
다음 프로젝트를 만들 때 한 번 활용해 봐야 겠다.