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

MMD モデルローダー (PmxLoader, PmdLoader)

このセクションでは、MMD モデルファイル (PMX, PMD) を読み込むために使用されるコンポーネントについて説明します。

MMD モデルは PmxLoader または PmdLoader を使用して読み込むことができます。

PmxLoader/PmdLoader

PmxLoaderPmdLoader は、それぞれ PMX と PMD ファイルを読み込むために使用されるローダーです。

Babylon.js SceneLoader へのローダーの登録

これらは Babylon.js SceneLoader API と統合されています。

したがって、使用する前に、まず PmxLoader または PmdLoader を Babylon.js SceneLoader に登録する必要があります。

これは、"babylon-mmd/esm/Loader/pmxLoader" または "babylon-mmd/esm/Loader/pmdLoader" をインポートすることで実行できます。

// .pmx ファイルを読み込むために、`PmxLoader` インスタンスをグローバル SceneLoader 状態に登録します。
import "babylon-mmd/esm/Loader/pmxLoader";

// .pmd ファイルを読み込むために、`PmdLoader` インスタンスをグローバル SceneLoader 状態に登録します。
import "babylon-mmd/esm/Loader/pmdLoader";

これは暗黙的に以下のコードを実行します:

RegisterSceneLoaderPlugin(new PmxLoader()); // "babylon-mmd/esm/Loader/pmxLoader" をインポートする場合
RegisterSceneLoaderPlugin(new PmdLoader()); // "babylon-mmd/esm/Loader/pmdLoader" をインポートする場合
備考

UMD パッケージを使用している場合、これらのサイドエフェクトはスクリプトがロードされるときに自動的に適用されます。したがって、それらを個別にインポートする必要はありません。

備考

import "babylon-mmd"; のようにルートからシンボルをインポートすると、すべてのサイドエフェクトが自動的に適用されます。したがって、それらを個別にインポートする必要はありません。

ただし、この場合、ツリーシェイキングが適用されないため、プロダクション環境では推奨されません。

MMD モデルの読み込み

Babylon.js SceneLoader API は、シーンに 3D アセットを追加するためのいくつかのファンクションを提供します。

これらのファンクションのいずれかを使用して、MMD モデルを読み込むことができます。

ImportMeshAsync

ImportMeshAsync ファンクションは、MMD モデルをシーンに追加し、読み込まれた要素を ISceneLoaderAsyncResult 形式で返します。

戻り値から MMD のルートノードである MmdMesh を取得できます。

const result: ISceneLoaderAsyncResult = await ImportMeshAsync("path/to/mmdModel.pmx", scene);
const mmdMesh = result.meshes[0] as MmdMesh;

上記の例では、result.meshes[0]MmdMesh にキャストしています。これは、MMD モデルを読み込む場合、常に有効です。

MMD モデルを読み込む場合、ISceneLoaderAsyncResult.meshes 配列の 最初の要素は常に MMD モデルの ルートメッシュです。

AppendSceneAsync

AppendSceneAsync ファンクションは、MMD モデルをシーンに追加します。ただし、戻り値がないため、シーンの meshes プロパティを使用して、読み込まれた要素を取得する必要があります。

したがって、このメソッドは 一般的には使用されません

await AppendSceneAsync("path/to/mmdModel.pmx", scene);

LoadAssetContainerAsync

LoadAssetContainerAsync ファンクションは、MMD モデルを読み込み、MMD モデルを構成するすべてのリソースを含む AssetContainer を返します。 この AssetContainer には、読み込まれたメッシュ、マテリアル、テクスチャなどが含まれています。

ImportMeshAsync と同様に、返された AssetContainer から MMD モデルの ルートメッシュを取得できます。

const assetContainer: AssetContainer = await LoadAssetContainerAsync("path/to/mmdModel.pmx", scene);
assetContainer.addAllToScene();
const mmdMesh = assetContainer.meshes[0] as MmdMesh;

上記の例では、assetContainer.meshes[0]MmdMesh にキャストしています。これは、MMD モデルを読み込む場合、常に有効です。

MMD モデルを読み込む場合、AssetContainer.meshes 配列の 最初の要素は常に MMD モデルの ルートメッシュです。

LoadAssetContainerAsync ファンクションは、MMD モデルが完全に読み込まれた後にすべてをシーンに一度に追加しますが、ImportMeshAsync ファンクションは、MMD モデルの読み込みプロセス中にメッシュ、マテリアル、テクスチャなどを非同期でシーンに追加します。非同期処理によって引き起こされる可能性のある問題を避けるために、MMD モデルを読み込むには LoadAssetContainerAsync ファンクションを使用することをお勧めします

ブラウザの File API を使用する

上記では、モデルの URL を使用して MMD モデルを読み込む方法を学びました。 しかし、URL ベースの読み込み方法には問題があり、これらは ブラウザの File API を使用することで解決できます。

ユーザーから受け取ったファイルを読み込むために File API を使用することもできます。

URL ベースの読み込みの問題点

URL を使用する場合、ローダーは PMX/PMD ファイルをフェッチし、その後、3D モデルに必要な テクスチャファイルを再度フェッチします。

PMX/PMD フォーマットには、ファイルの場所を基準にした相対パスとしてテクスチャファイルパスが含まれています

例えば、以下のようなファイル構造の場合:

file1
├── model.pmx
├── texture1.png
├── texture2.png
└── file2
├── texture3.png
└── texture4.png

テクスチャファイルパスは通常、PMX/PMD ファイルに以下のような文字列として格納されています:

texture1.png
texture2.png
file2/texture3.png
file2/texture4.png

しかし、Windows ファイルシステムはファイルやフォルダの大文字と小文字を区別しないため、以下のようなデータも有効です:

Texture1.png
Texture2.png
File2/Texture3.png
File2/Texture4.png

対照的に、ブラウザ環境でフェッチする場合、大文字と小文字が区別されるため、大文字と小文字が完全に一致しない場合、テクスチャは見つかりません。

この問題を解決するために、フェッチの代わりに File API ベースの読み込み方法を使用できます。

MMD モデルファイルを含むフォルダの選択

まず、File API を使用してローカルファイルを選択して読み込む方法を実装する必要があります。

ここでは、.pmx/.pmd ファイルだけでなく、モデルによって使用されるテクスチャファイルも読み込む必要があります。

したがって、ユーザーが MMD モデルの読み込みに必要なすべてのリソースを含むフォルダを選択できるようにする必要があります。

例えば、以下のようなファイル構造の場合:

file1
├── model.pmx
├── texture1.png
├── texture2.png
└── file2
├── texture3.png
└── texture4.png

ユーザーが file1 フォルダを選択できるようにする必要があります。

理想的には、showDirectoryPicker API を使用してフォルダを選択できますが、この機能は Firefox と Safari ではサポートされていません

したがって、このドキュメントでは、HTML のファイル入力を使用してフォルダを選択する方法について説明します。

まず、HTML のファイル入力を作成し、directory および webkitdirectory 属性を使用してディレクトリ選択を有効にします。

<input type="file" id="folderInput" directory webkitdirectory />

その後、ユーザーがフォルダを選択すると、フォルダ内のすべてのファイルを読み込むことができます。

const fileInput = document.getElementById("folderInput") as HTMLInputElement;
fileInput.onchange = (): void => {
if (fileInput.files === null) return;
const files = Array.from(fileInput.files);

// 読み込むモデルファイルを見つける(複数の PMX/PMD ファイルからユーザーが選択できるような UI を実装することもできます)
let modelFile: File | null = null;
for (const file of files) {
const name = file.name.toLowerCase();
if (name.endsWith(".pmx") || name.endsWith(".pmd")) {
modelFile = file;
break;
}
}
if (modelFile === null) {
console.error("PMX/PMD モデルファイルが見つかりません。");
return;
}

// これで、フォルダ内のすべてのファイルを含む files と、読み込むターゲットとしての modelFile が取得できました。
};

または、フォルダ選択のためにドラッグ&ドロップ機能を実装することもできます。これについては、babylon-mmd-viewer fileDropControlBuilder.ts を参照してください。

URL の代わりに File を使用する

上記で URL を使用して読み込んだコードで、URL をファイルに置き換えるだけです。ここでも、テクスチャを読み込むために、フォルダから読み込まれたすべてのファイルのリストを渡す必要があります。

const assetContainer: AssetContainer = await LoadAssetContainerAsync(
modelFile,
scene,
{
rootUrl: modelFile.webkitRelativePath.substring(0, modelFile.webkitRelativePath.lastIndexOf("/") + 1),
pluginOptions: {
mmdmodel: {
referenceFiles: files // 潜在的にテクスチャである可能性のあるすべてのファイルを渡す
}
}
}
);
assetContainer.addAllToScene();
const mmdMesh = assetContainer.meshes[0] as MmdMesh;

この方法で読み込む場合、ローダーは files.webkitRelativePath を使用してテクスチャを検索します。これにより、Windows ファイルシステムのパス解決方法をエミュレートして、テクスチャファイルを正確に見つけることができます。

rootUrl は、modelFile.webkitRelativePath から最後の / までのパスを抽出したものです。 このパスは、MMD モデルが配置されているフォルダのパスを表し、ローダーはこのパスを基準に相対パスを計算してテクスチャファイルを検索します。

URL テクスチャパスの解決

サーバーから MMD モデルを提供する場合、URL フェッチメソッドを使用する必要があるため、File API アプローチを使用できません。この場合、テクスチャ読み込みの問題を解決するために2つの方法を使用できます:

  1. モデル修正 - PMXEditor を使用して、モデルのテクスチャパスの大文字小文字の誤りを修正します。
  2. BPMX への変換 - PMX/PMD フォーマットから BPMX フォーマットに変換する際、変換プロセス中にテクスチャパスの問題が解決されます。詳細については、The Babylon PMX Format のドキュメントを参照してください。

ローダーオプション

MMD モデルローダーは、MMD モデルを読み込む際に複数のシナリオで最良の結果を得るためのさまざまなオプションを提供しています。

これらのオプションは pluginOptions を通して渡されます。

const assetContainer: AssetContainer = await LoadAssetContainerAsync(
modelFileOrUrl,
scene,
{
pluginOptions: {
mmdmodel: {
materialBuilder: null,
useSdef: true,
buildSkeleton: true,
buildMorph: true,
boundingBoxMargin: 10,
alwaysSetSubMeshesBoundingInfo: true,
preserveSerializationData: false,
loggingEnabled: false,

referenceFiles: [],
optimizeSubmeshes: true,
optimizeSingleMaterialModel: true
}
}
}
);

各オプションは以下の目的を果たします:

materialBuilder

MMD モデルにマテリアルを割り当てる方法を定義する IMmdMaterialBuilder インスタンスを設定します。
デフォルト値は null です。デフォルト値が null の場合、MMD モデルはマテリアルなしで読み込まれます。

詳細については、Material Builder のドキュメントを参照してください。

useSdef

モデルが SDEF(球状変形) をサポートするかどうかを設定します。
デフォルト値は true です。

詳細については、SDEF Support のドキュメントを参照してください。

buildSkeleton

スケルトンを読み込むかどうかを設定します。
デフォルト値は true です。

例えば、ステージを読み込む場合、スケルトンを作成する必要はないため、これを false に設定できます。スケルトンなしの MmdMesh は MMD ランタイムに登録できません

buildMorph

モーフを読み込むかどうかを設定します。
デフォルト値は true です。

例えば、ステージを読み込む場合、モーフを作成する必要はないため、これを false に設定できます。

boundingBoxMargin

バウンディングボックスのマージンを設定します。
デフォルト値は 10 です。

Babylon.js は、スケルトンによる変形が発生したときにバウンディングボックスを更新しません。バウンディングボックスは、明示的に BoundingInfoHelper を使用する場合にのみ更新されます。

したがって、MMD モデルにアニメーションが適用されると、バウンディングボックスとメッシュが一致しなくなり、カメラフラスタム内にあるメッシュがカリングされる可能性があります。 これを防ぐために、バウンディングボックスにマージンを設定することをお勧めします。

この値は、MMD アニメーションが MMD モデルを原点からどれだけ遠くに移動させるかに基づいて調整する必要があります。 MMD アニメーションが MMD モデルを原点から遠くに移動させる場合は、より大きな値を設定することをお勧めします。

例えば、ステージには動きがないため、boundingBoxMargin を 0 に設定しても問題ありません

MMD モデルメッシュの alwaysSelectAsActiveMesh プロパティが true に設定されている場合、そのメッシュには フラスタムカリングが適用されません。この場合も、boundingBoxMargin の値を設定する必要はありません。

alwaysSetSubMeshesBoundingInfo

常にサブメッシュにバウンディング情報を設定するかどうかを設定します。
デフォルト値は true です。

optimizeSubmeshes が false の場合

optimizeSubmeshesfalse に設定されている場合、このオプションは無視され、Mesh のすべての SubMesh BoundingInfo は常に Mesh の BoundingInfo と一致するように設定されます。

これは、MMD モデルのマテリアルのレンダリング順序を設定するためです。

MMD モデルは、マテリアルをレンダリングする際に常に同じ順序でレンダリングする必要があります。 サブメッシュがすべて独立した Mesh に分割されている場合、Mesh.alphaIndex を使用してレンダリング順序を設定できます。

ただし、1つの Mesh に複数の SubMesh が存在する場合、各 SubMesh の描画順序を通常の方法で設定することはできず、Babylon.js は各 SubMeshBoundingInfo に基づいてソートしてレンダリング順序を設定します。 これを解決するために、すべての SubMesh BoundingInfo は同じに設定されます。Babylon.js はこの場合、レンダリング順序をソートする際に安定ソートを使用するため、レンダリングは Mesh.subMeshes の順序で実行されます。

optimizeSubmeshes が true の場合

この場合、Mesh あたり 1つの SubMesh しか存在しないため、Mesh の BoundingInfo を SubMesh にコピーすることは意味がないように思えるかもしれません。 Mesh あたり 1つの SubMesh が存在する場合、Babylon.js は BoundingInfo を SubMesh に保存せず、SubMesh.getBoundingInfo() を呼び出す際に Mesh の BoundingInfo を返します。

ただし、scene.clearCachedVertexData() を実行して、既に GPU にアップロードされた VertexData を削除した場合、 SubMesh.getBoundingInfo() を呼び出すと、SubMeshMesh の BoundingInfo の代わりに undefined を返します

その理由は、SubMesh.getBoundingInfo()this.IsGlobal が実際とは異なり false を返すためです。これはバグです。

// https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Meshes/subMesh.ts#L230-L249
class SubMesh {
// ...

/**
* Returns true if this submesh covers the entire parent mesh
* @ignorenaming
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public get IsGlobal(): boolean {
return this.verticesStart === 0 && this.verticesCount === this._mesh.getTotalVertices() && this.indexStart === 0 && this.indexCount === this._mesh.getTotalIndices();
}

/**
* Returns the submesh BoundingInfo object
* @returns current bounding info (or mesh's one if the submesh is global)
*/
public getBoundingInfo(): BoundingInfo {
if (this.IsGlobal || this._mesh.hasThinInstances) {
return this._mesh.getBoundingInfo();
}

return this._boundingInfo;
}

// ...
}

このため、レンダリングプロセス中のソートが失敗しレンダリング中にエラーがスローされる可能性があります。

この問題は、MeshBoundingInfoSubMesh にコピーすることで解決されます。

preserveSerializationData

再シリアル化のためのデータを保存するかどうかを設定します。
デフォルト値は false です。

babylon-mmd で使用されない MMD モデル内のデータを保存するには、preserveSerializationDatatrue に設定する必要があります。 この場合、ボーンの tailPosition やマテリアルの英語名など、追加情報を保存できます。

PMX/PMD モデルを読み込んで BPMX に変換する場合は、BpmxConverter を使用して、このオプションを true に設定して、損失なく BPMX に変換する必要があります。

loggingEnabled

ロギングを有効にするかどうかを設定します。
デフォルト値は false です。

開発中はロギングを有効にすることをお勧めします。これは、無効な PMX/PMD ファイルを読み込む際の問題を診断するのに役立ちます。

この値が false の場合、ローダーは読み込みプロセス中に発生する問題に関する警告を出力しません

referenceFiles

参照ファイルのリストを設定します。
デフォルト値は [] です。

参照ファイルは MMD モデルのテクスチャを読み込むために使用されます。

optimizeSubmeshes

サブメッシュの最適化を有効にするかどうかを設定します。
デフォルト値は true です。

この値が false の場合、MMD モデルは 1つの Mesh に複数の SubMesh として読み込まれます。

例えば、MMD モデルに 3つのマテリアルがある場合、このモデルは 1つの Mesh に 3つの SubMesh として読み込まれ、MultiMaterial が使用され、各 SubMesh に別々の Material が割り当てられます。

// マテリアルに基づいて複数の SubMeshes で読み込まれた MMD モデル
Mesh1 {
subMeshes: [
SubMesh1,
SubMesh2,
SubMesh3
],
material: MultiMaterial {
materials: [
Material1,
Material2,
Material3
]
}
}

この値が true の場合、MMD モデルはマテリアルの数に基づいて 複数の Mesh に分割されます。Mesh には 1つの SubMesh しかありません

// マテリアルに基づいて複数の Meshes に分割された MMD モデル
Mesh1 {
children: [
Mesh2 {
subMeshes: [ SubMesh1 ],
material: Material1
},
Mesh3 {
subMeshes: [ SubMesh2 ],
material: Material2
},
Mesh4 {
subMeshes: [ SubMesh3 ],
material: Material3
}
]
}

この場合、1つのジオメトリを複数の部分に分割するプロセスで情報損失が発生する可能性があります

状況によっては、このオプションを false に設定する方が良いパフォーマンスを提供する場合があります。

optimizeSingleMaterialModel

シングルマテリアルモデルの最適化を有効にするかどうかを設定します。
デフォルト値は true です。

optimizeSubmeshes が true の場合、MMD モデルが単一のマテリアルを使用する場合でも、ルートメッシュの下に 1つのメッシュとして読み込まれます。 この場合、ジオメトリをルートメッシュに含めることで 1つの Mesh インスタンスに最適化することができ、この最適化は optimizeSingleMaterialModel が true の場合に適用されます。

// optimizeSingleMaterialModel: false, optimizeSubmeshes: true で読み込まれた 1つのマテリアルを使用する MMD モデル
Mesh1 {
children: [
Mesh2 {
subMeshes: [ SubMesh1 ],
material: Material1
}
]
}
// optimizeSingleMaterialModel: true, optimizeSubmeshes: true で読み込まれた 1つのマテリアルを使用する MMD モデル
Mesh1 {
subMeshes: [ SubMesh1 ]
}

optimizeSubmeshes が false の場合、このオプションは 無視されます。

さらに進む

babylon-mmd は、複数のユースケースをサポートするための 様々な読み込みオプションと、MMD の動作を再現するためのいくつかの機能を提供しています。

  • BMP テクスチャの読み込みの問題 - BMP テクスチャが正しく読み込まれない問題については、Fix BMP Texture Loader を参照してください。
  • モデル変形の問題 - モデルの変形が MMD と異なる問題については、SDEF Support を参照してください。
  • マテリアルビルダー - マテリアルビルダーに関する詳細情報については、Material Builder を参照してください。
  • MMD スタンダードマテリアル - MMD シェーダーを再現する MMD スタンダードマテリアルに関する詳細情報については、MMD Standard Material を参照してください。
  • BPMX - PMX/PMD ファイルの変換と最適化に関する詳細情報については、The Babylon PMX Format を参照してください。