PMXファイルの構造(バイト単位の仕様)

PMXは「ヘッダで型の幅を宣言してから、可変長のブロックを順に並べる」という形式です。長さフィールドを持たないブロックが多いため、1つでも読み違えるとそこから先が全部ずれます。実物のモデルを自前で歩いて書き換えるパーサを作ったときに、実際に引っかかった点まで含めてまとめます。

最終更新: 2026-09-01 · kphpリポジトリ同梱の実PMX 5本(アリシア2本・別モデル2本・ステージ1本、頂点4,669〜40,688)で走査を確認。等倍で焼き直すとバイト列が1バイトも変わらないことをテストで固定している

前提

  • 数値はリトルエンディアン。 整数はuint8/uint16/int32、実数はfloat32
  • 文字列は「バイト長(uint32) + 本体」。VMDと違って固定長ではありません。文字コードはヘッダの宣言(UTF-16LE か UTF-8)に従います。
  • インデックスの幅はモデルごとに違います。 1・2・4バイトのいずれかで、ヘッダに書かれています。ここを決め打ちすると、大きいモデルだけ壊れます。
  • ブロックの区切りはバイト数ではなく件数だけで示されます。読み飛ばすにも中身を正しく歩く必要があります。

ヘッダ

オフセット長さ内容
04ASCIIPMX (末尾は空白)
44float32バージョン。2.0 または 2.1
81uint8この後に続く設定バイトの数。実測した5本はすべて 8
91uint8文字コード。0 = UTF-16LE、1 = UTF-8
101uint8追加UVの数(0〜4)
111uint8頂点インデックスの幅(1/2/4)
121uint8テクスチャインデックスの幅
131uint8材質インデックスの幅
141uint8ボーンインデックスの幅
151uint8モーフインデックスの幅
161uint8剛体インデックスの幅

設定バイトの数(オフセット8)は8とは限らない前提で読むこと。 将来の拡張に備えて、8個読んだあと「宣言された長さ − 8」だけ読み飛ばすようにしておけば、増えても壊れません。

実測した5本の宣言はこうでした。頂点数より右の数字はインデックスの幅(バイト数)です。同じサイトに置いてあるモデル同士でも違います。

モデル頂点数頂点テクスチャ材質ボーンモーフ剛体
alicia.pmx22,311211211
ad_org8.pmx40,688211222
ad_ik2.pmx39,540211222
blue_stage_simple.pmx4,669211111

5本とも文字コードは 0(UTF-16LE)、追加UVは 0 でした。ヘッダの直後には「モデル名・英語名・コメント・英語コメント」の4つの文字列がこの順で並びます。

インデックスは符号の扱いが幅によって違います。 ボーン・材質などの参照は「参照なし」を −1 で表すため符号付きで読みます。一方、頂点インデックス(面とモーフが使う)は符号なしです。1バイトなら uint8/int8、2バイトなら uint16/int16、4バイトは常に int32 です。

頂点

「頂点数(uint32)+ 本体」。1件のレイアウトは次のとおりですが、ウェイトの種別によって長さが変わります

長さ内容
12float32 × 3位置
12float32 × 3法線
8float32 × 2UV
16 × 追加UV数float32 × 4 × n追加UV(ヘッダで0なら無し)
1uint8ウェイト種別(0〜4)
可変ボーンとウェイト(下表)
4float32エッジ倍率
種別名前中身長さ(ボーン幅 = B)
0BDEF1ボーン1本B
1BDEF2ボーン2本 + ウェイト1つ(残りは 1 − w)2B + 4
2BDEF4ボーン4本 + ウェイト4つ4B + 16
3SDEFBDEF2と同じ並び + C・R0・R1(float32 × 9)2B + 4 + 36
4QDEFBDEF4と同じ並び(PMX 2.1)4B + 16

実測した5本の内訳です。SDEFは実際に使われています(1本のモデルで3,132頂点)。QDEFは5本とも0件でした。

モデルBDEF1BDEF2BDEF4SDEF
alicia.pmx8,85810,1213,3320
ad_org8.pmx19,20016,8131,5433,132
ad_ik2.pmx18,99716,6491,4842,410
blue_stage_simple.pmx4,669000

SDEFを読み飛ばす実装にする場合も、36バイトぶんは必ず進めること。 ここを飛ばし忘れると、以降の頂点が全部ずれます。変形としてBDEF2扱いにするのは妥当な割り切りで(three.jsのMMDLoaderも同じ)、肩やひじの潰れ方が本家と厳密には一致しない、という差で済みます。

面とテクスチャ

  • : 「頂点インデックスの総数(uint32)+ 本体」。3つで1つの三角形なので、面数は総数 ÷ 3 です。1件の幅は頂点インデックスの幅(多くは2バイト)。
  • テクスチャ: 「件数(uint32)+ 文字列 × 件数」。中身はPMXからの相対パス(Texture\body.png のようにWindows流の区切り)です。

面は材質ごとに連続して並んでいます。 材質の情報には「自分が使う頂点インデックスの数」しか書かれておらず、開始位置は書かれていません。先頭から順に足していった累積が開始位置になります。この性質のおかげで、材質ごとの頂点を切り出すのは簡単です。

材質

長さ内容
可変文字列 × 2材質名・英語名
16float32 × 4拡散色(R, G, B, 不透明度)
16float32 × 3 + 1反射色 + 反射強度
12float32 × 3環境色
1uint8描画フラグ(両面・地面影・セルフ影・エッジ など)
16float32 × 4エッジ色
4float32エッジ幅
B_tex × 2indexテクスチャ / スフィアテクスチャ
1uint8スフィアモード(0 = 無効 / 1 = 乗算 / 2 = 加算 / 3 = サブテクスチャ)
1uint8共有トゥーンフラグ
可変index または uint8フラグが0ならテクスチャインデックス、1なら1バイトの共有トゥーン番号
可変文字列メモ
4int32この材質が使う頂点インデックスの数(3の倍数)

共有トゥーンフラグの分岐が最大の罠です。 ここを1バイト読み違えると、材質より後ろのブロック(ボーン・モーフ・剛体)が丸ごとずれます。「材質までは読めるのにボーンで落ちる」ときは、まずここを疑ってください。

材質は見た目のパーツの単位(髪・顔・スカート・籠手…)なので、「このモデルは何でできているのか」を読むにも、色や大きさを個別に変えるにも、この単位が合います。色を差し替えるときは拡散色のRGB 12バイトだけを書き換え、4つ目の不透明度は触らないこと — 半透明にしてある材質を勝手に不透明へ戻すと、髪の透け表現が壊れます。

ボーン

長さ内容
可変文字列 × 2ボーン名・英語名
12float32 × 3位置(モデル空間の絶対座標)
Bindex親ボーン(−1 で親なし)
4int32変形階層
2uint16フラグ(ビットマスク)
可変フラグに応じた追加ブロック(下表)
ビット意味続くもの
0x0001接続先がボーン指定ボーンインデックス(B)。立っていなければ代わりに座標オフセット 12バイト
0x0020IKIKブロック(下記)
0x0100 / 0x0200回転付与 / 移動付与付与親ボーン(B)+ 付与率(float32)
0x0400軸固定軸ベクトル 12バイト
0x0800ローカル軸X軸・Z軸 24バイト
0x2000外部親変形Key 4バイト

IKブロックは「IKターゲット(B)+ ループ回数(int32)+ 単位角(float32)+ リンク数(int32)」に続けて、リンクごとに「ボーン(B)+ 角度制限の有無(uint8)」が並び、制限ありのときだけ下限・上限の24バイトが入ります。

ボーンの位置は絶対座標で、回転は持ちません。 ローカルの位置は「自分の座標 − 親の座標」で求めます。だからボーンを動かす種類の編集をするときは、親から順に積み上げる形になります。

「親は必ず自分より前にある」は成り立ちません。 実物のモデル(ad_ik2.pmx)に、親の添字が自分より後ろのボーンが1本ありました。先頭から1回なめるだけの実装は、親の座標が未計算のまま参照して落ちます。トポロジカル順に並べ直してから処理してください。

もう1つ実物で分かったこと: 足IKボーンは足の子ではありません(親は「全ての親」)。脚の長さを変えても勝手には追従しないので、動かす先(足首)と同じだけずらす必要があります。「動かす先へ重ねる」ではいけません — つま先IKのように、IKボーンと動かす先が元から同じ位置に無いものがあります。

モーフ

「モーフ名・英語名 + パネル(uint8)+ 種別(uint8)+ 要素数(int32)+ 要素 × n」。種別によって1要素の長さが変わります。

種別内容1要素の長さ
0グループモーフindex + float32
1頂点頂点index + 移動量 12バイト
2ボーンボーンindex + 移動 12 + 回転 16
3UV頂点index + float32 × 4
4〜7追加UV1〜43と同じ並び
8材質材質index + 1 + 拡散16 + 反射12 + 強度4 + 環境12 + エッジ色16 + エッジ幅4 + テクスチャ16 + スフィア16 + トゥーン16
9フリップ(PMX 2.1)モーフindex + float32
10インパルス(PMX 2.1)剛体index + 1 + 12 + 12

種別4〜7(追加UVモーフ)を読み飛ばさない実装が実在します。 three.jsが内部で使うmmdparserがそれで、この種のモーフを持つモデルでは解析がずれます。自前で歩くなら、種別3と同じ並びとして進めれば問題ありません。実測した5本には4〜7は含まれていなかったので(内訳はグループ・頂点・ボーン・UV・材質のみ)、手元のモデルで再現しないからといって無いことにはできません。

表示枠・剛体・ジョイント

  • 表示枠: 「枠名・英語名 + 特殊枠フラグ(uint8)+ 要素数(int32)」に続けて、要素ごとに「対象(uint8)+ index」。対象が0ならボーン、1ならモーフなので、読むインデックスの幅が要素ごとに変わります。
  • 剛体: 名前2つ + 関連ボーン(B) + グループ(1) + 非衝突グループ(2) + 形状(1) + 大きさ12 + 位置12 + 回転12 + 質量・移動減衰・回転減衰・反発力・摩擦力(4 × 5) + 物理演算の種類(1)。
  • ジョイント: 名前2つ + 種類(1) + 剛体A・B(剛体index × 2) + 位置12 + 回転12 + 移動制限・回転制限・ばね(12 × 6)。

剛体の「物理演算の種類」は 0 = ボーン追従、1 = 物理演算、2 = 物理+ボーン位置合わせ です。ここが0以外のボーン(髪・スカート)は物理が毎フレーム動かすので、ポーズをVMDへ書き出すときは除外するのが正解です。実際に、素立ちのつもりで採ったポーズが63ボーンになり、この条件で絞ったら10ボーンになりました(残った10本は足IKが曲げている脚のボーンで、これは書き出してよいものです)。

PMX 2.1にはこの後にソフトボディのブロックが続きますが、実物で見かけたことがなく、このサイトでも読んでいません。2.0のファイルにはそもそも存在しません。

書き換えるときの作法

  • 「全部を構造体へ展開して書き戻す」をやらない。 走査時に書き換えたい場所のバイト位置だけを控えておき、そこだけ上書きするほうが安全です。読まなかったブロックや、float32の往復で下位ビットが変わる値を巻き込まずに済みます。
  • 等倍(何も変えない設定)で焼き直したらバイト列が完全一致する、をテストで固定する。 走査位置が1つでもずれていれば必ず落ちる、いちばん厳しい検査です。このとき「変化なしの箇所は書き戻さない」分岐が要ります — 法線の再正規化やfloat32の往復だけで下位ビットが動き、実際に全モデルが「差分あり」になりました。
  • 文字列を差し替えると長さが変わる。 モデル名を変えるだけでも後続のオフセットが全部ずれるので、位置を控えて上書きする方式のままでは足りません。前後を切って繋ぎ直す必要があります。