Trilibを使ってFBXファイルをHoloLensで読み込む
Unity EditorおよびHoloLens(UWP)上で、FBXファイルをスクリプトから読み込みたい。そういうとき、ありますよね。
そんなときは、こちらの「TriLib - Model loader package」を使います。
環境
- Unity 2018.3.14f1
- HoloToolkit-Unity 2017.4.3.0
- Visual Studio 2017 Version 15.9.13
使い方
基本的な使い方は開発者サイトで見られます。
しかし、UWP用には少し工夫が必要です。マニュアルには「windows store用アプリ(つまりUWP)で動作させるにはzipファイルとして読み込む必要がある」とあるのですが、これはfbxをzipに圧縮するという意味ではありません。
LoadFromMemoryWithTexturesメソッドにファイルのバイト配列を渡す際に、そのファイルの拡張子がzipだと伝えればOKです。
UWP用の変更はUnity Editor上でも問題なく動くので、#UNITY_UWPディレクティブなどは使わず次のように実装します。
using System.Collections; using System.Collections.Generic; using UnityEngine; using TriLib; using System.IO; using System; public class FbxLoader : MonoBehaviour { // Start is called before the first frame update void Start() { // FBXファイルはStreamingAssets以下に設置 loadFbx(Application.streamingAssetsPath + "/crystal.fbx"); } void loadFbx(string filePath) { // ファイルのバイト配列取得 byte[] fileBytes = File.ReadAllBytes(filePath); using (var assetLoader = new AssetLoaderAsync()) { try { // ロード時オプションを作成 var assetLoaderOptions = AssetLoaderOptions.CreateInstance(); // このオプションをtrueにしないとMixedRealityのプロジェクトが正しく動作しない assetLoaderOptions.UseOriginalPositionRotationAndScale = true; // バイト配列と拡張子がzipだと伝える var loadingThread = assetLoader.LoadFromMemoryWithTextures(fileBytes, "zip", assetLoaderOptions, null, delegate (GameObject loadedGameObject) { // ロード完了後に行われる処理 // ロードされたfbxのモデルはloadedGameObject割り当てられる Debug.Log("Load Finished"); }); } catch (Exception exception) { Debug.Log("exception"); } } } }
とりあえずUnityでこれを実行してみる。
サイズが気になりますが(後述)、とりあえず先にHoloLensで出してみましょう。
プロジェクトをUWPへ書き出すとき、次のようがエラーが出ます。
Assets\TriLib\TriLibExtras\Scripts\AvatarLoader.cs(397,40): error CS0619: 'HumanDescription' is obsolete: 'HumanDescription is not supported on .NET scripting backend.'
Assets\TriLib\TriLibExtras\Scripts\AvatarLoader.cs(408,31): error CS0619: 'AvatarBuilder.BuildHumanAvatar(GameObject, HumanDescription)' is obsolete: 'BuildHumanAvatar is not supported on .NET scripting backend.'
ヒューマンまわりのエラーが出てます。今回はヒューマンモデルは対象外として、TrilibExtra以下のファイルも使わないのでフォルダごと削除してしまいましょう。
HoloLensで出してみました。
サイズ表示の問題、対策
基本的な使い方は以上です。ここからはサイズの問題について見ていきます。
上記の実行結果は想定していたよりも相当大きいサイズで表示されていました。これはtrilibではFBXファイル内の1単位(インチだったりメートルだったり)の長さを見ておらず、unityのスケール設定(メートル)を適用していることが原因です。
対策は2つ考えられます。
対策① モデルのスケール設定をメートルにしてから出力する
ファイルのほうをプロジェクトに合わせます。モデルの1単位当たりの大きさをメートルに設定してからfbxに出力すればOKです。
3ds Maxでは、メニューからCustomize/Units Setup.../System Unit Setupと進んでいき、System Unit Scaleの値を「1Unit = 1.0 meters」にすればOKです。
対策② FBXをascii形式で出力してUnits設定を取得する
FBXにはascii形式とbinary形式があります。ascii形式の場合その中身は文字列なので、そこから1単位当たりの大きさの値を取ってくることができます。
取ってきた値をロードされたgameObjectに適用すればOKです。コードとしては以下になります。
using System.Collections; using System.Collections.Generic; using UnityEngine; using TriLib; using System.IO; using System; public class FbxLoader : MonoBehaviour { // Start is called before the first frame update void Start() { loadFbx(Application.streamingAssetsPath + "/crystal_ascii.fbx"); } void loadFbx(string filePath) { byte[] fileBytes = File.ReadAllBytes(filePath); float scaleFactor = 1f; // binaryかどうかの判定 // (参考)https://www.slideshare.net/L1048576/fbx-1-1 if (fileBytes[21] == 0x1a & fileBytes[22] == 0x00) { Debug.Log("binary"); } else { Debug.Log("ascii"); string asciiText = System.Text.Encoding.ASCII.GetString(fileBytes); foreach (string line in asciiText.Split('\n')) { if (line.Contains("UnitScaleFactor")) { string[] tips = line.Split(','); scaleFactor = float.Parse(tips[tips.Length - 1]) / 100f; break; } } } using (var assetLoader = new AssetLoaderAsync()) { try { var assetLoaderOptions = AssetLoaderOptions.CreateInstance(); assetLoaderOptions.UseOriginalPositionRotationAndScale = true; var loadingThread = assetLoader.LoadFromMemoryWithTextures(fileBytes, "zip", assetLoaderOptions, null, delegate (GameObject loadedGameObject) { // ロード完了後に行われる処理 // ロードされたfbxのモデルはloadedGameObject割り当てられる Debug.Log("Load Finished"); // モデルにサイズを適用 loadedGameObject.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor); }); } catch (Exception exception) { Debug.Log("exception"); } } } }
ascii形式のFBXはサイズが大きくなりがちなので、どちらの方法を選択するかは検討の余地があるかと思います。