0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustでVRMファイルをパースしてみた

Posted at

本記事では、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ファイルの基本情報を取得するだけでしたが、このコードを拡張することで以下のようなことにも挑戦してみたいです:

  1. モデルの構造解析: ボーン(スケルトン)の階層構造を解析して表示
  2. メッシュデータの取得: 頂点や面のデータを取得して3D描画や物理演算に利用
  3. 表情(BlendShape)の抽出: VRMの特徴である表情データを取得
  4. VRMのバージョン変換: VRM 0.xからVRM 1.0への変換ツール
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?