본문 바로가기
Ect./Library

react-babylonjs 라이브러리 사용해보기

by 쿠리의일상 2023. 10. 3.

https://brianzinn.github.io/react-babylonjs/

 

https://brianzinn.github.io/react-babylonjs/

react-babylonjs integrates the Babylon.js real time 3D engine with React. react-babylonjs lets you build 3D and XR scenes and games using reactive programming, reusable components, and hooks. The Babylon.js API is mostly covered declaratively thanks to cod

brianzinn.github.io

 

3D, XR, 게임 등에 사용하는 바빌론 라이브러리 리액트 Wrapper

 

바빌론js 의 대부분 선언적 코드 생성으로 인해 그림자, 물리, 3D 모델 등을 props 로 추가하여 작업할 수 있다고 한다. 바빌론js를 사용하기 앞서 리액트를 사용하는 개발자로써 다뤄보기로 하였다. 공식문서 순서에 따라 정리해보았으며 자세한 사항을 알고자한다면... 직접 해석하길 바란다. (나도 안되는 영어로 읽어본 것이니)

 

시작하기

npm i react-babylonjs @babylonjs/core @babylonjs/gui
yarn add react-babylonjs @babylonjs/core @babylonjs/gui

설치는 기본적으로 babylonjs/core 가 필요하고, react-babylonjs 로 사용해줘야한다.

 

예시를 보며 분석하기

import {
  Engine,
  FreeCamera,
  HemisphericLight,
  MeshBuilder,
  Scene,
  Vector3,
} from '@babylonjs/core'
import React, { useEffect, useRef } from 'react'
const SceneComponent = (props) => {
  const reactCanvas = useRef(null)
  const {
    canvasId,
    antialias,
    engineOptions,
    adaptToDeviceRatio,
    sceneOptions,
    onRender,
    onSceneReady,
    ...rest
  } = props
  useEffect(() => {
    if (!reactCanvas.current) return
    const engine = new Engine(
      reactCanvas.current,
      antialias,
      engineOptions,
      adaptToDeviceRatio
    )
    const scene = new Scene(engine, sceneOptions)
    if (scene.isReady()) {
      onSceneReady(scene)
    } else {
      scene.onReadyObservable.addOnce(onSceneReady)
    }
    engine.runRenderLoop(() => {
      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)
      }
    }
  }, [
    antialias,
    engineOptions,
    adaptToDeviceRatio,
    sceneOptions,
    onRender,
    onSceneReady,
  ])
  return <canvas id={canvasId} ref={reactCanvas} {...rest} />
}
let box
const onSceneReady = (scene) => {
  // This creates and positions a free camera (non-mesh)
  var camera = new FreeCamera('camera1', new Vector3(0, 5, -10), scene)
  // This targets the camera to scene origin
  camera.setTarget(Vector3.Zero())
  const canvas = scene.getEngine().getRenderingCanvas()
  // This attaches the camera to the canvas
  camera.attachControl(canvas, true)
  // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
  var light = new HemisphericLight('light', new Vector3(0, 1, 0), scene)
  // Default intensity is 1. Let's dim the light a small amount
  light.intensity = 0.7
  // Our built-in 'box' shape.
  box = MeshBuilder.CreateBox('box', { size: 2 }, scene)
  // Move the box upward 1/2 its height
  box.position.y = 1
  // Our built-in 'ground' shape.
  MeshBuilder.CreateGround('ground', { width: 6, height: 6 }, scene)
}
/**
 * Will run on every frame render.  We are spinning the box on y-axis.
 */
const onRender = (scene) => {
  if (box !== undefined) {
    var deltaTimeInMillis = scene.getEngine().getDeltaTime()
    const rpm = 10
    box.rotation.y += (rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000)
  }
}
const App = () => (
  <div>
    <SceneComponent
      canvasId="babylon-canvas"
      antialias
      onSceneReady={onSceneReady}
      onRender={onRender}
    />
  </div>
)
export default App

 

기본적으로 사용되는 Engine, FreeCamera, HemisphericLight, MeshBuilder, Scene, Vector3 는 core 에서 가져오고, 씬 컴포넌트에 canvasId 와 onSceneReady, onRender props 를 지정해서 씬을 직접 커스텀 해줄 수 있다.

onSceneReady 의 경우 씬과 관련된 카메라, 조명 등의 설정과 보여질 컴포넌트에 대한 렌더링을 설정해준다.

onRender 는 씬 렌더가 완료되면 보여질 컴포넌트의 행위 등을 설정한다. 위의 예시의 경우 box 오브젝트의 rotation.y 를 사용하여 회전처리를 해준다. 

이를 babylonjs-hook 라이브러리를 추가하여 더 간단하게 처리가 가능하다.

import {
  FreeCamera,
  HemisphericLight,
  MeshBuilder,
  Vector3,
} from '@babylonjs/core'
import SceneComponent from 'babylonjs-hook'
let box
const onSceneReady = (scene) => {
  // This creates and positions a free camera (non-mesh)
  var camera = new FreeCamera('camera1', new Vector3(0, 5, -10), scene)
  // This targets the camera to scene origin
  camera.setTarget(Vector3.Zero())
  const canvas = scene.getEngine().getRenderingCanvas()
  // This attaches the camera to the canvas
  camera.attachControl(canvas, true)
  // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
  var light = new HemisphericLight('light', new Vector3(0, 1, 0), scene)
  // Default intensity is 1. Let's dim the light a small amount
  light.intensity = 0.7
  // Our built-in 'box' shape.
  box = MeshBuilder.CreateBox('box', { size: 2 }, scene)
  // Move the box upward 1/2 its height
  box.position.y = 1
  // Our built-in 'ground' shape.
  MeshBuilder.CreateGround('ground', { width: 6, height: 6 }, scene)
}
/**
 * Will run on every frame render.  We are spinning the box on y-axis.
 */
const onRender = (scene) => {
  if (box !== undefined) {
    var deltaTimeInMillis = scene.getEngine().getDeltaTime()
    const rpm = 10
    box.rotation.y += (rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000)
  }
}
const App = () => (
  <div>
    <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} />
  </div>
)
export default App

위의 예시와 동일한 코드지만 그 길이가 줄어들었다. babylonjs-hook 라이브러리의 SceneComponent 를 사용하여 씬 컴포넌트를 직접 만들어주는 과정을 생략하게 된 것이다. 나머지는 동일하다.

 

 

기본 씬부터 차근히 만들어보기

react-babylonjs에서 기본적으로 씬은 Engine 과 Scene 컴포넌트를 사용하여 만들어줄 수 있다.

import { Engine, Scene } from 'react-babylonjs'

const BabylonApp = ({ children }) => (
  <div style={{ flex: 1, display: 'flex' }}>
    <Engine antialias adaptToDeviceRatio canvasId="babylon-canvas">
      <Scene>{children}</Scene>
    </Engine>
  </div>
)

 

빛, 카메라, 표면 추가하기

위의 예제로는 씬 안에 아무것도 추가된 게 없으므로 보이지 않는 게 당연하다. 무언가 보이게 만들기 위해선 빛과 카메라 그리고 무언가의 컴포넌트를 넣어줘야 한다.

빛의 경우 hemispericLight

카메라는 freeCamera

땅의 경우 ground 컴포넌트를 넣어서 기본 씬을 구성해주자.

import { Engine, Scene } from "react-babylonjs";
import { Vector3 } from "@babylonjs/core";

export default ({ children }) => (
  <div style={{ flex: 1, display: "flex" }}>
    <Engine
      antialias
      adaptToDeviceRatio
      canvasId="babylon-js"
      renderOptions={{
        whenVisibleOnly: true,
      }}
    >
      <Scene>
        <freeCamera name="camera1" position={new Vector3(0, 5, -10)} setTarget={[Vector3.Zero()]} />
        <hemisphericLight name="light1" intensity={0.7} direction={new Vector3(0, 1, 0)} />
        <ground name="ground" width={6} height={6} />
      </Scene>
    </Engine>
  </div>
);

 

땅 위에 박스를 추가해준다. 박스의 위치를 지정해줘야한다.

박스 사이즈가 2일 때 position 값을 Vector3(0, 0, 0) 으로 한 경우
박스 사이즈가 2일 때 중심점을 기준으로 0이므로 Vector3(0, 1, 0) 으로 설정

  <div style={{ flex: 1, display: "flex" }}>
    <Engine
      antialias
      adaptToDeviceRatio
      canvasId="babylon-js"
      renderOptions={{
        whenVisibleOnly: true,
      }}
    >
      <Scene>
        <freeCamera name="camera1" position={new Vector3(0, 5, -10)} setTarget={[Vector3.Zero()]} />
        <hemisphericLight name="light1" intensity={0.7} direction={new Vector3(0, 1, 0)} />
        <ground name="ground" width={6} height={6} />
        <box name="box" size={2} position={new Vector3(0, 1, 0)} rotation={Vector3.Zero()} />
      </Scene>
    </Engine>
  </div>

이런 식으로 씬 안에 컴포넌트를 넣어줘서 처리한다. 만약 박스 컴포넌트를 분리하고자 한다면

import { MeshBuilder } from '@babylonjs/core'

const box = MeshBuilder.CreateBox('box', { size: 2 }, scene);
box.position = new Vector3(0, 1, 0);
box.rotation = Vector3.Zero();

이런 식으로 지정해줄 수도 있다.

다른 컴포넌트들도 컴포넌트화 되어 있는 것을 위처럼 분리해서 사용해줄 수 있다.

import { FreeCamera, Vector3 } from '@babylonjs/core'
var camera = new FreeCamera('camera1', new Vector3(0, 5, -10), scene)
camera.setTarget(Vector3.Zero())

 

기본적인 내용은 이러하고 이제 애니메이션 같은건 차차 다뤄볼 예정이다.