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

PMX形式をBPMX形式に変換する

このセクションでは、PMXファイルをBPMXファイルに変換する方法について説明します。

PMXファイルをBPMXファイルに変換するには、以下の2つの方法があります:

  • ウェブアプリケーションを使用して変換する方法
  • プログラムを使用して変換する方法

それぞれの方法にはメリットとデメリットがありますので、ニーズに合わせて適切な方法を選択してください。

コンバーターアプリケーションを使用する

babylon-mmdPMX/PMDファイルをBPMXファイルに変換するためのウェブアプリケーションを提供しています。

以下のリンクからアプリケーションを使用できます。

PMX to BPMX コンバーター

コンバーターUI PMX to BPMX コンバーターのスクリーンショット。モデル:YYB Hatsune Miku_NT

  1. PMX/PMDファイルを含むフォルダをドラッグアンドドロップします。

    • MMDモデルを読み込むために必要なすべてのテクスチャファイルを含んでいる必要があります。
  2. ファイルリストから変換するPMX/PMDファイルを選択すると、右側のシーンにモデルが表示されます。

  3. 最適化オプションを設定します。各オプションは、変換したいモデルの特性に応じて異なる設定が可能です。

    • シリアライゼーションデータの保存: babylon-mmdが使用しないPMX/PMDファイルからのシリアライゼーションデータを保存します。
      • テクスチャパスマテリアル英語名表示枠などが含まれます。
      • これは内部的にloaderOptions.mmdmodel.preserveSerializationDataオプションによって制御されます。
    • サブメッシュの最適化: サブメッシュを個別のメッシュに分離するかどうかを設定します。
      • これは内部的にloaderOptions.mmdmodel.optimizeSubmeshesオプションによって制御されます。
    • スケルトンの構築: モデルのスケルトンデータを保存するかどうかを設定します。
      • ステージのような、スケルトンを必要としないモデルではこのオプションをオフにできます。
    • モーフターゲットの構築: モデルのモーフターゲットデータを保存するかどうかを設定します。
      • ステージのような、モーフターゲットを必要としないモデルではこのオプションをオフにできます。
  4. マテリアルのレンダリングの問題を修正します。

    • この手順はオプションです。モデルが正しくレンダリングされている場合は、このステップをスキップできます。
    • この作業については、下記のマテリアルレンダリング方法の修正セクションを参照してください。
  5. 変換を実行します。

備考

名前は「PMX to BPMX コンバーター」ですが、PMDファイルもサポートしています。

BPMXのダウンロード BPMX形式でダウンロードされた変換済みファイル。

マテリアルレンダリング方法の修正

BPMXファイルは、マテリアルのアルファ評価結果をフォーマット内に保存します。

具体的には、MmdMaterialRenderMethod.AlphaEvaluation または**MmdMaterialRenderMethod.DepthWriteAlphaBlendingWithEvaluation** メソッドに必要なアルファ評価結果をそれぞれ保存します。

この結果は後でモデルを読み込む際に使用され、アルファ評価ステップをスキップすることでモデル読み込みプロセスを高速化します。また、アルゴリズムの欠陥により正しくレンダリングされない要素を手動で修正し変換することも可能です。

Fix Materialタブでは、これらのアルファ評価結果を修正するためのUIを提供しています。

Fix Material UI Fix MaterialのAlpha Modeタブ

Alpha Modeは、MmdMaterialRenderMethod.AlphaEvaluation レンダリングメソッドでモデルがどのように見えるかを表示します。ここで、おかしく見えるマテリアルのレンダリング方法を変更できます。

YYB Hatsune Miku_NTモデルでは、B、B-L、sleeve05のレンダリング方法をAlpha Blendに変更すると、より良い結果が得られます。

Fix Material UI 2 Fix MaterialのForce Depth Write Modeタブ

Force Depth Write Modeは、MmdMaterialRenderMethod.DepthWriteAlphaBlendingWithEvaluation レンダリングメソッドでモデルがどのように見えるかを表示します。ここで、おかしく見えるマテリアルのレンダリング方法を変更できます。

YYB Hatsune Miku_NTモデルでは、sleeve05のレンダリング方法をAlpha Blendに変更すると、より良い結果が得られます。

プログラムによる変換

BPMX変換は、BpmxConverter によって実行されます。

BpmxConverter は**MmdMesh** を入力として受け取り、BPMX形式に変換します。

最も単純な使用例は次のとおりです:

const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.deleteTextureBufferAfterLoad = false; // 1
const assetContainer = await LoadAssetContainerAsync(
fileOrUrl,
scene,
{
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder,
loggingEnabled: true
}
}
}
);
const mmdMesh = assetContainer.meshes[0] as MmdMesh;

const bpmxConverter = new BpmxConverter();
bpmxConverter.loggingEnabled = true;
const arrayBuffer = bpmxConverter.convert(mmdMesh); // 2
assetContainer.dispose(); // 3
  1. デフォルトでは、マテリアルビルダーはテクスチャをGPUにアップロードした後にバッファを削除するように設定されています。ただし、これではテクスチャをシリアライズできなくなるため、マテリアルビルダーの**deleteTextureBufferAfterLoad** オプションをfalseに設定する必要があります。

  2. BpmxConverter.convert を使用して変換を実行します。この関数は、2番目のパラメータとしてオプションを取ることができます。

  3. assetContainer.dispose() を呼び出してリソースを解放します。assetContainer.addAllToScene() を使用した場合は、すべてのリソース(ジオメトリ、マテリアル、テクスチャ、モーフターゲットマネージャー、スケルトン)を手動で解放する必要があります。

ただし、上記の例では、アルファ評価結果がBPMXファイルに保存されていません。アルファ評価結果を保存するには、TextureAlphaChecker を使用して手動でアルファ評価結果を生成し、BpmxConverter に渡す必要があります。

これらすべてを行う例を以下に示します:

const settings = {
preserveSerializationData: true,
optimizeSubmeshes: true,
buildSkeleton: true,
buildMorph: true
};

const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.deleteTextureBufferAfterLoad = false;
materialBuilder.renderMethod = MmdMaterialRenderMethod.AlphaEvaluation;
materialBuilder.forceDisableAlphaEvaluation = true;

const textureAlphaChecker = new TextureAlphaChecker(scene);

const assetContainer = await LoadAssetContainerAsync(
fileOrUrl,
scene,
{
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder,
preserveSerializationData: settings.preserveSerializationData,
optimizeSubmeshes: settings.optimizeSubmeshes,
loggingEnabled: true
}
}
}
);
const mmdMesh = assetContainer.meshes[0] as MmdMesh;

const meshes = mmdMesh.metadata.meshes;
const materials = mmdMesh.metadata.materials;
const translucentMaterials: boolean[] = new Array(materials.length).fill(false);
const alphaEvaluateResults: number[] = new Array(materials.length).fill(-1);

for (let i = 0; i < materials.length; ++i) {
const material = materials[i] as MmdStandardMaterial;

// collect referenced meshes
const referencedMeshes: ReferencedMesh[] = [];
for (let meshIndex = 0; meshIndex < meshes.length; ++meshIndex) {
const mesh = meshes[meshIndex];
if ((mesh.material as MultiMaterial).subMaterials !== undefined) {
const subMaterials = (mesh.material as MultiMaterial).subMaterials;
for (let subMaterialIndex = 0; subMaterialIndex < subMaterials.length; ++subMaterialIndex) {
const subMaterial = subMaterials[subMaterialIndex];
if (subMaterial === material) {
referencedMeshes.push({
mesh,
subMeshIndex: subMaterialIndex
});
}
}
} else {
if (mesh.material === material) referencedMeshes.push(mesh);
}
}

const diffuseTexture = material.diffuseTexture;

// evaluate DepthWriteAlphaBlendingWithEvaluation renderMethod result manually
if (material.alpha < 1) {
translucentMaterials[i] = true;
} else if (!diffuseTexture) {
translucentMaterials[i] = false;
} else {
translucentMaterials[i] = true;
for (let referencedMeshIndex = 0; referencedMeshIndex < referencedMeshes.length; ++referencedMeshIndex) {
const referencedMesh = referencedMeshes[referencedMeshIndex];
let isOpaque = false;
if ((referencedMesh as { mesh: Mesh; subMeshIndex: number }).subMeshIndex !== undefined) {
const { mesh, subMeshIndex } = referencedMesh as { mesh: Mesh; subMeshIndex: number };
isOpaque = await textureAlphaChecker.hasFragmentsOnlyOpaqueOnGeometryAsync(diffuseTexture, mesh, subMeshIndex);
} else {
isOpaque = await textureAlphaChecker.hasFragmentsOnlyOpaqueOnGeometryAsync(diffuseTexture, referencedMesh as Mesh, null);
}
if (isOpaque) {
translucentMaterials[i] = false;
break;
}
}
}

// evaluate AlphaEvaluation renderMethod result manually
if (diffuseTexture !== null) {
let transparencyMode = Number.MIN_SAFE_INTEGER;
for (let i = 0; i < referencedMeshes.length; ++i) {
const referencedMesh = referencedMeshes[i];

const newTransparencyMode = await textureAlphaChecker.hasTranslucentFragmentsOnGeometryAsync(
diffuseTexture,
(referencedMesh as { mesh: Mesh })?.mesh ?? referencedMesh as Mesh,
(referencedMesh as { subMeshIndex: number })?.subMeshIndex !== undefined
? (referencedMesh as { subMeshIndex: number }).subMeshIndex
: null,
materialBuilder.alphaThreshold,
materialBuilder.alphaBlendThreshold
);

if (transparencyMode < newTransparencyMode) {
transparencyMode = newTransparencyMode;
}
}
alphaEvaluateResults[i] = transparencyMode !== Number.MIN_SAFE_INTEGER
? transparencyMode
: Material.MATERIAL_OPAQUE;
} else {
alphaEvaluateResults[i] = Material.MATERIAL_OPAQUE;
}
}

const bpmxConverter = new BpmxConverter();
bpmxConverter.loggingEnabled = true;
const arrayBuffer = bpmxConverter.convert(mmdMesh, {
buildSkeleton: settings.buildSkeleton,
buildMorph: settings.buildMorph,
translucentMaterials: translucentMaterials,
alphaEvaluateResults: alphaEvaluateResults
});
assetContainer.dispose();

必要に応じて再利用できるよう、これに関する関数を作成することができます。

より詳細な実装の詳細については、PMX to BPMX コンバーターのソースを参照してください

BpmxConverter APIはさまざまなオプションをサポートしており、すべての仕様に完全に準拠するコードを書くのは非常に難しいですが、必要な機能のみを選択的に使用することで、有用な結果を得ることができます。