바로 시작할 수 있는 프로젝트 구성
git clone https://github.com/GISDEVCODE/babylonjs-with-javascript-starter.git 생성할폴더
Mesh의 Bounding Box 크기값
const getParentSize = parent => { const sizes = parent.getHierarchyBoundingVectors() const size = { x: sizes.max.x - sizes.min.x, y: sizes.max.y - sizes.min.y, z: sizes.max.z - sizes.min.z } return size };
HDR, ENV를 통한 광원 및 배경
데이터 형식에 따라 코드도 달라짐. 먼저 HDR에 대한 코드는 다음과 같다.
const hdrTexture = new BABYLON.HDRCubeTexture("christmas_photo_studio_01_2k.hdr", this.#scene, 512); this.#scene.environmentTexture = hdrTexture; /* const skybox = */ this.#scene.createDefaultSkybox(this.#scene.environmentTexture); // skybox.visibility = 0.1;
ENV에 대한 코드는 다음과 같다.
this.#scene.createDefaultEnvironment({ environmentTexture: "./forest.env", // as lighting skyboxTexture: "./forest.env", // as background });
매시에 대한 로컬좌표계축의 원점을 유지하고 이동
sphere.setPivotMatrix(BABYLON.Matrix.Translation(2, 2, 0), false);
여러개의 매시로 구성된 배열(meshes)를 하나의 Mesh로 만드는 코드
const singleMesh = Mesh.MergeMeshes(meshes as Mesh[], true, true, undefined, false, true);
Three.js의 Group에 대응하는 클래스는 BABYLON.TransformNode이다.
async #createModel() { const { meshes } = await BABYLON.SceneLoader.ImportMeshAsync("", "/", "Barrel_01_2k.gltf"); this.#group = new BABYLON.TransformNode("group", this.#scene); meshes[1].parent = this.#group; this.#group.position.y = -0.5; }
WebGPU 렌더러 설정
export default class App { #engine; #scene; #mesh; constructor() { this.#setupBabylon(); } async #setupBabylon() { const canvas = document.querySelector("canvas"); this.#engine = new BABYLON.WebGPUEngine(canvas, { adaptToDeviceRatio: true }); await this.#engine.initAsync(); this.#scene = new BABYLON.Scene(this.#engine); this.#createCamera(); this.#createLight(); this.#createModel(); this.#setupEvents(); } ...
기본적으로 사용하는 좌표계는 왼손 좌표계이다. 하지만 오른손 좌표계로의 전환도 가능한데 아래의 코드를 실행해 주면 바로 오른손 좌표계로 전환되어 이를 기준으로 개발이 가능하다.
scene.useRightHandedSystem = true;
glTF 형식 등을 가져오기 위해 설치해야할 패키지
npm i babylonjs-loaders
import "babylonjs-loaders" .. #createModel() { BABYLON.SceneLoader.ImportMeshAsync( "", "https://assets.babylonjs.com/meshes/", "both_houses_scene.babylon").then((result) => { const house1 = this.#scene.getMeshByName("detached_house"); house1.position.y = 2; const house2 = result.meshes[2]; house2.position.y = 1; } ); BABYLON.SceneLoader.ImportMesh("", Assets.meshes.Yeti.rootUrl, Assets.meshes.Yeti.filename, this.#scene, (meshes) => { meshes[0].scaling = new BABYLON.Vector3(0.1, 0.1, 0.1); } ); }
위 코드에서 Assets은 다음 코드가 필요함
<script src="https://assets.babylonjs.com/generated/Assets.js"></script>
도 단위를 라디언 단위로 변경해주는 API
BABYLON.Tools.ToRadians(45);
물리엔진 하복(Havok)을 사용하기 위해서는 먼저 @babylonjs/havok를 설치하고 node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm 파일을 node_modules/.vite/deps 경로에 복사해 두어야 한다. 아래는 코드예시이다.
import * as BABYLON from "babylonjs" import HavokPhysics from "@babylonjs/havok"; export default class App { #engine; #scene; constructor() { const canvas = document.querySelector("canvas"); this.#engine = new BABYLON.Engine(canvas, true, { adaptToDeviceRatio: true }); this.#scene = new BABYLON.Scene(this.#engine); this.#createCamera(); this.#createLight(); this.#createModel(); this.#setupEvents(); } #createLight() { this.#scene.createDefaultLight(); } #createCamera() { this.#scene.createDefaultCamera(true, false, true); const camera = this.#scene.cameras[0]; camera.position = new BABYLON.Vector3(4, 4, 10); } async #createModel() { const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, this.#scene); sphere.position.y = 4; const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 10, height: 10 }, this.#scene); const havokInstance = await HavokPhysics(); const hk = new BABYLON.HavokPlugin(true, havokInstance); this.#scene.enablePhysics(new BABYLON.Vector3(0, -9.8, 0), hk); const sphereAggregate = new BABYLON.PhysicsAggregate( sphere, BABYLON.PhysicsShapeType.SPHERE, { mass: 1, restitution: 0.75 }, this.#scene ); const groundAggregate = new BABYLON.PhysicsAggregate( ground, BABYLON.PhysicsShapeType.BOX, { mass: 0 }, this.#scene ); const viewer = new BABYLON.Debug.PhysicsViewer(this.#scene); for (const mesh of this.#scene.meshes) { if (mesh.physicsBody) { viewer.showBody(mesh.physicsBody); } } } #setupEvents() { window.addEventListener("resize", this.#resize.bind(this)); this.#scene.registerBeforeRender(this.update.bind(this)); this.#engine.runRenderLoop(this.render.bind(this)) } update({ deltaTime }) { } render() { this.#scene.render(); } #resize() { this.#engine.resize(); } }
Node Material Editor 예
위의 결과를 JSON으로 저장할 수 있으며 코드를 통해 사용하는 예는 아래와 같다.
BABYLON.NodeMaterial.ParseFromFileAsync("nodeMat", "./nodeMaterial.json", this.#scene).then((mat) => { this.#mesh.material = mat; });
Node Geometry Editor 예
위의 결과를 JSON으로 저장할 수 있으며 코드를 통해 사용하는 예는 아래와 같다.
#createModel() { const assetsManager = new BABYLON.AssetsManager(this.#scene); const nodeGeometryFile = assetsManager.addTextFileTask("file", "./nodeGeometry.json"); assetsManager.load(); assetsManager.onFinish = async (tasks) => { const nodeGeometryJSON = JSON.parse(nodeGeometryFile.text); const nodeGeometry = await BABYLON.NodeGeometry.Parse(nodeGeometryJSON); nodeGeometry.build(); /* const myGeometry = */ nodeGeometry.createMesh("myGeometry"); } }
ArcRotateCamera의 마우스 휠 줌 기능 비활성화
this.#scene.createDefaultCamera(true, false, true); const camera = this.#scene.cameras[0]; camera.inputs.removeByType("ArcRotateCameraMouseWheelInput");
scene을 구성하는 mesh를 제거하기 위해서는 dispose 매서드를 호출
const meshes = this.#scene.getMeshesById("cannon"); meshes[0].dispose();
사용할 카메라 선택하기
this.#scene.activeCamera = myCamera; this.#scene.activeCamera.attachControl(true); // 위의 코드는 다음 한줄로 대체가능함 myCamera.attachControl(true);
앞면 뒷면 모두 렌더링하기
방법은 2가지 인데 매시에 대해서 sideOrientation: BABYLON.Mesh.DOUBLESIDE를 지정하는 것, 또는 재질의 backFaceCulling = false로 지정하는 것으로 가능함
const plane = BABYLON.MeshBuilder.CreatePlane("wall", { size: 10, sideOrientation: BABYLON.Mesh.DOUBLESIDE }, this.#scene); const planeMaterial = new BABYLON.StandardMaterial("planeMat", this.#scene); planeMaterial.backFaceCulling = false; plane.material = planeMaterial;