Polyはすごい
Polyはさまざまな3Dモデルや360°写真1をCCライセンスで提供しているオンラインライブラリです。
「無料ですぐ使えるモデル置いとくよ! みんなでAR/VRコンテンツを開発していこうね!」というGoogle様のお慈悲です。
詳しいことは公式とかその周辺記事を漁ってみてください。
Poly API を使ってみる
[Unity] GooglePolyを使ってローポリ3Dオブジェクトを検索/配置する
Poly toolkit for unity使った。
Poly Toolkit for Unity
エディタでロードしている記事はわりとあったんですが、ランタイムロードしている日本語の記事が見つからなかったので使った感触を書きます。2019/04/14時点の情報を元に書いてます。
Unity QuickStart
Unity QuickStart
クイックスタートに従って進めれば問題ありません。スクリーンショットつきの丁寧な説明なのですんなり行けると思います。
Troubleshootingのところにobsolete APIsを自動アップデートしてね!とか書いてありますが、これに驚いているようではGoogleのProductを使う「資格」はない。2
API
Polyを使うすべてのシーンに置いておけと書いてあるPolyToolkitManager
のPrefab
ですが、PolyApi.Init();
をAwake
で実行する以外何もしてない……。
いずれ機能が追加されるかもしれないですし、置いとけって書いてあるんだから置いておきましょう。
おそらく公式が想定している手順であろう以下をコードでやっていきます。ついでにCallback
でつらいのでTask
に変換します。ログイン周りはめんどくさいのでパス。
- モデルの基礎情報を取得する
- サムネイルを表示する
- モデルを
GameObject
としてロード - 権利表記
モデルの基礎情報を取得する
検索についての共通
PolyRequest
を継承した検索条件のstruct
を各検索APIに渡します。
検索結果のPolyListAssetsResult.nextPageToken
を渡せば次が検索できるので、検索条件はどこかの変数にとっておいて使いまわしましょう。
PolyApi.ListAssets
普通の検索です。普通。
public static Task<PolyListAssetsResult> SearchListAssetsAsync(PolyListAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
PolyApi.ListUserAssets
ログインが前提。ユーザが投稿したモデル一覧が検索されます。
public static Task<PolyListAssetsResult> SearchUserListAssetsAsync(PolyListUserAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListUserAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
PolyApi.ListLikedAssets
ログイン中のユーザがお気に入りしたモデル一覧です。お気に入りはブラウザのサイトからできます。
public static Task<PolyListAssetsResult> SearchLikedListAssetsAsync(PolyListLikedAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListLikedAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
PolyApi.GetAsset
使うモデルがあらかじめわかっている場合はこちらを使います。ブラウザで見たときの末尾の文字列です。
サンプルやAPIから返ってくる識別子にはassets/
がついていますが、別になくても動きます。それでいいのか。
public static Task<PolyAsset> GetAssetAsync(string name)
{
var tcs = new TaskCompletionSource<PolyAsset>();
PolyApi.GetAsset(name, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
サムネイルを表示する
PolyApi.FetchThumbnail
サムネイルを取得します。これをしないとPolyAsset.thumbnailTexture
はnullのままです。
中ではPolyAsset.thumbnail.url
を読み込んでるだけです。とはいえPolyFetchThumbnailOptions
によるサイズ調整とかダウンロード処理のキューイングとかしてくれてるので使っておきましょう。
public static Task<PolyAsset> FetchThumbnailAsync(PolyAsset asset, PolyFetchThumbnailOptions options)
{
var tcs = new TaskCompletionSource<PolyAsset>();
PolyApi.FetchThumbnail(asset, options, (polyAsset, status) =>
{
if (status.ok)
{
tcs.SetResult(polyAsset);
}
else
{
tcs.SetException(new Exception(status.errorMessage));
}
});
return tcs.Task;
}
モデルをGameObject
としてロード
PolyApi.Import
ようやくモデルの読み込みです。PolyImportOptions
の各パラメータの意味はエディタ上でインポートするときの表示と突き合わせると理解しやすいです。Scaleをちゃんと調整しておかないとめっちゃでかいやつが出てきます。
public static Task<(GameObject obj, PolyAsset polyAsset)> ImportAsync(PolyAsset asset, PolyImportOptions? options = null)
{
var tcs = new TaskCompletionSource<(GameObject obj, PolyAsset polyAsset)>();
PolyApi.Import(asset, options ?? PolyImportOptions.Default(), (polyAsset, result) =>
{
if (result.Ok)
{
tcs.SetResult((result.Value.gameObject, polyAsset));
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
権利表記
PolyApi.GenerateAttributions
はEditor上でインポートしておいたモデルの権利表記に加えてランタイムロードしたPolyAsset
のList
を渡せば全部まとめて権利表記文を書き出してくれるイカしたAPIです。
引数次第でどちらかだけ出す、なんてこともできます。
ただし第二引数List<PolyAsset> runtimeAssets
のデフォルトはnull
ですが中でnullチェックしないで触ってます。第二引数なしで蹴ると必ずぬるぽになるので適当にnewしたリストでも入れてあげましょう。ははは。3
ちなみにひとつだけ知りたい、という場合はPolyAsset.AttributionInfo
で取れます。
実践
「人気順にAnimalカテゴリからdogを検索して一番目のモデルをロードして権利表記も表示する」
メソッドをStartで実行してみます。
private async void SearchAndShowExample()
{
var searchQuery = PolyListAssetsRequest.Featured();
// カテゴリ:動物
searchQuery.category = PolyCategory.ANIMALS;
// 犬を検索
searchQuery.keywords = "dog";
searchQuery.pageSize = 5;
var search = await PolyTasker.SearchListAssetsAsync(searchQuery);
var (obj, asset) = await PolyTasker.ImportAsync(search.assets[0], PolyImportOptions.Default());
_text.text = asset.AttributionInfo;
}
出ました! 狐が。4
権利表記もちゃんと読み込めています。
Task化
いままでのtask化したものをまとめてwrapper classにしました。使ってください。
gistはこちら。
using System;
using System.Threading.Tasks;
using PolyToolkit;
using UnityEngine;
namespace Daimao.Nekomimi.Poly
{
public static class PolyTasker
{
public static Task<PolyListAssetsResult> SearchListAssetsAsync(PolyListAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
public static Task<PolyListAssetsResult> SearchUserListAssetsAsync(PolyListUserAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListUserAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
public static Task<PolyListAssetsResult> SearchLikedListAssetsAsync(PolyListLikedAssetsRequest assetsRequest)
{
var tcs = new TaskCompletionSource<PolyListAssetsResult>();
PolyApi.ListLikedAssets(assetsRequest, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
public static Task<PolyAsset> FetchThumbnailAsync(PolyAsset asset, PolyFetchThumbnailOptions options)
{
var tcs = new TaskCompletionSource<PolyAsset>();
PolyApi.FetchThumbnail(asset, options, (polyAsset, status) =>
{
if (status.ok)
{
tcs.SetResult(polyAsset);
}
else
{
tcs.SetException(new Exception(status.errorMessage));
}
});
return tcs.Task;
}
public static Task<PolyAsset> GetAssetAsync(string name)
{
var tcs = new TaskCompletionSource<PolyAsset>();
PolyApi.GetAsset(name, result =>
{
if (result.Ok)
{
tcs.SetResult(result.Value);
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
public static Task<(GameObject obj, PolyAsset polyAsset)> ImportAsync(PolyAsset asset, PolyImportOptions? options = null)
{
var tcs = new TaskCompletionSource<(GameObject obj, PolyAsset polyAsset)>();
PolyApi.Import(asset, options ?? PolyImportOptions.Default(), (polyAsset, result) =>
{
if (result.Ok)
{
tcs.SetResult((result.Value.gameObject, polyAsset));
}
else
{
tcs.SetException(new Exception(result.Status.errorMessage));
}
});
return tcs.Task;
}
}
}
気になる点
Android, iOSではcacheできない
https://github.com/googlevr/poly-toolkit-unity/issues/9
できなくはないらしい。
ていうかGoogle様!!!!!!これ主にARCoreとかARKitで使ってほしいやつですよね!!!?キャッシュはモバイルにこそ必要!!!!!なんでそこサポートしてないの!!?!?!?!!?!?!delegate
使ってる
delegateをどうやったらUniRxで扱えるかわからなかったのと、なんかdelegateは古くてダサいらしいからActionとか使ったほうが……と思ったんですがおそろしく深いところまでcallback渡しに行ってるのでそう簡単に手を入れられない。API制限
PolyもGCPの一部なので呼び出し回数に制限があります。おそらくPoly Toolkit for Unity
もPoly API
をラップしただけのもののはずなので3000 queries-per-minute (QPM)が適応されるはず。でも「もっと!」っておねだりはできるっぽい。
まとめ
3Dモデル関連はぜんぜん詳しくないのですが、競合サービスと比べてランタイムロードできるのはけっこうな強みのような気がします。でもあんま更新されてなくてGoogle様これ飽きたのでは……?
わりと普通に勝てるサービスだと思うのでもっと頑張ってほしいな!
おしまい。
おまけ
見てて面白かったモデル載せます。見てる感じえろいのは流石にフィルタリングしてるっぽいですが、後は著作権とかない無法の沃野と化してます。5
公式動画ですら1:00あたりにやべーやつが大地に立ってるしな!
私的にこういうLowPolyなモデル、特にGoogle様公式のモデルがほんと好きです。大好き。6
iframeの埋め込みが動かないっぽいのでリンクから見てください。
Doggo DayDream
かわいい。
Godzilla
手でコントローラ持ってるのがチャームポイント。
Roadtrip
かわいい。アニメーションしてる。
Ninja Droid WIP
ニンジャナンデ? カワイイヤッター!
Corgi-saurus Rex
見えなくはない。
Turkey Attack!
Attack!