TriLibからTriLib2への移行【3Dモデルインポーター】

#TriLib2
TriLibはUnityで使用できる、クロスプラットフォームのランタイム3Dモデルインポーターです。
2020年10月にLegacyである1.9から2.0にメジャーバージョンアップが行われました。
この記事は、TriLib2理解促進のため製作者のサイトの「基本的な使用方法」と「TriLibからTriLib2への移行」を日本語にしたものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/995e90e6-8c5a-670e-da7d-d2cf6f7de588.png)
製作者のサイトは[こちら](https://ricardoreis.net/trilib-2/)
AppStoreは[こちら](https://assetstore.unity.com/packages/tools/modeling/trilib-2-model-loading-package-157548)
APIドキュメントは[こちら](https://ricardoreis.net/trilib/trilib2/docs/index.html)

##機能
- FBX, OBJ, GLTF2, STL, PLY, 3MF, ZIP ファイルをサポート
- クロスプラットフォームサポート：Windows,Mac,Linux,UWP,Android,WebGL,iOS
- モデルのインポート（ファイルシステムから、Web上のURLから、カスタムソースから）
- メッシュインポート
- アニメーションインポート
- マテリアルインポート
- テクスチャインポート
- アバター作成
- Legacy-to-Humanoid アニメーションリターゲティング
- 高度なカスタマイズ
- C＃で書かれているので、ネイティブ・プラグインの依存関係なし
- コメント付きのソースコード
- さまざまなサンプル
- 完全なPBRモデルビューア

##パッケージ
パッケージはコアとなるTriLibCoreがあり、それぞれオプションも付属しています。
**TriLibCore**：TriLibコア機能
**TriLibSamples**：TriLibサンプル（オプション）
**TriLibStandaloneFileBrowser**：ファイルブラウザーモデル読み込みクラス（オプション）
**TriLibUniversalRP**：UniversalRP Render Pipelineクラス（オプション）
**TriLibHDRP**：HDRP Render Pipelineクラス（オプション）
**TriLibGltfDraco**：GLTF Draco Decoderクラス（オプション）

#基本的な使用法（モデルのロード）
TriLibでモデルとその依存関係をロードする主な方法は3つあります。

- ローカルファイルシステムのロード
- URLの読み込み
- カスタムソースの読み込み

CoreとなるロードメソッドがStreamをデータ入力として受け入れ、テクスチャのロードに使用されるMappersオブジェクト、およびカスタムデータストリームからの外部データを受け入れるため、ユーザーは他のロード機能を適応させることができます。

#ローカルファイルからのロード

TriLibに付属のStandaloneFileBrowserクラスを使用すると、ローカルファイルシステムからの簡単にモデルを読み込むことができます。

例：空のUnityプロジェクトを作成し、TriLibUnityパッケージをインポート。
※StandaloneFileBrowserフォルダーを含めてください。

「TestLoader.cs」としてnew scriptを作成します。このスクリプトをSceneのMain Cameraに割り当てます。

```C#:TestLoader.cs
using TriLibCore;
using TriLibCore.General;
using UnityEngine;

public class TestLoader : MonoBehaviour
{
    // ユーザーがGUIボタンをクリックして新しいモデルをロードできるようにします。
    private void OnGUI()
    {
        // モデルの読み込みプロセスを開始するためのボタンを表示します。
        if (GUILayout.Button("Load Model from File"))
        {
            // AssetLoaderOptionsインスタンスを作成します。
            // AssetLoaderOptionsは、ロードプロセスの多くの側面を構成するために使用されるクラスです。
            // 今回はデフォルト設定を変更しないので、インスタンスをそのまま使用できます。
            var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();

            // AssetLoaderFilePickerインスタンスを作成します。
            // AssetLoaderFilePickerは、ユーザーがローカルファイルシステムからモデルを選択できるようにするクラスです。
            var assetLoaderFilePicker = AssetLoaderFilePicker.CreateInstance();

            // モデル選択ファイルピッカーを表示します。
            assetLoaderFilePicker.LoadModelFromFilePickerAsync("Select a File", OnLoad, OnMaterialsLoad, OnProgress, OnBeginLoad, OnError, null, assetLoaderOptions);
        }
    }

    // このイベントは、モデルがロードされようとしているときに呼び出されます。
    // このイベントを使用して、スレッドのサポートなしでプラットフォームにロード画面を表示するなど、ロードの準備を行うことができます。
    // このイベントは、ファイルピッカーダイアログでファイルが選択されているかどうかを示すブール値を受け取ります。
    private void OnBeginLoad(bool anyModelSelected)
    {

    }

    // このイベントは、モデルの読み込みの進行状況が変更されたときに呼び出されます。
    // このイベントを使用して、たとえば、読み込みの進行状況バーを更新できます。
    // 「progress」値は正規化されたfloatとして提供されます（0から1になります）。
    // UWPやWebGLなどのプラットフォームは、スレッドを使用しないため、現時点ではこのメソッドを呼び出しません。
    private void OnProgress(AssetLoaderContext assetLoaderContext, float progress)
    {

    }

    // このイベントは、モデルの読み込み中に重大なエラーが発生したときに呼び出されます。
    // これを使用して、ユーザーにメッセージを表示できます。
    private void OnError(IContextualizedError contextualizedError)
    {

    }

    // このイベントは、すべてのモデルのGame Objectとメッシュが読み込まれたときに呼び出されます。
    // この段階ではまだマテリアルとテクスチャの処理が行われている可能性があります。
    private void OnLoad(AssetLoaderContext assetLoaderContext)
    {
        // ルートにロードされたGameObjectは、"assetLoaderContext.RootGameObject" フィールドに割り当てられます。
        //すべてのマテリアルとテクスチャがロードされたときにのみGameObjectが表示されるようにしたい場合は、このステップで無効にすることができます。
        var myLoadedGameObject = assetLoaderContext.RootGameObject;
        myLoadedGameObject.SetActive(false);
    }

    // このイベントは、すべてのマテリアルとテクスチャがロードされたときにOnLoadの後に呼び出されます。
    // このイベントは、重大な読み込みエラーの後にも呼び出されるため、必要なリソースをクリーンアップできます。
    private void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
    {
        // ルートにロードされたGameObjectは、"assetLoaderContext.RootGameObject"フィールドに割り当てられます。
        // 必要に応じて、このステップでGameObjectを再び表示できます。
        var myLoadedGameObject = assetLoaderContext.RootGameObject;
        myLoadedGameObject.SetActive(true);
    }
}
```

#URLからのロード
AssetDownloaderクラスを使用して、URL（ネットワーク）からモデルをロードできます。使用法は比較的単純で、ローカルファイルシステムのロードに似ています。
先ほどと同じく、StandaloneFileBrowserフォルダを含め、TestLoader.csを作成します。

```C#:TestLoader.cs
using TriLibCore;
using TriLibCore.General;
using UnityEngine;

public class TestLoader : MonoBehaviour
{
    // ユーザーがGUIボタンをクリックして新しいモデルをロードできるようにします。
    private void OnGUI()
    {
        // モデルの読み込みプロセスを開始するためのボタンを表示します。
        if (GUILayout.Button("Load Model from URL"))
        {
            // AssetLoaderOptionsインスタンスを作成します。
            // AssetLoaderOptionsは、ロードプロセスの多くの側面を構成するために使用されるクラスです。
            // 今回はデフォルト設定を変更しないので、インスタンスをそのまま使用できます。
            var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();

            // Webリクエストを作成します。
            //  web-requestには、モデルのダウンロード方法に関する情報が含まれています。
            // TriLibWebサイトから003_visemes.zipモデルをダウンロード
            var webRequest = AssetDownloader.CreateWebRequest("https://ricardoreis.net/trilib/demos/avatars/003/003_visemes.zip");

            // モデルのダウンロードを開始します。
            AssetDownloader.LoadModelFromUri(webRequest, OnLoad, OnMaterialsLoad, OnProgress, OnError, null, assetLoaderOptions);
        }
    }

    // このイベントは、モデルの読み込みの進行状況が変更されたときに呼び出されます。
    // このイベントを使用して、たとえば、読み込みの進行状況バーを更新できます。
    // 「progress」値は正規化されたfloatとして提供されます（0から1になります）。
    // UWPやWebGLなどのプラットフォームは、スレッドを使用しないため、現時点ではこのメソッドを呼び出しません。
    private void OnProgress(AssetLoaderContext assetLoaderContext, float progress)
    {

    }

    // このイベントは、モデルの読み込み中に重大なエラーが発生したときに呼び出されます。
    // これを使用して、ユーザーにメッセージを表示できます。
    private void OnError(IContextualizedError contextualizedError)
    {

    }

    // このイベントは、すべてのモデルのGameObjectとメッシュが読み込まれたときに呼び出されます。
    // の段階ではまだマテリアルとテクスチャの処理が行われている可能性があります。
    private void OnLoad(AssetLoaderContext assetLoaderContext)
    {
        // ルートにロードされたGameObjectは、"assetLoaderContext.RootGameObject"フィールドに割り当てられます。
        // すべてのマテリアルとテクスチャがロードされたときにのみGameObjectが表示されるようにしたい場合は、このステップで無効にすることができます。
        var myLoadedGameObject = assetLoaderContext.RootGameObject;
        myLoadedGameObject.SetActive(false);
    }

    // このイベントは、すべてのマテリアルとテクスチャがロードされたときにOnLoadの後に呼び出されます。
    // このイベントは、重大な読み込みエラーの後にも呼び出されるため、必要なリソースをクリーンアップできます。
    private void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
    {
        // ルートにロードされたGameObjectは、"assetLoaderContext.RootGameObject"フィールドに割り当てられます。
        // 必要に応じて、このステップでGameObjectを再び表示できます。
        var myLoadedGameObject = assetLoaderContext.RootGameObject;
        myLoadedGameObject.SetActive(true);
    }
}
```

#カスタムソースのロード
上記の方法に加えて、ストリームから読み取ることができる任意のデータソースからモデルをロードできます。
基本メソッド**LoadModelFromFileAsync**および**LoadModelFromStreamAsync**を使用すると、モデルをロードするために、それぞれパスまたはカスタムストリームを渡すことができます。

###パスの使用

```C#:パスの使用
var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
AssetLoader.LoadModelFromFile("PATH_TO_MY_MODEL.FBX", OnLoad, OnMaterialsLoad, OnProgress, OnError, null, assetLoaderOptions);
```

###ストリームの使用
ストリームの読み込みを使用する場合は、カスタムデータソースから外部モデルの依存関係とテクスチャも読み込むことができます。これを行うには、**TextureMapper**と**ExternalDataMapper**を継承するクラスを作成して、データのロード方法を指定する必要があります。

```C#:ストリームの使用
var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance<ExternalDataMapperSample>();
assetLoaderOptions.TextureMapper = ScriptableObject.CreateInstance<TextureMapperSample>();
var modelPath = "PATH_TO_MY_MODEL.FBX";
AssetLoader.LoadModelFromStream(File.OpenRead(modelPath), modelPath, null, OnLoad, OnMaterialsLoad, OnProgress, OnError, null, assetLoaderOptions);
```

上記のコードで使用されているMappersは次のとおりです。1つは外部リソースファイルからSteamを作成し、もう1つはファイルからTextureLoadingContextsを作成します。

```C#:外部リソースファイルからSteamを作成
public class ExternalDataMapperSample : ExternalDataMapper
{
	public override Stream Map(AssetLoaderContext assetLoaderContext, string originalFilename, out string finalPath)
	{
		finalPath = $"{assetLoaderContext.BasePath}/{FileUtils.GetFilename(originalFilename)}";
		if (File.Exists(finalPath))
		{
			Debug.Log($"Found external file at: {finalPath}");
			return File.OpenRead(finalPath);
		}
		throw new Exception($"File {originalFilename} not found.");
	}
}
```

```C#:ファイルからTextureLoadingContextsを作成
public class TextureMapperSample : TextureMapper
{
	public override TextureLoadingContext Map(AssetLoaderContext assetLoaderContext, ITexture texture)
	{
		var finalPath = $"{assetLoaderContext.BasePath}/{FileUtils.GetFilename(texture.Filename)}";
		if (File.Exists(finalPath))
		{
			var textureLoadingContext = new TextureLoadingContext
			{
				Context = assetLoaderContext,
				Stream = File.OpenRead(finalPath),
				Texture = texture
			};
			Debug.Log($"Found texture at: {finalPath}");
			return textureLoadingContext;
		}
		throw new Exception($"Texture {texture.Filename} not found.");
	}
}
```

#TriLib1からTriLib2への移行

TriLib2の使用法はTriLib1と非常に似ていますが、いくつかの基本的な変更が加えられています。

##ファイルからのモデルのロード

TriLib 1のファイルからモデルをロードする場合、次のスニペットが使用されます。

```C#:TriLib1
// TriLib 1 Code
// --------------------
using (var assetLoader = new AssetLoader()) {
    var assetLoaderOptions = AssetLoaderOptions.CreateInstance();   //AssetLoaderOptionsインスタンスを作成します。
                                                                    //AssetLoaderOptionsを使用すると、モデルをロードするためのオプションを指定できます。
                                                                    //（オプション）このオブジェクトの作成とそのパラメータをスキップするか、nullを渡すことができます。
    
    //LoadFromFileメソッドに渡す前にassetLoaderOptionsを変更できます。AssetLoaderOptions APIリファレンスは次の場所で確認できます：
    //https://ricardoreis.net/trilib/manual/html/class_tri_lib_1_1_asset_loader_options.html
    
    var wrapperGameObject = gameObject;                             //モデルがロードされるゲームオブジェクトを設定します。
                                                                    //（オプション）このオブジェクトの作成とそのパラメータをスキップするか、nullを渡すことができます。  

    var myGameObject = assetLoader.LoadFromFile("PATH TO MY FILE.FBX", assetLoaderOptions, wrapperGameObject); //モデルを同期的にロードし、参照をmyGameObjectに保存します。
}
```

```C#:TriLib1
// TriLib 1 Code
// --------------------
using (var assetLoaderAsync = new AssetLoaderAsync()) {
    var assetLoaderOptions = AssetLoaderOptions.CreateInstance();   //AssetLoaderOptionsインスタンスを作成します。
                                                                    // AssetLoaderOptionsを使用すると、モデルをロードするためのオプションを指定できます。
                                                                    //（オプション）このオブジェクトの作成とそのパラメータをスキップするか、nullを渡すことができます。
    
    // LoadFromFileメソッドに渡す前にassetLoaderOptionsを変更できます。AssetLoaderOptions APIリファレンスは次の場所で確認できます：
    //https://ricardoreis.net/trilib/manual/html/class_tri_lib_1_1_asset_loader_options.html
    
    var wrapperGameObject = gameObject;                             //モデルがロードされるゲームオブジェクトを設定します。
                                                                    //（オプション）このオブジェクトの作成とそのパラメータをスキップするか、nullを渡すことができます。  

    var thread = assetLoaderAsync.LoadFromFile("PATH TO MY FILE.FBX", assetLoaderOptions, wrapperGameObject, delegate(GameObject myGameObject) {
        //ここではmyGameObjectを使用してロードされたモデルへの参照を取得することができます。
    }); //モデルを非同期でロードし、作成されたタスク/スレッドへの参照を返します。
}>
```

TriLib 2の最初の違いは、モデルを同期または非同期ロードするかどうかを指定する必要がないことです。これは、TriLib 2がサポートするプラットフォームで非同期を使用し、サポートがない場合はフォールバックしてロードを同期するためです。

TriLib 2では、**AssetLoader**または**AssetLoaderAsync**クラスをインスタンス化する必要はありません。現在、すべてのロードは**AssetLoader**静的クラスを使用して行われます。

TriLib 2は、モデルのロード時に**GameObject**または**Thread**を返さなくなりました。代わりに、ロードされた**GameObject**を**RootGameObject**フィールドとして含む**AssetLoaderContext**オブジェクトと、モデルのロードに関するより多くの情報が生成されます。

TriLib 2も**AssetLoaderOptions**を使用しますが、これらはよりクリーンでファイル形式にとらわれないアプローチを採用するように変更されました。**AssetLoaderOptions**を作成する方法は同じです。

これらの変更を考慮すると、上記のTriLib1サンプルに一致する単純なTriLib2モデルのロードは次のように再現できます。

```C#:TriLib2
// TriLib 2 Code
// --------------------
var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
AssetLoader.LoadModelFromFile("PATH TO MY FILE.FBX", null, delegate(AssetLoaderContext assetLoaderContext) {
    var myGameObject = assetLoaderContext.RootGameObject;
}, null, null, null, null, assetLoaderOptions);
```

##カスタムソースからのモデルのロード

TriLib 1では、カスタムデータを取得するためのコールバック、そのようなデータが存在するかどうかを確認するためのコールバック、および任意の外部リソースからカスタム外部テクスチャデータを返すためのコールバックを使用して、バイト配列からモデルをロードできます。


```C#:TriLib1
// TriLib 1 Code
// --------------------
var modelData = File.ReadAllBytes("PATH_TO_MY_FILE.OBJ");
using (var assetLoader = new AssetLoader()) {
	var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
	var myGameObject = assetLoader.LoadFromMemory(modelData, "MY_FILE.OBJ", assetLoaderOptions, null, "PATH_TO", 
	delegate(string path, int fileId, ref int fileSize) { //外部データ読み取りコールバック
		var resourceData = File.ReadAllBytes(path); //すべての外部ファイルデータを読み取ります
		fileSize = resourceData.Length; //外部ファイルサイズを取得します
		return resourceData; //外部ファイルデータを返します
	},
	delegate(string path, int fileId) { //外部データチェックコールバック
		return File.Exists(path); //外部ファイルが存在する場合はtrueを返します
	},
	delegate (string path, string basePath) { //外部テクスチャデータ読み取りコールバック
		var finalPath = Path.Combine(basePath, path); //モデルのベースパスとテクスチャパスを組み合わせます
		if (File.Exists(finalPath)) { //結合されたパスにファイルがあるかどうかを確認します
			return File.ReadAllBytes(finalPath); //ファイルデータを返します
		}
		return null; //指定されたファイルが見つからなかった場合はnullを返します
	},
	delegate (float progress) { //進行状況処理コールバック
		Debug.Log("Loaded:" + progress); //モデルの読み込みの進行状況をコンソールに書き込みます
	});
}
```

TriLib 2は、さまざまなアプローチを使用して、カスタムデータソースからモデルをロードします。

- **バイト配列では機能しなくなりました。** TriLib2は**Stream**データの読み取りで動作します。
- モデルが使用する外部リソースは、**ExternalDataMapper**クラスを継承するクラスを使用してロードする必要があります。
- モデルが使用する外部テクスチャは、**TextureMapper**クラスを継承するクラスを使用してロードする必要があります。

以下の例では、カスタム**Stream**からモデルをロードし、その外部リソースをカスタム**ExternalDataMapper**クラスと**TextureMapper**クラスで処理します。

```C#:TriLib2
// TriLib 2 Code
// --------------------
var assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance<ExternalDataMapperSample>();
assetLoaderOptions.TextureMapper = ScriptableObject.CreateInstance<TextureMapperSample>();
var modelPath = "PATH_TO_MY_MODEL.FBX";
AssetLoader.LoadModelFromStream(File.OpenRead(modelPath), modelPath, null, OnLoad, OnMaterialsLoad, OnProgress, OnError, null, assetLoaderOptions);
```
上記のコードで使用されている**Mappers**は次のとおりです。 1つは**外部リソースファイル**から**Streams**を作成し、もう1つはファイルから**TextureLoadingContexts**を作成します。

```C#:TriLib2
// TriLib 2 Code
// --------------------
public class ExternalDataMapperSample : ExternalDataMapper
{
	public override Stream Map(AssetLoaderContext assetLoaderContext, string originalFilename, out string finalPath)
	{
		finalPath = $"{assetLoaderContext.BasePath}/{FileUtils.GetFilename(originalFilename)}";
		if (File.Exists(finalPath))
		{
			Debug.Log($"Found external file at: {finalPath}");
			return File.OpenRead(finalPath);
		}
		throw new Exception($"File {originalFilename} not found.");
	}
}
```

```C#:TriLib2
// TriLib 2 Code
// --------------------
public class TextureMapperSample : TextureMapper
{
	public override TextureLoadingContext Map(AssetLoaderContext assetLoaderContext, ITexture texture)
	{
		var finalPath = $"{assetLoaderContext.BasePath}/{FileUtils.GetFilename(texture.Filename)}";
		if (File.Exists(finalPath))
		{
			var textureLoadingContext = new TextureLoadingContext
			{
				Context = assetLoaderContext,
				Stream = File.OpenRead(finalPath),
				Texture = texture
			};
			Debug.Log($"Found texture at: {finalPath}");
			return textureLoadingContext;
		}
		throw new Exception($"Texture {texture.Filename} not found.");
	}
}
```