テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】StandardShaderをAssetbundleに格納した際にNormalやAlphaが動作しない問題の回避方法

f:id:tsubaki_t1:20160625171548p:plain

AssetBundleにシェーダーを格納し常にロードしておく事で、シェーダーのコンパイルGPU転送コスト、それにシェーダーのメモリ消費を回避出来ます。
特にシェーダーのコンパイルGPU転送等は地味に馬鹿にならないコストを要求するので、出来れば積極的に排除したい要因の一つです。

ただこの方法は1点問題があったので、その問題と回避方法について紹介します。

発生する問題

例えば下の画像は、Resourcesから取得した場合と、Assetbundleから取得した場合の比較です。
Resourcesから取得したシェーダーはノーマルマップが有効に働いていますが、AssetBundle から取得したシェーダーはノーマルマップが設定できていません。光を当てると質感がノッペリになってしまっています。

f:id:tsubaki_t1:20160625172223p:plain

設定内容は下のような感じです。StandardShaderを拡張したシェーダーを作成し、それをShaderアセットバンドルに格納しています。

f:id:tsubaki_t1:20160625172611p:plain

f:id:tsubaki_t1:20160625173302j:plain

これは同様に「Normalmap」や「Fade/Cutout/Transparent」「Emissive」を設定した項目でも発生します(トップ絵はFadeで行った例です)

問題の原因

この問題は、シェーダのストリップ機能から来ているみたいです。これは使用していないシェーダーをゲームから取り除く機能みたいです。

UnityのStandardシェーダーは少しだけ異なる処理をバリアントを利用して制御しています。これは、例えばαの利用やNormalの描画等、表現する必要のないシェーダー表現をスキップしたシェーダーを作成し、計算を回避しています。
(シェーダーはifを使用すると非常に遅くなるので、パターンの分だけシェーダーを用意しているみたいです)

f:id:tsubaki_t1:20160625175550j:plain

全パターンを網羅したシェーダーを用意すれば当然無駄に重いシェーダーが出来ますし、全てのパターンのシェーダーを格納すると、大体300MB近くになるそうなので、使用していないシェーダーをストリップするのは、まぁ正しいアプローチだと思います。

docs.unity3d.com

ここで問題になるのがAssetBundleに格納した場合です。
Assetbundleに格納したシェーダーは「どのシェーダーバリアントを格納すれば良いのか」を「他のシェーダーからの参照を巻き戻って判断」出来ていないらしく、ノーマルもアルファも無いシェーダーを格納するみたいです。

その為、他のマテリアルでEmissionやNormalやAlphaを設定してあった場合でも、シェーダーは自己発光(Emission)や凹凸(Normal)やAlpha(透過)の無い絵が出てきてしまうみたいです。

EnableKeywordで行けるかなと思いましたが、残念ながら有効な手ではありませんでした。ShaderVariantも同様。うーん。

解決方法

これの回避方法を探した所、一応ありました。要するにAssetBundleに「対象のパラメータを使用している」という情報を与えれば良いみたいです。つまり、

FadeやNormalといったパラメータを実際に使用しているマテリアルを同じアセットバンドルに含める感じです。これらのマテリアルは含めるだけで良く、別に実行時にロードする必要はありません

また、ここで「使用していること」を判断するためのテクスチャ等は、16x16のような小さいテクスチャでOKです。

f:id:tsubaki_t1:20160625235148p:plain

StandardShaderやシェーダーをアセットに含める事で、シェーダーの重複インスタンス化やら何やらを防げるので良い所はありますが、こういった所は注意する必要がありそうです。

関連

tsubakit1.hateblo.jp

dnasoftwares.hatenablog.com