本記事では、Rustという安全性と高速性を兼ね備えた言語を使って、VRMファイルを読み込み、その内部構造を解析してみた手順を紹介します
環境準備
今回使用する環境は以下の通りです:
- Rust 1.70以上
- 必要なクレート:
-
gltf
- glTF/GLB形式のパーサ(VRMはglTFの拡張形式) -
serde_json
- JSON操作用
-
まずは新しいRustプロジェクトを作成し、必要なクレート(ライブラリ)を追加します:
# 新しいプロジェクトの作成
cargo new vrm_parser
cd vrm_parser
# 必要なクレートの追加
cargo add gltf
cargo add serde_json
VRMファイルとは
VRM(Virtual Reality Model)は、humanoid型の3Dアバターデータを扱うためのファイルフォーマットです。基本的には、glTF 2.0という3Dモデル用の標準フォーマットを拡張したもので、アバターの表情や物理演算、利用許諾情報などが含まれています。
VRMは内部的にglTF(あるいはバイナリ形式のGLB)として構成されており、拡張情報が含まれています。そのため、まずはglTFのパーサを使ってVRMファイルを読み込む必要があります。
コードの全体像
以下が今回実装するVRMファイル解析プログラムの全体像です:
mod vrm_loader;
use std::{fs, path::Path};
use gltf::Gltf;
use serde_json::{Map, Value};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// VRMファイルのパスを指定
let vrm_file_path = Path::new("models/sample_avatar.vrm");
// ファイルをバイナリとして読み込み
let vrm_data = fs::read(vrm_file_path)?;
// gltfクレートを使ってVRMファイルを解析
let gltf_content = Gltf::from_slice(&vrm_data)?;
let document = gltf_content.document;
// JSON形式に変換して中身を調べる
let gltf_json = &document.clone().into_json();
println!("{:?}", &gltf_json);
// 基本情報の表示
println!("VRM file loaded successfully!");
println!("Assets: {:?}", &gltf_json.asset);
println!("Scenes: {:?}", &gltf_json.scenes);
println!("Node Count: {:?}", &gltf_json.nodes.len());
println!("Mesh Count: {:?}", &gltf_json.meshes.len());
println!("Material Count: {:?}", &gltf_json.materials.len());
println!("Texture Count: {:?}", &gltf_json.textures.len());
println!("Image Count: {:?}", &gltf_json.images.len());
println!("Animation Count: {:?}", &gltf_json.animations.len());
println!("Skin Count: {:?}", &gltf_json.skins.len());
println!("Camera Count: {:?}", &gltf_json.cameras.len());
// 拡張情報の取得と表示
let extension = document.extensions() as Option<&Map<String, Value>>;
for (key, _) in extension.unwrap_or(&Map::new()) {
println!("Extension Key: {:?}", key);
}
// VRM拡張情報の詳細取得
if let Some(vrm_extension) = document.extension_value("VRM") as Option<&Value> {
if let Some(meta) = vrm_extension.get("meta") {
if let Some(title) = meta.get("title") {
println!("VRM Title: {:?}", title);
}
if let Some(version) = meta.get("version") {
println!("VRM Version: {:?}", version);
}
if let Some(author) = meta.get("author") {
println!("VRM Author: {:?}", author);
}
if let Some(spec) = meta.get("specVersion") {
println!("VRM Spec Version: {:?}", spec);
}
} else {
println!("No VRM meta information found.");
}
} else {
println!("No VRM extension found.");
}
Ok(())
}
ここでは、VRMファイルを読み込み、その内部構造を解析して、基本情報やVRM特有の拡張情報を表示しています。それでは、このコードを段階的に解説していきましょう。
各部分の詳細解説
1. ファイルの読み込み
let vrm_file_path = Path::new("models/ichigofrog_0314.vrm");
let vrm_data = fs::read(vrm_file_path)?;
この部分では、Rustの標準ライブラリであるstd::fs
を使ってVRMファイルをバイナリデータとして読み込んでいます。Path::new()
で指定したパスにあるファイルを、fs::read()
関数で読み込み、結果はバイト配列(Vec<u8>
)としてvrm_data
に格納されます。
?
演算子は、エラーが発生した場合にそのエラーを呼び出し元に伝播させるRustの機能です。例えばファイルが存在しない場合などにエラーが発生します。
2. gltfクレートを使った解析
let gltf_content = Gltf::from_slice(&vrm_data)?;
let document = gltf_content.document;
読み込んだバイナリデータをgltf
クレートのGltf::from_slice()
関数に渡して解析します。VRMはglTFのサブセットなので、glTFのパーサで解析できます。gltf_content
からはdocument
プロパティを取得して、ドキュメントのメインコンテンツにアクセスします。
3. 基本情報の表示
let gltf_json = &document.clone().into_json();
println!("{:?}", &gltf_json);
println!("VRM file loaded successfully!");
println!("Assets: {:?}", &gltf_json.asset);
println!("Scenes: {:?}", &gltf_json.scenes);
// 他の情報表示...
ここでは、document
をJSON形式に変換して、様々な基本情報を表示しています。glTF形式には以下のような要素が含まれています:
- asset: バージョンなどのglTFアセット情報
- scene: シーンの構造
- nodes: 3Dオブジェクトのノード(階層構造)
- meshes: 3Dメッシュデータ
- materials: マテリアル(テクスチャの割り当て方)
- textures: テクスチャ情報
- images: 画像データ
- animations: アニメーション情報
- skins: スキニング情報(ボーンによる変形)
- cameras: カメラ情報
これらの要素を表示することで、モデルの基本的な構成を把握できます。
4. 拡張情報の探索
let extension = document.extensions() as Option<&Map<String, Value>>;
for (key, _) in extension.unwrap_or(&Map::new()) {
println!("Extension Key: {:?}", key);
}
glTFフォーマットは拡張可能な設計になっており、VRMはその拡張の一つです。ここではどのような拡張が含まれているかをチェックしています。VRMファイルの場合、"VRM"
という拡張キーが存在するはずです。
unwrap_or()
メソッドは、拡張情報が存在しない場合に空のマップを返す安全な方法です。
5. VRM特有の情報取得
if let Some(vrm_extension) = document.extension_value("VRM") as Option<&Value> {
if let Some(meta) = vrm_extension.get("meta") {
// タイトル、バージョン、作者などの取得
// ...
} else {
println!("No VRM meta information found.");
}
} else {
println!("No VRM extension found.");
}
ここでは、VRM拡張情報から「メタデータ」を取得し、モデルのタイトル、バージョン、作者などの情報を表示しています。
Rustのif let
構文は、パターンマッチングの一種で、「値がある場合のみ処理を行う」という場合に便利です。これにより、拡張情報がない場合や、必要なメタデータがない場合にも適切に対応できます。
実行結果の例
以下は、VRMファイルを解析した場合の出力例です:
VRM file loaded successfully!
Assets: Asset { version: "2.0", generator: Some("VRM Exporter"), min_version: None, copyright: None, extensions: None, extras: None }
Scenes: [Scene { name: Some("Scene"), nodes: [0, 1], extensions: None, extras: None }]
Node Count: 35
Mesh Count: 1
Material Count: 3
Texture Count: 6
Image Count: 6
Animation Count: 0
Skin Count: 1
Camera Count: 0
Extension Key: "VRM"
VRM Title: "サンプルアバター"
VRM Version: "1.0"
VRM Author: "サンプル作者"
VRM Spec Version: "0.0"
このような情報を取得できれば、VRMモデルの基本的な情報を把握できたことになります。
発展的な活用方法
今回はVRMファイルの基本情報を取得するだけでしたが、このコードを拡張することで以下のようなことにも挑戦してみたいです:
- モデルの構造解析: ボーン(スケルトン)の階層構造を解析して表示
- メッシュデータの取得: 頂点や面のデータを取得して3D描画や物理演算に利用
- 表情(BlendShape)の抽出: VRMの特徴である表情データを取得
- VRMのバージョン変換: VRM 0.xからVRM 1.0への変換ツール