본문으로 건너뛰기

기본 씬 만들기

이제 기본 씬을 만들어 봅시다. 다음 요소들을 추가할 예정입니다.

  • 카메라 생성: 씬을 바라볼 시점을 제공합니다.
  • 배경색과 앰비언트 컬러 설정: 씬의 **클리어컬러(ClearColor)**와 **앰비언트컬러(AmbientColor)**를 설정합니다.
  • 라이팅 추가: 모델이 잘 보이도록 씬을 조명합니다.
  • 그라운드 생성: 모델이 설 수 있는 바닥을 만듭니다.

카메라 생성

먼저 카메라를 생성합니다.

렌더러가 정상 동작하려면 카메라가 **액티브카메라(activeCamera)**로 설정되어 있어야 합니다.

src/sceneBuilder.ts
//...
import { MmdCamera } from "babylon-mmd/esm/Runtime/mmdCamera";

import type { ISceneBuilder } from "./baseRuntime";

export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);

const mmdCamera = new MmdCamera("MmdCamera", new Vector3(0, 10, 0), scene);

return scene;
}
}

여기에서는 babylon-mmd 패키지가 제공하는 **MmdCamera**를 사용합니다.

이 카메라는 MMD 소프트웨어의 카메라 동작을 재현합니다.

배경색과 앰비언트 컬러 설정

배경색앰비언트 라이팅을 설정합니다. 배경색은 ClearColor, 앰비언트 라이팅은 **AmbientColor**로 지정합니다.

src/sceneBuilder.ts
//...
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
//...
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
//...

export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);
scene.clearColor = new Color4(0.95, 0.95, 0.95, 1.0);
scene.ambientColor = new Color3(0.5, 0.5, 0.5);

const mmdCamera = new MmdCamera("MmdCamera", new Vector3(0, 10, 0), scene);

return scene;
}
}

여기에서 **scene.ambientColor**는 모든 머티리얼의 ambientColor 속성에 영향을 줍니다.

MMD 모델의 경우 셰이딩을 정확히 재현하려면 ambientColor에 0.5 스케일링을 적용해야 하므로 이 (0.5, 0.5, 0.5) 값은 임의가 아니라 의도적인 값입니다.

라이트 생성

MMD의 라이팅 모델단일 디렉셔널 라이트로 정의됩니다.

따라서 다른 라이팅 구성을 사용하면 제대로 렌더링되지 않을 수 있습니다. 예를 들어 헤미스페릭 라이트를 함께 사용하면 MMD 소프트웨어와는 다른 셰이딩 결과가 나올 수 있습니다.

따라서 **DirectionalLight**를 만들고, 그림자를 렌더링하기 위해 **ShadowGenerator**도 생성합니다.

src/sceneBuilder.ts
import "@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent";
//...

import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { ShadowGenerator } from "@babylonjs/core/Lights/Shadows/shadowGenerator";
//...
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
//...
export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);
scene.clearColor = new Color4(0.95, 0.95, 0.95, 1.0);
scene.ambientColor = new Color3(0.5, 0.5, 0.5);

const mmdCamera = new MmdCamera("MmdCamera", new Vector3(0, 10, 0), scene);

const directionalLight = new DirectionalLight("DirectionalLight", new Vector3(0.5, -1, 1), scene);
directionalLight.intensity = 1.0;
directionalLight.autoCalcShadowZBounds = true;

const shadowGenerator = new ShadowGenerator(1024, directionalLight, true);
shadowGenerator.transparencyShadow = true;
shadowGenerator.usePercentageCloserFiltering = true;
shadowGenerator.forceBackFacesOnly = true;
shadowGenerator.filteringQuality = ShadowGenerator.QUALITY_MEDIUM;
shadowGenerator.frustumEdgeFalloff = 0.1;
return scene;
}
}

ShadowGenerator 설정은 임의의 값이므로 필요에 따라 조정할 수 있습니다.

그라운드 생성

그라운드 플레인을 생성합니다. 이는 씬을 시각적으로 이해하는 데 도움이 되며 필수 사항은 아닙니다.

src/sceneBuilder.ts
//...
import { CreateGround } from "@babylonjs/core/Meshes/Builders/groundBuilder";
//...
export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);

// ...

const shadowGenerator = new ShadowGenerator(1024, directionalLight, true);
shadowGenerator.transparencyShadow = true;
shadowGenerator.usePercentageCloserFiltering = true;
shadowGenerator.forceBackFacesOnly = true;
shadowGenerator.filteringQuality = ShadowGenerator.QUALITY_MEDIUM;
shadowGenerator.frustumEdgeFalloff = 0.1;

const ground = CreateGround("ground1", { width: 100, height: 100, subdivisions: 2, updatable: false }, scene);
ground.receiveShadows = true;
return scene;
}
}

결과

이제 씬을 실행하면 더 이상 에러가 발생하지 않으며, 아래와 같은 하얀 화면이 표시됩니다.

result

전체 코드
src/sceneBuilder.ts
import "@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent";

import type { AbstractEngine } from "@babylonjs/core/Engines/abstractEngine";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { ShadowGenerator } from "@babylonjs/core/Lights/Shadows/shadowGenerator";
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { CreateGround } from "@babylonjs/core/Meshes/Builders/groundBuilder";
import { Scene } from "@babylonjs/core/scene";
import { MmdCamera } from "babylon-mmd/esm/Runtime/mmdCamera";

import type { ISceneBuilder } from "./baseRuntime";

export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);
scene.clearColor = new Color4(0.95, 0.95, 0.95, 1.0);
scene.ambientColor = new Color3(0.5, 0.5, 0.5);

const mmdCamera = new MmdCamera("MmdCamera", new Vector3(0, 10, 0), scene);

const directionalLight = new DirectionalLight("DirectionalLight", new Vector3(0.5, -1, 1), scene);
directionalLight.intensity = 1.0;
directionalLight.autoCalcShadowZBounds = true;

const shadowGenerator = new ShadowGenerator(1024, directionalLight, true);
shadowGenerator.transparencyShadow = true;
shadowGenerator.usePercentageCloserFiltering = true;
shadowGenerator.forceBackFacesOnly = true;
shadowGenerator.filteringQuality = ShadowGenerator.QUALITY_MEDIUM;
shadowGenerator.frustumEdgeFalloff = 0.1;

const ground = CreateGround("ground1", { width: 100, height: 100, subdivisions: 2, updatable: false }, scene);
ground.receiveShadows = true;

return scene;
}
}