テラシュールブログ

旧テラシュールウェアブログ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

【Unity】RenderTextureをTexture2DにしてSpriteに使用する

f:id:tsubaki_t1:20160606221733g:plain

以前RenderTextureにテクスチャをバックバッファとして展開し、Spriteとして使用することでSetPassを抑えて使用する方法に聞かれたので、一応ココに書いておきます。

結論としては、別にSpriteとして使用しなくても良いよねって話です。
内容としては、ココから昨日の「【Unity】マテリアルやSetPassを増やさずテクスチャのUVを変える 」に繋がります。(昨日の記事はコレの検証中に出たというか)

 

Sprite云々とは別にRenderTextureをTexture2Dに流し込む話は時々聞くので、需要はあるのかもしれません。ただ、UnityがGrahic APIのドレをどの様に使用してるかで動作が変わってくると思うので、余りお薦めはしません。

RenderTextureをTexture2Dにする

出来るか出来ないかと言われれば、一応出来ます。ただ、余りお薦めはしません。

流れとしては、こんな感じです。

  • RenderTextureを元にTexture2Dを作る
  • Texture2Dの中身をRenderTextureにする

Texture2DをSpriteにする

Texture2DをSpriteに設定します。
rtがrendertexture、tex2DがTexture2Dです。

var rect = new Rect(0, 0, rt.width, rt.height);
var pivot = new Vector2(0.5f, 0.5f);
var sprite = Sprite.Create(tex2D, rect, pivot);

 rectとpivotはスプライトに使用する範囲と中心点です。これで指定範囲のスプライトが作られます。一応。

後はTexture2DにRenderTextureの中身を流し込みます。

RenderTextureをTexture2Dに流し込む

RenderTextureの中身を元にTexture2Dを作成します。ただ、Texture2DとRenderTextureの両方を作ると無駄なので、RenderTextureのポインタを取得し、そのガワと一致するようにTexture2Dのガワを設定します。

コード的にはこんな感じです。

var ptr = rt.GetNativeTexturePtr();
tex2D = Texture2D.CreateExternalTexture(
            rt.width, rt.height, TextureFormat.ARGB32, false, false, ptr);

次に中身を流し込みます。

var ptr = rt.GetNativeTexturePtr();
tex2D.UpdateExternalTexture(ptr);

 これで、見た目はTexture2D・中身はRenderTextureの名探偵が出来上がります。一応。

ただ、動作は完全にGrahic APIに依存するため、多分安定して動作しません。例えばDirectX11では動きませんでした。またガワはTexture2Dですが中身が違う為か、GetPixel等のTexture2D独自のAPIが使えたり使えなかったりします。また、これは失敗すると「Unityエディタが落ちる」のもお薦めしない理由の一つです。

代替案

今回はSpriteの使用を前提として考えたためRenderTextureをTexture2Dに流し込みましたが、素直にSpriteとか使わず、やはりquad+rendertextureで良い気がします。

描画順はRendererのsortingOrdersortingLayerNameでSpriteと同じような感じに設定できます。その際、マテリアルはTransparent(透明)な奴を使ってください。Transparentシェーダーとか楽で良さそうです。

Unity - スクリプトリファレンス: Renderer.sortingOrder

Unity - スクリプトリファレンス: Renderer.sortingLayerName

テクスチャの差替はMaterialPropertyBlockを使用すれば、かなり低コストで行えます。設定順を少し考えた方が良いかもしれませんが、まぁ。

SetPassを減らすアプローチは、テクスチャのUV変更をメッシュ側でやればOKです。flipも似たような感じで出来ます。まぁRenderTextureを使うようなケースなので、別にここまでしなくても良いとは思いますが。

 

色々と考えましたが、最終的にはこっちの方が楽な気がします。

代替案2

追記を書いてて思い出しましたが、Texture2D.PackTexturesなんてのもありました。これ使えば別にRenderTexture使わなくてもなんとかなったんじゃ感が…
もしくは(Monoのメモリが膨らみそうですが)SetPixelでスタンプみたいに貼りまくるとかでしょうか。
(検証したところ、どちらもTextureにReadWriteを要求する上、GPUに上がってるのが1枚しか結合されてなかったり、少し不思議な挙動を)

SetPass削減は良い事ですが、ソレをするためのコストが本当に適正かと言われれば…どうなんでしょう。少し気にしすぎな気がしなくも。

追記1

このブログ記事を書く際、内容及び表現に不快感を与えてしまった事をお詫び致します。

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

【Unity】WebGLで日本語入力を行う

WebGLは日本語の入力が出来ません。何故ならばIMが使用出来ないからです。
UnityのWebGLでもIMは使用出来ず、キー入力を片っ端から受けソノママ流し込める半角英数字のみが表現可能みたいです。なのでコピペも出来ません。

 

そんな問題があった訳ですが、Unityの入力をHTML側で代替する手段が公開されたみたいです。

WebGLNativeInputField

f:id:tsubaki_t1:20160603020650g:plain

WebGLNativeInputFieldはWebGL上で入力を行う際にダイアログを表示し、そこに入力するシステムです。要するにダイアログに表示したHTMLの入力欄ならばIMEを使用して入力を制御する事も可能という寸法です。

helpdesk.unity3d.co.jp

ちなみに、Androidも同じシステムで入力制御しているっぽいです。入力時にキーボード上に出てくる「入力確認欄」がAndroidネイティブのInputFieldのソレです。

使い方

  • こちらからWebGLNativeInputFieldのUnitypackageを入手し、インポート。
  • GameObject>Create>UI>InpufFieldで、InputFieldを作成。
  • InputFieldコンポーネントを削除し、WebGLNativeInputField.csを設定。

f:id:tsubaki_t1:20160603021640j:plain

f:id:tsubaki_t1:20160603021646j:plain

これでWebGLで入力欄を選択した際、ダイアログが表示されます。また、エディタや他のプラットフォームで実行した場合、通常のWebGLとして振る舞います。

 

また日本語を表示するには「日本語が含まれるフォント」を設定する必要があります。初期設定のArialには英数字しか含まれていないので、WebGLは日本語のフォント表示出来ない為です。
(デモにはM+ FONTS | ABOUTのフォントが含まれていました。フォントをゲームに組み込む場合「再配布」扱いとなるので、使用にはご注意を)

f:id:tsubaki_t1:20160603024335j:plain

問題点

この方法は、現在2つの問題があります。

  • ダイアログを表示中はゲームの進行が停止する。
  • ダイアログを一度キャンセルした後にダイアログ表示を抑制すると、以降ダイアログが(ページを一旦閉じるまで)表示されなくなる。
    (ブラウザ毎に挙動が異なる可能性も)
    Overlay htmlを使う設定で回避出来るみたいです。

f:id:tsubaki_t1:20160603022021j:plain

プロジェクトは現在オープンソースで提供されているみたいです。

github.com

関連

helpdesk.unity3d.co.jp

入力欄の位置にCanvas置いて云々する奴みたいです。

d.hatena.ne.jp

tips.hecomi.com

【Unity】Visual Studioが使えなくなった時の対策

f:id:tsubaki_t1:20160516233442j:plain

Visual Studioを起動したら使えなくなっていたので、使えるようにした過程をメモします。

事象

Visual Studioを起動すると、上の図のようなダイアログが出てきてVisual Studioが操作できなくなりました。これは要するに、Visual Studioの試用期間(30日)が過ぎたので、本登録しろって事です。

対策

左のSign in(アカウントが既にある)もしくはSign Up(アカウントを新規作成)で登録するだけです。なお、これはUnityアカウントではなくMicrosoftのアカウントです。

f:id:tsubaki_t1:20160516234012j:plain

自分はMicrosoftのアカウントを持っているのでSign In。

ログイン作業を進めていくとHost your next project in Visual Studio Team Servicesとか聞かれるので、何も気にせずContinue。問題があったらその時考えます。

f:id:tsubaki_t1:20160516234137j:plain

これでVisual Studioが使えるようになりました。

Visual Studio Community Edition

なお、忘れがちですがこのVisual Studio Community Editionは使用条件があります。

下の条件を満たしていないケースでは、Visual Studioを使えないので、Monodevelop(もしくはそれ以外のIDE)を使用する必要があります。

  • 個人開発者
  • 学習、学術調査目的の開発者
  • オープン ソース開発者
  • 中小規模の企業や団体
    (PC 台数 250 台未満かつ年商 1 億円未満の企業や団体。当該組織は 5 名まで利用可能)

Visual Studio Community - Visual Studio

インストールが面倒くさいなら、とりあえず確実にインストールされてるMonodevelopを設定します。

Edit>Preferences...を選択して、External ToolのExternal ScriptEditorをMonodevelop(Built-in)に設定するだけです。

f:id:tsubaki_t1:20160516235007j:plain

f:id:tsubaki_t1:20160516235016j:plain

まぁ、最近はVisual Studio CodeにUnity対応デバッガが付いたり、その他ツールも色々と便利っぽいので、そっちを選んでも良いかもしれません。

関連

tsubakit1.hateblo.

www.atmarkit.co.jp:

しかし条件があるとはいえ、Visual Studioが無料で使えるとか凄い時代になったものですね…自分はお年玉貯めてVS6.0買ったクチなのですが…