メインコンテンツまでスキップ

MMD モデルの読み込み

ここではMMD モデルをシーンに読み込みシャドウを追加します。

PMX モデルのダウンロード

まず、読み込むための PMX モデルが必要です。

この例では YYB Hatsune Miku 10th Anniversary モデルを使用します。

モデルをダウンロードし、解凍して res/private_test/model/ フォルダーに配置します。

vscode-file-structure
モデルフォルダー構造の例

必要なサイドエフェクトのインポート

まず、モデルを読み込むために必要なサイドエフェクトをインポートします。

src/sceneBuilder.ts
//...
import "babylon-mmd/esm/Loader/pmxLoader";
import "babylon-mmd/esm/Loader/mmdOutlineRenderer";
//...

babylon-mmdBabylon.js の SceneLoader を拡張して PMX/PMD モデルの読み込みを可能にします。

PMD モデルを読み込むには、babylon-mmd/esm/Loader/pmdLoader をインポートし、以下で説明する PMX モデルの読み込みと同じメソッドを使用します。

mmdOutlineRenderer は、MMD モデルのアウトラインを描画する機能を提供します。アウトラインのレンダリングが不要な場合は、インポートする必要はありません。

PMX モデルの読み込み

LoadAssetContainerAsync ファンクションを使用してモデルを読み込みます。pluginOptions 内の mmdmodel オプションを指定することで、MMD モデルローダーに必要な設定を渡すことができます。

src/sceneBuilder.ts
//...
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 にモデルを追加します。

src/sceneBuilder.ts
//...
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 配列を反復処理し、各メッシュがシャドウを受け取るように設定します。

結果

ブラウザで確認すると、モデルが読み込まれていることが確認できます。

result

完全なコード
src/sceneBuilder.ts
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;
}
}