AssetBundleにモデルを格納した時、モデルのマテリアルがユニークなシェーダーを使用しているとモデルがピンクになる事があります。
今回はその原因と対策についてです。
- モデルがピンクになるという事
- AssetBundleに格納したSceneやModelがピンクになる
- 問題の原因は、グラフィックAPIが異なる事
- 解決方法1:Graphics APIを対象プラットフォームに合わせる
- 解決法2:Shaderを実行時に差し替える(非推奨)
- 解決法3:AssetBundleシミュレーター
- 関連
モデルがピンクになるという事
Unityでモデルを生成した時、条件によっては伝統的なピンク色になる事があります。
この現象を発生させるためには、モデル表現に使用するMaterialや、Materialが使用するShaderの参照が見つからない(missing)状態や、シェーダーが動作しない場合*1等があります。
要するに、下のいずれかのケースでモデルがピンク色になります。
- Shaderへの参照が外れた
- Shaderにエラーがある
- Shaderが動作しない(環境依存)
- Materialが無い(missing)
- Unlit/Colorシェーダーで(R:1, G:0, B:1,A:1)を指定している
ちなみに、このピンクはInternalErrorShaderっぽいですが上書き方法が分からないので、細かい事は置いておきます。
AssetBundleに格納したSceneやModelがピンクになる
さて、今回の内容ですが、AssetBundleに格納したモデル(PrefabやSceneからの参照)を表現する際、独自のシェーダーを使用しているモデルがピンク色になるという現象が確認出来る事があります。
下の画像の場合、Lit Outline のシェーダーを使用してAndroid向けにAssetBundle化した物をUnity エディターで展開した場合です。見事にLit Outlineのモデルがピンク色です。
なお、この問題はエディターでのみ発生します。
問題の原因は、グラフィックAPIが異なる事
この問題ですが、ピンク色になる原因は「Shaderが動作しない」です。
Unityエディタは、初期設定ではWindowsならDirect X11、OSXならOpenGLCore*2で動作しています。
逆にAssetBundleに格納されているシェーダーは、iOS/Android向けに出力した場合 OpenGLES2/3やMetalといった物向けのシェーダーが、(5.5以降はバイナリに変換され)格納されます。
要するに、レンダラーとシェーダーのGraphics APIが異なります。
結果としてAssetBundleから取得したシェーダーは読込に失敗し、ピンク色になります。*3
解決方法1:Graphics APIを対象プラットフォームに合わせる
問題はエディターのレンダラーが使用してるGraphics APIとAssetBundleに格納したシェーダーが使用するGraphics APIが異なり読めない事なので、解決方法は簡単です。Grahipcs APIを一致させれば良いのです。
Unity エディターのGraphics APIは(特に指定が無ければ)Standalone設定と一致するので、Grahpcis APIを読み込みたいAssetBundleが使用しているGraphics APIと合わせます。
これでエディターで読み込んだ場合も、モデルがピンクになる事は回避出来ます。
ただし、(例えば LightmapのRealtime GI等)任意のCPU向けに最適化されていて読めないデータ等もあるので、エディターのGraphics APIを合わせるよりは、エディターで動作確認する際にはStandalone向けにAssetBundleを出すほうが健全といえば健全に見えます。
解決法2:Shaderを実行時に差し替える(非推奨)
Googleで検索したときによくヒットする回避方法が、シェーダーを実行時に差し替える方法です。これはエディターのみ効果があり、実行時に使用されている場合には無駄なパフォーマンス浪費となります。
例えば下のようなコードをシーンやPrefabに登録しておき、実行時にマテリアルのシェーダーをリロードしてやります。
この動作は、マテリアルが参照しているシェーダーを、エディターが保持しているStandalone向けのシェーダーに差し替えてやるという事です。
その為、AssetBundle化後にシェーダー内容を書き換えると、書き換えた内容が使用されるのが確認出来ます。下の図では、Lit outlineのシェーダーをAssetBundleに出力した後、シェーダーの中身をDefuseに差し替えてエディターで動作確認したものです。見事にシェーダーがDefuseに差し替えられています。
またアプリケーションにこの機構が含まれていると、シーンに余計なデータが含まれデシリアライズコストが上がり、ついでにシェーダー検索の余計なコストも増加します。
とは言え、ゲームの構造によってはMaterialの数は殆ど無くAssetBundleのビルドコストが超高いというケースもあります。そういった場合はStandalone向けをビルドするよりは、シェーダー差し替えを行う方が開発工数的には楽になるかもしれません。
(一応、ビルドコストを下げる為に、Standalone向けとMobile向けのシェーダーを用意しVariantで差し替える手法も使えなくはないですが、ワークフローが複雑です)
解決法3:AssetBundleシミュレーター
個人的には、この手法でやるよりはAssetBundle シミュレーターのような手法で動作検証するほうが良いような気もします。
勿論、あくまで「シミュレーター」なので実際の動作と異なるかもしれませんが、それはPCシェーダーもどっこいなので。
シミュレーター自作が面倒ならAssetBundleManagerを使うのも手です。
ただし、こちらは自動でAssetBundleを開放する範囲が広いので、日本のUnityユーザーがよく使うような「AssetBundle読んで即開放」みたいな使い方をする場合は、一部のプラットフォームで問題が出るかもしれません。
(ロード時参照があるAssetBundleをロードし、開放時に非参照のAssetBundleは全て開放されてしまう。つまり、単一のAssetBundleのヘッダーが何度も展開・開放される危険があるかもしれない)
関連
naochang | AssetBundle化したShaderが外れてしまう