바로 시작할 수 있는 프로젝트 구성
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;