MMD 모델 로드
이제 씬에 MMD 모델을 로드하고 그림자를 추가합니다.
PMX 모델 다운로드
먼저 로드할 PMX 모델이 필요합니다.
이 예제에서는 YYB 하츠네 미쿠 10th 애니버서리 모델을 사용합니다.
모델을 다운로드하여 압축을 풀고 res/private_test/model/
폴더에 배치하세요.
모델 폴더 구조 예시
사이드 이펙트 임포트
먼저 모델을 로드하는 데 필요한 사이드 이펙트를 임포트합니다.
//...
import "babylon-mmd/esm/Loader/pmxLoader";
import "babylon-mmd/esm/Loader/mmdOutlineRenderer";
//...
babylon-mmd는 Babylon.js의 SceneLoader를 확장하여 PMX/PMD 모델을 로드할 수 있게 합니다.
PMD 모델을 로드하려면 **babylon-mmd/esm/Loader/pmdLoader
**를 임포트하고 아래에서 설명하는 PMX 모델 로드 방식과 동일하게 사용하면 됩니다.
**mmdOutlineRenderer
**는 MMD 모델의 아웃라인을 그리는 기능을 제공합니다. 아웃라인 렌더링이 필요 없다면 임포트하지 않아도 됩니다.
PMX 모델 로드
모델을 로드하려면 LoadAssetContainerAsync
함수를 사용합니다. **pluginOptions
**의 mmdmodel
옵션을 지정하여 MMD 모델 로더에 필요한 설정을 전달할 수 있습니다.
//...
import { LoadAssetContainerAsync } from "@babylonjs/core/Loading/sceneLoader";
//...
import { MmdStandardMaterialBuilder } from "babylon-mmd/esm/Loader/mmdStandardMaterialBuilder";
//...
import type { MmdMesh } from "babylon-mmd/esm/Runtime/mmdMesh";
//...
export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const materialBuilder = new MmdStandardMaterialBuilder();
const scene = new Scene(engine);
// ...
const modelMesh = await LoadAssetContainerAsync(
"res/private_test/model/YYB Hatsune Miku_10th/YYB Hatsune Miku_10th_v1.02.pmx",
scene,
{
pluginOptions: {
mmdmodel: {
loggingEnabled: true,
materialBuilder: materialBuilder
}
}
}).then(result => {
result.addAllToScene();
return result.rootNodes[0] as MmdMesh;
});
return scene;
}
}
모델을 로드할 때 pluginOptions.mmdmodel
옵션으로 전달하는 설정은 다음과 같습니다.
loggingEnabled
: 로드 과정에서 로그를 출력할지 여부입니다. 디버깅에 유용합니다.materialBuilder
: MMD 모델 머티리얼을 생성할 구현체를 지정합니다. **MmdStandardMaterialBuilder
**는 기본적인 MMD 머티리얼을 생성하는 기본 구현체입니다. 커스텀 머티리얼을 사용하고 싶다면IMmdMaterialBuilder
인터페이스를 구현해 전달할 수 있습니다.
모델이 로드되면 **AssetContainer
**가 반환됩니다. addAllToScene
메서드를 호출해서 모델을 씬에 추가하세요.
모델의 루트 노드는 rootNodes
배열의 첫 번째 요소로 접근할 수 있습니다. MMD 모델의 경우 첫 번째 루트 노드는 항상 MmdMesh
타입을 만족합니다.
모델에 그림자 추가
모델에 그림자를 추가하려면 앞서 생성한 **ShadowGenerator
**에 모델을 등록합니다.
//...
export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
//...
for (const mesh of modelMesh.metadata.meshes) mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(modelMesh);
return scene;
}
}
//...
MMD 모델은 머티리얼별로 분할된 여러 메시에 의해 구성됩니다. 따라서 modelMesh.metadata.meshes
배열을 순회하며 각 메시에 receiveShadows를 설정합니다.
결과
이제 브라우저를 확인하면 모델이 로드된 모습을 볼 수 있습니다.
전체 코드
import "@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent";
import "babylon-mmd/esm/Loader/pmxLoader";
import "babylon-mmd/esm/Loader/mmdOutlineRenderer";
import type { AbstractEngine } from "@babylonjs/core/Engines/abstractEngine";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { ShadowGenerator } from "@babylonjs/core/Lights/Shadows/shadowGenerator";
import { LoadAssetContainerAsync } from "@babylonjs/core/Loading/sceneLoader";
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 { MmdStandardMaterialBuilder } from "babylon-mmd/esm/Loader/mmdStandardMaterialBuilder";
import { MmdCamera } from "babylon-mmd/esm/Runtime/mmdCamera";
import type { MmdMesh } from "babylon-mmd/esm/Runtime/mmdMesh";
import type { ISceneBuilder } from "./baseRuntime";
export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: AbstractEngine): Promise<Scene> {
const materialBuilder = new MmdStandardMaterialBuilder();
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;
const modelMesh = await LoadAssetContainerAsync(
"res/private_test/model/YYB Hatsune Miku_10th/YYB Hatsune Miku_10th_v1.02.pmx",
scene,
{
pluginOptions: {
mmdmodel: {
loggingEnabled: true,
materialBuilder: materialBuilder
}
}
}).then(result => {
result.addAllToScene();
return result.rootNodes[0] as MmdMesh;
});
for (const mesh of modelMesh.metadata.meshes) mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(modelMesh);
return scene;
}
}