MagicaVoxel のモデルデータを Unity に持っていって WebGL 出力する

2019-08-16

これは何

趣味開発で Unity の WebGL 出力をやった時の作業ログ。

このサイトを作った時に、なんか小綺麗な画像をトップに置こうと思って以下のボクセルアートを作った:

これは MagicaVoxel というソフトを使って作っているのだが、 唐突にこの 3D モデルデータをブラウザ上で描画して回転したい欲求に駆られた。

勉強を兼ねて素朴に three.js などを使って JS で実装しても良かったが、とりあえず手っ取り早そうな Unity で試すことにした。

MagicaVoxel から obj データをエクスポートする

MagicaVoxel は v0.99 (2017-11) で World Editor という機能が追加され、複数オブジェクトを配置できるようになっていた。 僕が作ったモデルもまず 16 x 16 x 16 でボクセルを作り、それを World Editor でコピーする形で作っていた。

16 x 16 x 16 のブロックを World 空間に複数配置

16 x 16 x 16 のブロックを World 空間に複数配置

MagicaVoxel ではブロック単位でファイルをエクスポートするようだ。World 全体を 1 ファイルにまとめてはくれない。 試しに OBJ 形式でエクスポートしてみたら、 obj / png / mtl ファイルが 132 個ずつ出力された ので、 このままではプログラムで扱えないな… となった。

他のツールで結合する?

まあ Blender を経由してエクスポートし直せばよいかな? と思ったけど Blender に 132 個の obj ファイルをインポートするのも標準では UI が無くて面倒だった。

一応以下のような Python を書いて実行すれば自動化できたが、 なんか単純なことをするだけなのにすごく遠回りしている気持ちになってきた。 (yak shaving というやつだ):

import os
import bpy

obj_dir = '/path/to/dir'
files = sorted(os.listdir(obj_dir))
obj_files = [item for item in files if item.endswith('.obj')]

for item in obj_files:
    file_path = os.path.join(obj_dir, item)
    bpy.ops.import_scene.obj(filepath = file_path)

MagicaVoxel 側で結合できることに気づいた

この辺りで、そもそも MagicaVoxel 側で 1 オブジェクトに結合できるという事実に気づいた。 公式のドキュメントに U : Union combine objects into one object とあった。

全選択して U キーを押してみたら確かに結合された。 が、なんかフチがハゲた。手前の地面が緑色になっている。なんだこれは…?

結合したらフチがハゲた

結合したらフチがハゲた

最初は意味が分からなかったが、よく観察すると結合時に微妙にシュリンクしたために、 フチが削られてこのような見た目になってしまったようだ。 どうも 1 ブロックは 126 x 126 x 126 が上限、というのが MagicaVoxel の仕様らしい。 なぜ 128 でないのかはよく分からない。 128 ならぴったり収まったのに。

まあ仕様には逆らえないので、仕方なく 5 ブロックに分けて結合してからエクスポートした。 5 ファイルくらいなら手で扱ってもバチは当たらないだろう。

まあもともと MagicaVoxel はゲームプログラム向きのデータを吐くためのツールではないと思うので、 「ミニチュアを作ってレンダリングして眺める」くらいの用途に使っておくのが無難だな、という学びを得た。

とは言え Blender を経由して fbx でエクスポートし直す

何にしても色々と調整が効くので、Unity に持っていく前に一度 Blender は通す。 Blender は最近正式リリースされた v2.80 (2019-07) を使う。 Blender は 「無料で使えて多機能だけどなんか操作体系が独特」 という噂をよく聞くソフトだったが、 v2.80 で UI が一新されて生まれ変わったようで、注目を集めている。 Unreal も Blender 推しだったり、アニメ制作のカラーが Blender に移行していたりと、波が来ているようだ。

プログラマの自分としても、趣味開発で「よし Maya を買おう」とはならないので、Blender の存在はありがたい。 いいタイミングなので、真面目に使い方を勉強しても良いかもしれない。

ちなみに最初に触った時は まずカメラの回転の仕方が分からなくて立ち止まった。 Unity / Unrel / MagicaVoxel などのノリで右クリックしてもコンテキストメニューが表示されるだけで、画面は動かない。 デフォルトだとカメラは マウス中ボタン に設定されている玄人仕様だった。

  • ちなみに中ボタンは Edit → Preference の Input の項目で Emulate 3 Button Mouse にチェックを入れれば option + 左クリック で代用できる
  • (この設定、デフォルトでも良い気がするが)
Blender で読み込んだ様子

Blender で読み込んだ様子

MagicaVoxel のデータは色を分けて描いた模様の部分を(テクスチャではなく)ポリゴンを割って表現するので、 見た目のわりに頂点数が多くなってしまう。今回のデータで 8350 ポリゴンあった。 Blender 上で頂点数を削ることはできるが、今回はいったんこのまま使ってみる。

エクスポート時の設定

fbx でエクスポートするとき、デフォルトでは Blender 上に配置された Light や Camera の情報もデータに含まれてしまうようだ。 このデータを Unity にインポートすると変な見た目になってしまい、 Unity 上からアセット内の Light / Camera を削除することもできなくて困ったので、 エクスポートの時点で余分なデータが含まれないようにしておく必要がある。

以下の Mesh だけ有効になった状態でエクスポートすれば問題なかった:

Mesh だけエクスポートする

Mesh だけエクスポートする

Unity にインポートしてライトとカメラを調整する

Blender から吐いた fbx と、MagicaVoxel が吐いたテクスチャファイル (png) を雑に Unity に放り込んだらちゃんとインポートされて色もついた。 この辺 Unity は楽でよい。

Unity に持ってきてしまえばこっちのもの

Unity に持ってきてしまえばこっちのもの

あとはライトを調整したり、Unity くささを減らすシンプルな Skybox のシェーダを書いたり、 マウスでカメラを操作する C# を書いたり、ライトマップを焼いたりして体裁を整える。

Web Assembly 形式で WebGL 出力する

ブラットフォームを WebGL に切り替えてビルドする。 出力されたファイルをサイトに配置してリソースのパスや css や HTML を自分のサイト用によしなに整える。 できたのがこれだ:

※ ロード時のプログレスバーがなんかうまく動かなかった。 DOM で表示されている部分なので、たぶんサイトの CSS と何かが競合しちゃってる系だと思うが、 ちょっと調べてよく分からなかったので諦めた。

以前の Unity は WebGL 版を JS に変換して出力していたが、2018.2 から Web Assembly 形式(以下 wasm)が標準となったようだ。 wasm は比較的モダンなブラウザでしか動かないが、JS と比較してサイズが軽くパフォーマンスが良い。

従来の JS 形式で WebGL 出力する

以前は Publishing Settings の Linker Target から wasm か asm.js かを選べたのだが、 今回使用した 2019.2 では そもそも Linker Target の項目が UI 上から消えていた。 最新版の公式ドキュメント を確認したところ、どうやら 2019.1 から消えたようだ。

  • Removed asm.js linker target in Unity 2019.1

wasm は比較的新しい技術なので、比較的新しい環境でしか動かない。 自分が見た範囲では、2018 年頃に買った手頃な Android 端末(Android 8.0.0)では動作したが、 2017 年末に買った iPad Pro(iOS 11.4.1)では動作しなかった。

まあ asm.js 版はメモリも食うしこれからは wasm で行きたいということなのだろうが、 UI から消してしまうとは前向きというか、未来志向なのだな…と思った。(何か事情があるのかもしれないが)

コードから Linker Target を変更する

とは言え、最新の Unity から JS 版 WenGL をビルドしたいこともあるだろう。 少し調べると、UI からは消えているが設定自体はまだ生きているらしく、エディタスクリプトから指定してやれば動くらしい。

Assets/Editor/EnableThreads.cs というファイルを作って以下を記述する:

using UnityEditor;
using UnityEngine;

[InitializeOnLoad]
class EnableThreads
{
    static EnableThreads()
    {
        PlayerSettings.WebGL.linkerTarget = WebGLLinkerTarget.Asm;
        PlayerSettings.WebGL.memorySize = 64;
    }
}

このファイルがある状態でビルドしてやると、asm.js 版の WebGL ビルドが作れた。

  • なお、memorySize = 32 ではメモリが足りなくて起動しなかった。 64 なら動いた

できたのがこれだ:

これは iPad Pro でも動作した。 (iPad では Full Screen が機能しなかったり、モバイルではタッチ時の挙動がなんか怪しいがご愛嬌)

所感

単にデータをエクスポート / インポートしてビルドするだけだろうと思っていたけど、何だかんだ手間がかかった。 しかしまあ実際に手を動かすとそれなりに得られるものがある。

それにブラウザ上で絵を動かすのはやはり楽しいものだ。 僕はブラウザ環境というものをそんなに信用してはいないのだが、 ちょっとした実験作やミニゲームを作るには良い舞台だと思う。