TriLib
UnityのプラグインTriLibについてまとめました。
AssetBundleでなくTrilibにした背景
何年か前にUnityでランタイム中に、objファイルをロードしたい!Webで動的にobjをアップロード・ダウンロードしたい!と考えていた僕は、まずは正攻法でAssetBundleでのチャレンジを行いました。(サーバー上でUnityの自動バッチ処理)
しかし、AssetBundleのファイルの扱いづらさや、アップロード時にUnityのバッチを実行する際のコスト(しかもmetaが上手く生成されないなどで心が折れたこと)もあり、結局挫折してしまいました。
そんな中、Trilibという便利なプラグインがあることを知り、これならAssetBundle使わなくていいのでは?!とテンション爆上がり。といいうことで、さっそく試していきます。
購入
購入はアセットストアから。25ドルです。
https://assetstore.unity.com/packages/tools/modeling/trilib-model-loader-package-91777?locale=ja-JP
参考リンク
Trilib製作者のサイト
https://ricardoreis.net/
ドキュメントはこちら
https://ricardoreis.net/trilib/manual/html/index.html
他の方の書いた記事はこちら
https://www.fast-system.jp/unity-trilib-assets-howto/
Unityからプラグインをインポート
Unityのバージョンは2018.4を使用しています。途中から2019.4に変更しましたが、どちらも動作しています。
まずは購入した後AssetSotreからimportします
警告が出るので、左側のボタンをクリックしてUnityを再起動を選択します。
サンプル
TriLibアセット内にサンプルがあるので、見ていきます。
サンプルは、Scene1からScene9まであり、それぞれの機能紹介は以下のページから確認できます。
https://ricardoreis.net/trilib/manual/html/md__c_1__repos__tri_lib187__assets__tri_lib__s_a_m_p_l_e_s.html
Scene1 アセット ロード サンプル
このサンプルでは、ローカルストレージとURIからモデルをロードできます。
Scene1のパスは以下の通りです。
Assets/TriLib/TriLib/Samples/Scenes/Scene1
シーン内はこのようになっています。
では、Playして動作を確認します。
一番上のLoad Local Assetを押すと、Please select a File と出てくるので、objファイルやfbxファイルの存在するディレクトリまで移動します。
特に用意がない場合は、TriLib内のSample内を参照します。
C:\Users\804910\Documents\Holo2Smaple\TrilibTest\Assets\TriLib\TriLib\Samples\Models
今回は、BigModel.objを選択します。このモデルは、ただの立方体です。(ちなみにBouncing.fbxは球体がバウンドするアニメーション付きのfbxデータです。)
立方体が読み込まれているのが確認できます。全体を確認する場合はSpin in X axis にチェックを入れると確認が可能です。
URLから取得
次に上から2つ目のボタン「Load remote Asset(URL)」から取得します。
デフォルトでは以下のzipファイルが記載されています。
https://ricardoreis.net/trilib/test1.zip
OKを押すとロードされて、読み込まれる・・と思いきや、エラーが表示されました。
No suitable reader found for the file format of file \$\$\$_magic_\$\$\$..zip
\$\$\$_magic_\$\$\$..zipファイル形式に適したリーダーが見つかりません ということは、読み込む形式を認識できていないようです。。なんで???(ちなみにzipファイルの中身は.3dsファイルとテクスチャです)
では、自分でアップロードしたデータではどうでしょうか。
最初にGoogleDriveで試します。GoogleDriveにfbxファイルをアップして、再度チャレンジ
しかし、これも先ほどと同様のエラーが出現し、ファイル形式を推定できませんでした。
次に、Amazon S3にfbxファイルをアップロードさせます。パブリックアクセスをすべてブロック系のチェックを外すか、もしくはバケットポリシーできちんとIPを指定してあげます。設定方法はS3に関するほかの方の記事を参考にしてください。
リンクは短縮にならないので、Extensionも認識しているので、行けそうです。
読み込めました!
AWSは行けましたが、Azureはどうでしょうか。Azure StorageにもAzure Storage Explolerから同様にコンテナを作ってFBXを放り込みます。アクセスレベルの変更は、時間がないのでSet Container AcSet Container Access Level からPublic Access levelに変更します。
Azure Storageも同様にExtensionも認識しているので、行けそうです。
Azureも問題なく、読み込みが完了しました。
単体の3dデータは読み込みができました。
以上のように、ロードの処理は問題なく動作しています。それなのにzipファイルの3dデータが読めないのは何故なのか。Web上に上がっているdemoではzipも問題なくロードできています。何故だ!?
https://ricardoreis.net/trilib/demo/
問題の切り分けを行っていきます。
まず最初に疑ったのがUnityのバージョンです、Unity2019.2をインストールして再度確認→同様のエラーが発生
次は、Playしている間だけでBuildしたら問題ないかもしれません→Windowsでビルド→ダメ
で、フォーラムでもいろいろ見て、、、1.9のバージョンでは解決していると書いてある。
最終的に原因は、Project Settingsのzip読み込み設定にチェックが入っていないという初歩的な見逃しでした。
書いてあるやんけ~
https://ricardoreis.net/?p=341
ということで、無事zipファイルを読み込むことができました。
Scene2 同期 アセット ロード サンプル
このサンプルは、TriLibプロジェクトフォルダーからモデルを同期的に読み込みます。
Playすると、右側に左と同じfbxファイルがロードされます。
動作としては、LoaderオブジェクトのLoadSample.csが実行されています。
using System;
using UnityEngine;
namespace TriLib
{
namespace Samples
{
/// <summary>
/// Represents a simple model loading sample.
/// </summary>
public class LoadSample : MonoBehaviour
{
#if UNITY_EDITOR || !UNITY_WINRT
/// <summary>
/// Tries to load "Bouncing.fbx" model
/// </summary>
protected void Start()
{
using (var assetLoader = new AssetLoader())
{
try
{
var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
assetLoaderOptions.RotationAngles = new Vector3(90f, 180f, 0f);
assetLoaderOptions.AutoPlayAnimations = true;
assetLoaderOptions.UseOriginalPositionRotationAndScale = true;
var loadedGameObject = assetLoader.LoadFromFile(string.Format("{0}/Bouncing.fbx", TriLibProjectUtils.FindPathRelativeToProject("Models", "t:Model Bouncing")), assetLoaderOptions);
loadedGameObject.transform.position = new Vector3(128f, 0f, 0f);
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
}
}
#endif
}
}
}
やっていることは単純で、AssetLoaderOptionsのインスタンスを作り、LoadFromFileメソッドに渡す前にassetLoaderOptionsを変更しています。ロードのオプションは、以下のドキュメントを参照してください。
https://ricardoreis.net/trilib/manual/html/class_tri_lib_1_1_asset_loader_options.html
このサンプルでは、回転軸の設定、FBXのアニメーションとトランスフォームを有効に。ロードはLoadFromFileでアセット内SampleのBouncing.fbxを相対パスで呼び出して、最後にポジションを少しX軸で右側にずらしているだけです。
Scene3 同期 アセット ダウンロード サンプル
ModelDownloaderオブジェクトに、2つのコンポーネントAsset DownloaderとSimple Rotaterが適用されています。名前の通り、前者がURI読み込みの便利なコンポーネントで、後者がシンプルな回転を加えるだけの機能となります。
サンプルでは、自動スタート有効、URIは単一の.3dsファイルのURI、タイムアウトは2000(2000msec(2秒)かな、と思いきや、_unityWebRequest.timeoutにそのまま突っ込んでいるので2000sec(2000秒、33分)ですね・・・ファイルサイズがデカいと2秒は余裕で超えるとはいえ、長すぎる気もしますが)、対象の拡張子、ラップするGame Object(読み込んだアセットがこのGameObjectの子要素となります。)
Scene4 非同期 アセット ロード サンプル
このサンプルは、TriLibプロジェクトフォルダーからモデルを非同期で読み込みます。
Scene2との違いは、assetLoaderに入れているものと、LoadFromFile時の処理の違いとなります。
Scene2では、同期のため全ての処理が完了してから処理が走っています。
対して、Scene4では非同期のため、先に組み込まれているGameObjectの処理が走った結果、ロードは少し遅れて処理されていることがわかります。
同期アセットロードと非同期セットロードの違い
同期と非同期の読み込みの処理の違いは、製作者のサイトにある基本的な使い方を参照したほうがわかりやすいです。
https://ricardoreis.net/?p=341
using (var assetLoader = new AssetLoader()) {
//AssetLoaderOptionsインスタンスを作成します。
var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
//AssetLoaderOptionsを使用すると、モデルをロードするためのオプションを指定できます。
//モデルがロードされるゲームオブジェクトを設定します。
var wrapperGameObject = gameObject;
//モデルを同期的にロードし、myGameObjectに参照を保存します。
var myGameObject = assetLoader.LoadFromFile("PATH TO MY FILE.FBX", assetLoaderOptions, wrapperGameObject);
}
using (var assetLoaderAsync = new AssetLoaderAsync()) {
// AssetLoaderOptionsインスタンスを作成します。
var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
// AssetLoaderOptionsを使用すると、モデルをロードするためのオプションを指定できます。
//モデルがロードされるゲームオブジェクトを設定します。
var wrapperGameObject = gameObject;
var thread = assetLoaderAsync.LoadFromFile("PATH TO MY FILE.FBX", assetLoaderOptions, wrapperGameObject, delegate(GameObject myGameObject) {
//ここで、ロードされたモデルへの参照をmyGameObjectを使用して取得できます。
}); //モデルを非同期にロードし、作成されたタスク/スレッドへの参照を返します。
}
注:非同期ローダーは読み込みリクエストごとに使用されることを想定しているため、読み込むモデルごとに新しいAssetLoaderAssyncを作成するか、処理が完了したらAssetLoaderAsyncインスタンスを再利用する必要があります。
Scene5 同期 バッチ アセット ダウンロード サンプル
このサンプルでは、TriLibサーバーから一連のモデルをダウンロードしてロードできます。サンプルが読み込まれた後、読み込みを高速化するためにローカルに保存されます。
DownloadSample.csでは、スタート時にまず、_fileDownloaders = new UnityWebRequest[urls.Length];で必要なURIを入れておき、OnGUI内の、Loadボタンが押された際に既にローカルにデータが存在しているかのチェックを行います。ここで初回は当然未ダウンロードなので、StartCoroutine(DownloadFile(・・・のWebからのダウンロード処理が走ります。(ローカルに保持されている場合はLoadFileで読み込み)
このローカルってどこ?というと、僕の環境だと以下の場所にデータがダウンロードされていました。
C:/Users/804910/AppData/LocalLow/DefaultCompany/New Unity Project/test3
これはApplication.persistentDataPathで指定しているためであって、用途に合わせてどこに保存するかは調整が必要になるはずです。参考:unityでplatformによって取得できるパス
※LocalLow:このフォルダ(%appdata%/../LocalLow)には移動できないデータが含まれていますが、アクセスレベルも低くなっています。たとえば、保護モードまたはセーフモードでWebブラウザを実行している場合、アプリはLocalLowフォルダのデータにしかアクセスできません。
そして、UnityWebRequest.Getでダウンロードしたファイルが、objやfbxのように単体のファイルであればそのままディレクトリ作成・データコピー・ファイル読み込みを行います。例外として、zip形式の場合はUnzipFromStream関数で、zipを展開してから同様の処理を行います。
using System;
using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
#if TRILIB_USE_ZIP
#if !UNITY_EDITOR && (NETFX_CORE || NET_4_6 || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
using System.IO.Compression;
#else
using ICSharpCode.SharpZipLib.Zip;
#endif
#endif
namespace TriLib
{
namespace Samples
{
public class DownloadSample : MonoBehaviour
{
//Here are our asset URLs
private string[] urls =
{
"http://ricardoreis.net/trilib/test1.zip",
"http://ricardoreis.net/trilib/test2.zip",
"http://ricardoreis.net/trilib/test3.zip",
"http://ricardoreis.net/trilib/test1.3ds"
};
//Stores a reference for file downloaders
private UnityWebRequest[] _fileDownloaders;
//Reference for the latest loaded GameObject
private GameObject _loadedGameObject;
//Start logic
private void Start()
{
//For each asset on the list, we create a slot for a new WWW instance
_fileDownloaders = new UnityWebRequest[urls.Length];
}
//GUI logic
private void OnGUI()
{
//Loop thru all avariable URLs
for (var i = 0; i < urls.Length; i++)
{
//Gets current URL
var url = urls[i];
//Gets current WWW instance (if avariable)
var fileDownloader = _fileDownloaders[i];
GUILayout.BeginHorizontal();
//Shows the URL on GUI
GUILayout.Label(url);
//Checks if current file downloader exists
if (fileDownloader == null)
{
//When clicking the "Load" button
if (GUILayout.Button("Load"))
{
//Destroys the latest loaded GameObject, if avariable
if (_loadedGameObject != null)
{
Destroy(_loadedGameObject);
}
//Gets current file name, extension, local path and local filename
var fileName = FileUtils.GetFilenameWithoutExtension(url);
var fileExtension = FileUtils.GetFileExtension(url);
var localFilePath = string.Format("{0}/{1}", Application.persistentDataPath, fileName);
var localFilename = string.Format("{0}/{1}{2}", localFilePath, fileName, fileExtension);
//Checks if local path exists, which indicates the file has been downloaded
if (Directory.Exists(localFilePath))
{
//Loads local file
LoadFile(fileExtension, localFilename);
}
else
{
//If local path doesn't exists, download the file and create the local folder
StartCoroutine(DownloadFile(url, i, fileExtension, localFilePath, localFilename));
}
}
}
else
{
//Shows the current file download progress
GUILayout.Label(string.Format("Downloaded {0:P2}", fileDownloader.downloadedBytes == 0 ? 0f : fileDownloader.downloadProgress));
}
GUILayout.EndHorizontal();
}
}
//Searches inside a path and returns the first path of an asset loadable by TriLib
private string GetReadableAssetPath(string path)
{
var supportedExtensions = AssetLoaderBase.GetSupportedFileExtensions();
foreach (var file in Directory.GetFiles(path))
{
var fileExtension = FileUtils.GetFileExtension(file);
if (supportedExtensions.Contains("*" + fileExtension + ";"))
{
return file;
}
}
foreach (var directory in Directory.GetDirectories(path))
{
var assetPath = GetReadableAssetPath(directory);
if (assetPath != null)
{
return assetPath;
}
}
return null;
}
//Loads an existing local file
private void LoadFile(string fileExtension, string localFilename)
{
//Creates a new AssetLoader instance
using (var assetLoader = new AssetLoader())
{
//Checks if the URL is a ZIP file
if (fileExtension == ".zip")
{
#if TRILIB_USE_ZIP
var localFilePath = FileUtils.GetFileDirectory(localFilename);
//Gets the first asset loadable by TriLib on the folder
var assetPath = GetReadableAssetPath(localFilePath);
if (assetPath == null)
{
Debug.LogError("No TriLib readable file could be found on the given directory");
return;
}
//Loads the found asset
_loadedGameObject = assetLoader.LoadFromFile(assetPath);
#else
throw new Exception("Please enable TriLib ZIP loading");
#endif
}
else
{
//If the URL is not a ZIP file, loads the file inside the folder
_loadedGameObject = assetLoader.LoadFromFile(localFilename);
}
//Move camera away to fit the loaded object in view
Camera.main.FitToBounds(_loadedGameObject.transform, 3f);
}
}
//Downloads a file to a local path or extract all ZIP file contents to the local path in case of ZIP files, then loads the file
private IEnumerator DownloadFile(string url, int index, string fileExtension, string localFilePath, string localFilename)
{
_fileDownloaders[index] = UnityWebRequest.Get(url);
yield return _fileDownloaders[index].SendWebRequest();
if (fileExtension == ".zip")
{
#if TRILIB_USE_ZIP
using (var memoryStream = new MemoryStream(_fileDownloaders[index].downloadHandler.data))
{
UnzipFromStream(memoryStream, localFilePath);
}
#else
throw new Exception("Please enable TriLib ZIP loading");
#endif
}
Directory.CreateDirectory(localFilePath);
File.WriteAllBytes(localFilename, _fileDownloaders[index].downloadHandler.data);
LoadFile(fileExtension, localFilename);
_fileDownloaders[index] = null;
}
#if TRILIB_USE_ZIP
//Helper function to extract all ZIP file contents to a local folder
private void UnzipFromStream(Stream zipStream, string outFolder)
{
#if !UNITY_EDITOR && (NETFX_CORE || NET_4_6 || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
var zipFile = new ZipArchive(zipStream, ZipArchiveMode.Read);
foreach (ZipArchiveEntry zipEntry in zipFile.Entries)
{
var zipFileStream = zipEntry.Open();
#else
var zipFile = new ZipFile(zipStream);
foreach (ZipEntry zipEntry in zipFile)
{
if (!zipEntry.IsFile)
{
continue;
}
var zipFileStream = zipFile.GetInputStream(zipEntry);
#endif
var entryFileName = zipEntry.Name;
var buffer = new byte[4096];
var fullZipToPath = Path.Combine(outFolder, entryFileName);
var directoryName = Path.GetDirectoryName(fullZipToPath);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
var fileName = Path.GetFileName(fullZipToPath);
if (fileName.Length == 0)
{
continue;
}
using (var streamWriter = File.Create(fullZipToPath))
{
#if !UNITY_EDITOR && (NETFX_CORE || NET_4_6 || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
zipFileStream.CopyTo(streamWriter);
#else
ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipFileStream, streamWriter, buffer);
#endif
}
}
}
#endif
}
}
}
Scene6 非同期 アセット ダウンロード サンプル
このサンプルは、URIからモデルを非同期に読み込みます。
このScene6は、ほぼScene4と同じで、4との違いはAsyncにチェックが入っているかどうかです。
Scene7 同期永続データパス アセット ロード サンプル
このサンプルでは、デバイスの永続データパスからモデルをロードできます。
まずはPlayで実行させます。しかし、ローカルのパスが出るだけで、特に何も出てきません。
Listening・・とあるので、このローカルパスに対して、ファイルを置いてあげる必要があります。
New Unity Project直下にfbxファイルもしくはobjファイル(TriLibがサポートしている形式)を配置します。そして再度実行するとボタンが現れました。
ボタンを押すと、データがロードされます。
Scene8 カスタムIO アセット ロード サンプル
このサンプルは、カスタムデータソースからモデルとテクスチャを読み込みます(データはbase-64文字列でエンコードされます)。
CustomIOLoadSample.csの中身を見ていきます。
処理内容としては、obj形式のファイルをテキスト形式から読み込む形で実現しています。テクスチャデータもBase64なので、すべて文字列です。
Obj、Mtl、テクスチャ、の文字列からassetLoader.LoadFromMemory(…の関数で読み込んでいます。
using System;
using UnityEngine;
using System.Text;
using STB;
using AOT;
namespace TriLib
{
namespace Samples
{
/// <summary>
/// Represents a custom IO system file loading.
/// You can load data from any source that returns a byte array with TriLib.
/// This sample loads a series of data from embedded data strings.
/// </summary>
public class CustomIOLoadSample : MonoBehaviour
{
/// <summary>
/// Obj file data.
/// </summary>
private const string ObjData = "mtllib cube.mtl\n\nv -1.000000 -1.000000 1.000000\nv 1.000000 -1.000000 1.000000\nv -1.000000 1.000000 1.000000\nv 1.000000 1.000000 1.000000\nv -1.000000 1.000000 -1.000000\nv 1.000000 1.000000 -1.000000\nv -1.000000 -1.000000 -1.000000\nv 1.000000 -1.000000 -1.000000\n\nvt 0.000000 0.000000\nvt 1.000000 0.000000\nvt 0.000000 1.000000\nvt 1.000000 1.000000\n\nvn 0.000000 0.000000 1.000000\nvn 0.000000 1.000000 0.000000\nvn 0.000000 0.000000 -1.000000\nvn 0.000000 -1.000000 0.000000\nvn 1.000000 0.000000 0.000000\nvn -1.000000 0.000000 0.000000\n\ng cube\nusemtl cube\ns 1\nf 1/1/1 2/2/1 3/3/1\nf 3/3/1 2/2/1 4/4/1\ns 2\nf 3/1/2 4/2/2 5/3/2\nf 5/3/2 4/2/2 6/4/2\ns 3\nf 5/4/3 6/3/3 7/2/3\nf 7/2/3 6/3/3 8/1/3\ns 4\nf 7/1/4 8/2/4 1/3/4\nf 1/3/4 8/2/4 2/4/4\ns 5\nf 2/1/5 8/2/5 4/3/5\nf 4/3/5 8/2/5 6/4/5\ns 6\nf 7/1/6 1/2/6 5/3/6\nf 5/3/6 1/2/6 3/4/6";
/// <summary>
/// MTL file name.
/// </summary>
private const string MtlFilename = "cube.mtl";
/// <summary>
/// MTL file data.
/// </summary>
private const string MtlData = "newmtl cube\n Ns 10.0000\n Ni 1.5000\n d 1.0000\n Tr 0.0000\n Tf 1.0000 1.0000 1.0000 \n illum 2\n Ka 0.0000 0.0000 0.0000\n Kd 0.5880 0.5880 0.5880\n Ks 0.0000 0.0000 0.0000\n Ke 0.0000 0.0000 0.0000\n map_Ka cube.png\n map_Kd cube.png";
/// <summary>
/// Texture file name.
/// </summary>
private const string TextureFilename = "cube.png";
/// <summary>
/// Texture file data.
/// </summary>
private const string TextureData = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVDhPrYrJDcAwDMO8/7hdoAegKAbtJo+W4EdH/MB5TDfka7YHJ0gwt05QOdZGIDvWRjhvFWgXCgcPMH8e6pDFQTTV4HXy0NqDk12B6+03Ii7udAgYy29ORgAAAABJRU5ErkJggg==";
/// <summary>
/// Load our custom model from embedded data strings.
/// </summary>
private void Start()
{
//Creates a new AssetLoader instance to load our model.
using (var assetLoader = new AssetLoader())
{
var objBytes = Encoding.UTF8.GetBytes(ObjData); //Transforms the OBJ file data into a char array.
//Loads the model contained in objBytes, passing:
//CustomDataCallback to load our mtlData contents;
//CustomExistsCallback to tell our mtlData contents exists;
//CustomTextureDataCallback to load our textureData;
assetLoader.LoadFromMemory(objBytes, ".obj", null, gameObject, null, CustomDataCallback, CustomExistsCallback, CustomTextureDataCallback);
}
}
/// <summary>
/// Checks if our custom texture has been requested and returns a new <see cref="EmbeddedTextureData"></see>.
/// </summary>
/// <param name="path">Requested texture path.</param>
/// <param name="basePath">Requested texture base path.</param>
/// <returns>If our custom texture has been requested, returns a new <see cref="EmbeddedTextureData"></see>, otherwise, returns <c>null</c></returns>
private static EmbeddedTextureData CustomTextureDataCallback(string path, string basePath)
{
//Checks if our custom texture has been requested.
if (path == TextureFilename)
{
var embeddedTextureData = new EmbeddedTextureData(); //Creates a new EmbeddedTextureData instance.
var textureBytes = Convert.FromBase64String(TextureData); //Transforms texture data from a Base64 string into a byte array.
embeddedTextureData.DataPointer = STBImageLoader.LoadTextureDataFromByteArray(textureBytes, out embeddedTextureData.Width, out embeddedTextureData.Height, out embeddedTextureData.NumChannels, out embeddedTextureData.DataLength); //Loads our custom texture data from the given byte array.
embeddedTextureData.OnDataDisposal = STBImageLoader.UnloadTextureData;
return embeddedTextureData;
}
//Returns null if our custom texture hasn't been requested.
return null;
}
/// <summary>
/// Checks if our custom mtl file has been requested and returns a new GC pointer handle for it.
/// </summary>
/// <param name="resourceFilename">Requested resource filename.</param>
/// <param name="resourceId">Requested resource ID.</param>
/// <param name="fileSize">Output requested resource file size.</param>
/// <returns>A new <see cref="IntPtr"/> if our custom mtl file has been requested, otherwise, returns a null <see cref="IntPtr"/>.</returns>
[MonoPInvokeCallback(typeof(AssimpInterop.DataCallback))]
private static IntPtr CustomDataCallback(string resourceFilename, int resourceId, ref int fileSize)
{
//Checks if our custom mtl file has been requested.
if (resourceFilename == MtlFilename)
{
var mtlBytes = Encoding.UTF8.GetBytes(MtlData); //Transforms our mtlData into a byte array.
fileSize = mtlBytes.Length; //Important: sets the fileSize output to our data array length.
var dataBuffer = AssimpInterop.LockGc(mtlBytes); //Lock the GC to don't destroy our array while being used.
var fileLoadData = AssetLoaderBase.FilesLoadData[resourceId]; //Retrieves the fileLoadData created for this request.
fileLoadData.AddBuffer(dataBuffer); //Adds the locked GC buffer to the request buffers list.
return dataBuffer.AddrOfPinnedObject(); //Returns our locked buffer pointer.
}
//Returns a null IntPtr if our custom mtl file hasn't been requested.
return IntPtr.Zero;
}
/// <summary>
/// Checks if it's our custom mtl file being requested and returns <c>true</c> to indicate the requested resource exists.
/// </summary>
/// <param name="resourceFilename">Requested resource filename.</param>
/// <param name="resourceId">Requested resource ID.</param>
/// <returns><c>true</c> if our custom mtl file has been requested, otherwise <c>false</c>.</returns>
[MonoPInvokeCallback(typeof(AssimpInterop.ExistsCallback))]
private static bool CustomExistsCallback(string resourceFilename, int resourceId)
{
return resourceFilename == MtlFilename;
}
}
}
}
Scene9 進行処理のサンプル
このサンプルは、ロードの進行状況インジケーターを表示しながら、TriLibプロジェクトフォルダーからモデルをロードします。
ProgressHandlingSample.csで処理を行っています。
HoloLens2用にビルドして読み込んでみる(現在調査中)
HoloLens2アプリでTriLibを利用してみます。
以下の記事が非常に参考になります。
http://araruaru.hatenadiary.com/entry/2019/06/24/110711
新しいSceneを作ります。
Assetsの直下にStreamingAssetsフォルダを作成し、objファイルを配置します。
SceneにCreateEmptyでGameObjectを追加して、名前をloadedGameObjectに変更。
NewScriptのコンポーネント追加で以下のように記載します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TriLib;
using System.IO;
using System;
public class trilibimport : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var filePath = Application.streamingAssetsPath + "/BigModel.obj";
byte[] fileBytes = File.ReadAllBytes(filePath);
using (var assetLoader = new AssetLoaderAsync())
{
var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
assetLoaderOptions.UseOriginalPositionRotationAndScale = true;
// バイト配列と拡張子がzipだと伝える
var loadingThread = assetLoader.LoadFromMemoryWithTextures(fileBytes, "zip", assetLoaderOptions, null, delegate (GameObject loadedGameObject)
{
// ロード完了後に行われる処理
// ロードされたfbxのモデルはloadedGameObject割り当てられる
Debug.Log("Load Finished");
});
}
}
}
これでPlayすると、立方体が読み込めているはずです。※GameObjectの位置とカメラの関係は調整が必要です
HoloLens2の下準備
ホロラボの中村さんの記事が非常に参考になります。
https://speakerdeck.com/hololab/hololens-hands-on-labs
下準備としては、このリンクからMicrosoft.MixedReality.Toolkit.Unity.Foundation.2.4.0.unitypackageをダウンロードして取り込みます。読み込んだらデフォルト設定をApplyします。
そして、BuildSettingでSceneを追加した後、UWPにSwitchしてProjectSettingのPlayer>PublishSettingsでPackageNameを設定します。
同じくPlayer内のXR Settingsで、Virtual Reality SDKsにWindows Mixed Realityを設定します。(Depthは16bit)
Add to Scene and Configure...でMixedRealityToolkitとMixedRealityPlayspace追加
プロファイルをDefaultHoloLens2ConfigurationProfileに変更した後、Copy&Customize→Clone
再度BuildSettingを開き、Buildを行う。
重複しているらしいです。何も触ってないぞ!
調べたところ、UWPのPluginsにはx86、x64、arm64の3つのフォルダがあるのですが、arm64の設定がx86になっています。これをarm64に変更してやる必要があります。
トラップはもう一つあります。arm64に入っているdllですが、なぜかarm64でなくただのarmでビルドされています。CMakeでビルドしなおす必要があります。CPUをx86からarm64に変更してApplyします。対象のdllは2つ、あるので忘れずに。assimp.dll、stb_image.dll。
ただし、現状arm64でビルドしても上手く動作しません。
https://forum.unity.com/threads/trilib-on-hololens-2-unable-to-load-dll-assimp.856954/
なぜか
Unable to load DLL 'assimp': The specified module could not be found.
の表示が出続けています。CMakeの方法が悪いのか、それともUnity側の設定が悪いのか、再度確認する必要があります。
現在調査中 後日追記します。