導入
初心者ながら、VRゲームを作りたくなって、いろいろアセットを調べていたら、「Japanese Otaku City」というアセットを見つけました。
https://www.assetstore.unity3d.com/jp/#!/content/20359
秋葉原の街並みや道路や線路が再現されていて、しかも無料!これ使ったら面白いゲーム作れそう〜と思いダウンロードしました。
調べてみると、この現象は有名らしく、修正方法がアセットストアにコメントされていたり、いくつかのブログで言及されていたりします。
http://fantom1x.blog130.fc2.com/blog-entry-170.html
http://nobikko-nobinobi.hatenablog.com/entry/2016/02/20/234153
しかし、どこも解決方法である
「void vert (inout appdata_full v, out Input o) {
の下に、UNITY_INITIALIZE_OUTPUT(Input,o);
を加える」
としか書いておらず、なぜそれをすると直るのかが、初心者としては理解できませんでした...
そこで今回はなぜこれで直るのかという点について詳しく見ていこうと思います。
(ちなみに、自分はUnity5、Unity2017を使って見てみましたが、どちらもピンク色になりました。)
なぜピンクになってしまうのか
そもそもなぜピンク色になってしまうのでしょうか。
ピンク色になってしまう原因は、以下のブログで詳しく説明されています。
http://tsubakit1.hateblo.jp/entry/2017/03/30/090000
この現象を発生させるためには、モデル表現に使用するMaterialや、Materialが使用するShaderの参照が見つからない(missing)状態や、シェーダーが動作しない場合*1等があります。
Materialやシェーダーへの参照が外れていたり、シェーダー自体にエラーがあったりなどする場合に、Unityではピンク色で表示されるということらしいです。
今回は、「シェーダー自体にエラーがある」というケースに該当すると思われます。
(シェーダーについては、以下のブログがとても役に立ちました。
http://www.shibuya24.info/entry/2016/06/04/234031)
なぜこれで直るのか
なぜ直るのかを調べるために、まずはUNITY_INITIALIZE_OUTPUT(Input,o);
の意味から見ていこうと思います。
UNITY_INITIALIZE_OUTPUT
UNITY_INITIALIZE_OUTPUT
の意味は、ドキュメントに書いてありました。
https://docs.unity3d.com/ja/540/Manual/SL-BuiltinMacros.html
指定された 型 の変数 name を 0 に初期化します。
型と変数名を指定するとその変数を0に初期化してくれるマクロということらしいです。
つまり、UNITY_INITIALIZE_OUTPUT(Input,o);
はInput
型のo
という変数を0に初期化しているということになります。
ということは、わざわざこのマクロを使わなくても0に初期化さえできればいいので、直接0に初期化するコードを書いてもいいわけです。
例えば、su_VertexCol_1UV_Single.shaderでは、74~78行目にInput型が定義されています。
struct Input {
float2 uv_MainTexture;
float4 color : COLOR;
};
これを0で初期化するために、以下のように書いてもマクロを使うのと同値ということになります。
void vert (inout appdata_full v, out Input o) {
o.uv_MainTexture = float2(0, 0);
o.color = float4(0, 0, 0, 0);
su_Double_and_Clip.shaderの方でも同様です。
これでUNITY_INITIALIZE_OUTPUT
の意味はわかりました。
しかし、なぜ変数の初期化が必要なのでしょうか。
シェーダーコンパイラの変遷
変数の初期化が必要になった理由をいろいろ調べてみると、Unity内部で使用しているシェーダーのコンパイラが変わったことに関係があるようです。
https://docs.unity3d.com/jp/530/Manual/UpgradeGuide5-Shaders.html
D3D9 シェーダーコンパイラーは Cg 2.2 から HLSL に変更
出力変数はすべて初期化する必要があります。D3D11 でしていたのと同じように UNITY_INITIALIZE_OUTPUT ヘルパーマクロを使用してください。
D3D9というのはDirect3D 9の略で、Direct3Dとは3Dグラフィックスを描画するためのAPIのことです。
出力変数というのは、vert関数の引数についているout
というキーワードのことです。詳細は以下を参照してください。
https://msdn.microsoft.com/ja-jp/library/bb509606(v=vs.85).aspx
Unity4から5にグレードアップした際、Direct3D 9で使っているコンパイラがCgコンパイラからHLSLコンパイラに変わったことで、文法に違いが出てきたようです。
(ちょっとここらへん自信ないです...)
Japanese Otaku CityはUnity 4.3.4を用いて作られているので、おそらくそのときはD3D9を使っていて問題なかったものの、Unity5が登場したことでシェーダーの文法が変わり、エラーがでるようになってしまったということのようです。バージョン1.0から更新されていないので、特にメンテナンスされていないのでしょう...
ちなみに、D3D11の方に対しては、Unity4からUNITY_INITIALIZE_OUTPUTを使いましょうということがリリースノートに書かれていました。
https://unity3d.com/jp/unity/whats-new/unity-4.0
UNITY_INITIALIZE_OUTPUT(type,name) to help with DX11 shader compiler requiring full initialization of "out" parameters.
そしてさらに、先月7月10日には、Windows XPのサポート終了とともに、Unity2017.3からWindows環境でのDirectX 9のサポートを終了するという告知もでていました。
https://blogs.unity3d.com/jp/2017/07/10/deprecating-directx-9/
これからは出力変数にはUNITY_INITIALIZE_OUTPUT
をつけるのが普通になるということですね。
詳しいコンパイラの内部とかまでは調べられていませんが、ここらへんの経緯がわかっただけでもだいぶすっきりしました。
これでようやくなぜUNITY_INITIALIZE_OUTPUT
をつけるとピンク色なテクスチャが直るのかがわかってきました。
ついでなので、vert関数についてもう少し詳しく見てみようと思います。
void vert (inout appdata_full v, out Input o)
void vert
この関数がどんな関数なのかは以下のブログ、ドキュメントを参照するとわかりやすいです。
http://tsumikiseisaku.com/blog/shader-tutorial-vfshader/
https://docs.unity3d.com/ja/current/Manual/SL-SurfaceShaders.html
#pragma surface surfaceFunction lightModel [optionalparams]
vertex:VertexFunction - カスタム頂点修正関数。この関数は、頂点シェーダーの作成の開始時に呼び出され、頂点データごとに修正や計算を行うことができます。
su_VertexCol_1UV_Single.shaderの中身を参考に見てみると、30行目にドキュメントと同じようなことが書いてあります。
#pragma surface surf BlinnPhongEditor vertex:vert
つまり、30行目でこのシェーダーに対するサーフェスシェーダーが定義されていて(surf関数)、そのサーフェスシェーダーのオプションとしてvert関数を頂点シェーダーとして指定しているということみたいです。
inout appdata_full v
引数の意味も見ていきます。
inout
inout
は、HLSLのキーワードで、入力にも出力にも使える変数であることを示すための修飾子です。詳細は以下に書かれています。
https://msdn.microsoft.com/ja-jp/library/bb944006(v=vs.85).aspx
https://msdn.microsoft.com/ja-jp/library/bb509606(v=vs.85).aspx
appdata_full
appdata_full
については以下に詳細が書かれています。
https://docs.unity3d.com/jp/530/Manual/SL-VertexProgramInputs.html
頂点データの流し込みは、1つずつデータを渡す代わりに、しばしば 1つの構造体として宣言されます。一般的に使用される頂点構造体は、UnityCG.cginc の include ファイル で定義され、たいていそれだけで十分です。構造体には以下のものがあります。
appdata_full: 位置、接線、法線、4 つのテクスチャ座標および色で構成されています。
つまり、appdata_full
とは、頂点シェーダー関数であるvertに対して、頂点データを渡すための構造体の一種ということになります。
ちなみに、appdata_fullの中身は、/Applications/Unity/Unity.app/Contents/CGIncludes/UnityCG.cginc
にあります。
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
位置(POSITION)、接線(TANGENT)、法線(NORMAL)、4 つのテクスチャ座標(TEXCOORD0~3)および色(COLOR)、で確かに構成されていますね。
out Input o
Input
については前述の通りです。
out
も前述の通りですが、inout
と同じ修飾子のひとつで、出力用の変数であることを示します。
まとめ
- なぜピンク色になってしまうのか?
- 今回はシェーダーにエラーがあり、Unityがそれを知らせてくれていた
- なぜこの方法で直るのか?
- やっていることは変数の初期化
- Unity内部でのシェーダーのコンパイラがUnityのバージョンと共に変わり、出力変数に対して初期化が必須になった
- vert関数とはなんなのか?
- 頂点シェーダーを書くための関数
- モデルの頂点に対してなにかしらの処理を行う
- モデルの頂点データと、出力用の変数を受け取っている
- 頂点シェーダーを書くための関数
Unityわかる人にとっては当たり前なことだったかもしれませんが、検索してもなかなか「なぜ直るのか」について調べている方がいらっしゃらなかったので、同じ疑問を持った方のお役に立てたら幸いです。