MMD モデルの読み込み
ここではMMD モデルをシーンに読み込み、シャドウを追加します。
PMX モデルのダウンロード
まず、読み込むための PMX モデルが必要です。
この例では YYB Hatsune Miku 10th Anniversary モデルを使用します。
モデルをダウンロードし、解凍して 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
配列を反復処理し、各メッシュがシャドウを受け取るように設定します。
結果
ブラウザで確認すると、モデルが読み込まれていることが確認できます。
完全なコード
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;
}
}