2012/05/10

Unity ShaderLabでのあれこれ

 最近Unityを触っています。ShaderLabでiOS向けシェーダーを書いているのですが、その中で気づいたことなど書いてみます。

●モバイル向けシェーダーの心得

 各種Unity勉強会に出ましたけど、iOS等モバイル向けシェーダーではリアルタイムライティングはご法度ということ。普通はバーテックスシェーダー内でライトマップを参照したり、ライトプローブを参照して色を取ってきてフラグメントシェーダーで色を載せるということをやるようです。
 いい例となるのがShadowGunのシェーダーサンプル。このアプリではリアルタイムライティングは完全に行っていませんでした。背景など動かないものはライトマップの参照がメイン。カメラからの相対ベクトルでの擬似スペキュラーライティングを若干与えているので、これについては平行光源での頂点ライティングと同じくらいの処理負荷はかかるものと思われます。キャラでは、ライトプローブ参照と擬似スペキュラーライティングでした。
  iOSでのシェーダーサイクルは以下の数値。(PVRUniSCoEditor調べ)

背景用シェーダー
 vert cycles: 72
 frag cycles: 6

キャラ用シェーダー
 vert cycles: 86
 frag cycles: 28

 となっています。背景のフラグメントシェーダーが軽いことがよく分かります。6cyclesという数値はUnity標準のMobile/Unlit(Support Lightmap)での7cyclesよりも軽い数値なのが驚きです。

 速いシェーダーを書くには、なるべく演算精度を下げるのが良いです。floatなんで言語道断。halfもかなり絞りたいです。fixedがほとんどになると思います。あとは、あたりまえですけど、演算コード量を減らすことですね。
 さらに、コツとしてフラグメントシェーダーは描画面積に対して掛け算で重くなっていくので、バーテックスシェーダーで事前に処理出来る部分はなるべくそっちで計算させておくというのもいいです。ShadowGunのシェーダーでバーテックスシェーダーのサイクル数が多めなのはそのせいですね。
 ちなみに、バーテックスシェーダーで事前計算した値はもちろん頂点毎の情報となりますが、フラグメントシェーダーへ値が受け渡された時点で頂点間で値が補間されますので、まあまあ良い感じになりますよ。


●Unityでのバッドノウハウ

 ライティングがご法度ということでなるべくライティング計算を行いたくないのですが、ライトカラーだけは参照してどうにかしたいとか、ライトベクトルだけ参照して良い感じに使いたいとかあると思います。
 で、普通はSurfaceシェーダーを使ってライティング計算を書くわけなんですけど、Unity内部で何が行われているかわからないので、無駄を省くのが難しいとかありますよね。

ライトカラー参照
 てなわけで、以下のようなコードで直接参照したくなります。

SubShader {
  Pass {
    Lighting Off
  }
  CGPROGRAM
  #pragma vertex vert
  #pragma fragment frag
  #include "UnityCG.cginc"
  struct v2f {
    float4 pos : SV_POSITION;
    fixed2 uv : TEXCOORD0;
    fixed3 col : COLOR;
  }
  v2f vert(appdata_base v) {
    v2f o;
    頂点演算は省きます。
    o.col.rgb = _LightColor0.rgb;
  }
  フラグメントシェーダーは省きます。
  ENDCG
}

 でも、なんかライトの色が取得できないことがありますね。Lighting Offが効いているみたいです。 というわけで、一応ライティングしますよって宣言してやらないと色が取れないみたいです。Passの前にTagで指定します。
Tag {
  "LightMode" = "Vertex"
}

 みたいにやります。すると色がちゃんと取れます。ちなみに、unity_LightColor[0].rgbでも取得できます。cgincを読むとこっちのほうが後々まで使用できる宣言みたいですお。

ライトベクトルが取りたい
 はい。これなんですけど、公式ドキュメントに書かれている_ObjectSpaceLightPosが宣言されていないらしく、一見取得出来ないように見えます。なので、しかたなくSurfaceシェーダーでやるしかないかなぁとか思うのですけど、unity_LightPosition[0].xyzで行けます。

実機でGLSLコンパイルが通らない!
 なんかね、Unity上では正しくシェーダーがコンパイルできて表示も問題なくても、XCodeで実機ビルドすると実機での実行時にGLSLが正しくコンパイルされないことがあるんですよ。原因はバーテックスシェーダーでのVaryingパラメータの命名が同名にされてしまうことがあるということ。
 現象としては、このコミュニティでの記事と同じです。xlv_という変数が大量に宣言されちゃうみたいなんですね。
 これを解消するには、struct v2fでの中身にsemantic nameで何らかの指定をつけておくと良いというもの。
struct v2f {
  float4 pos : SV_POSITION;
  fixed2 uv : TEXCOORD0; <これとか
  fixed custom : TEXCOORD1; <これとか
  half4 custom2; <これはだめ
}

 みたいな感じです。カッコ悪いしなんだかなぁと思いますけど、これで実機実行時には、xlv_TEXCOORD1とかで参照されることになって名前がかぶらなくなるわけです。…ひどい。ひどすぎるぜUnity!

実機だと描画結果が違う!
 MacとかでUnity Editor上でみてる描画結果と実機での描画結果が違うことがあります。特にfixed変数を使っているところで。まあ、これは普通にGLSLを書いてOpenGLESでやってるときにも起こることですけど。
 この場合は、fixedの部分をhalfに置き換えていくと描画結果が一致します。どこを戻さないといけないのか試して少しづつやるといいです。なるべくfixedを使ったほうが軽いですから。
 ちなみに、GLSLベタ書きの場合は、lowpが問題になってます。mediumpに変えていくと直ります。同じ事ですね。


 以上です。最近苦労してたことが大体わかってきたので、まとめておきました。Have a nice shader life!

1 件のコメント:

  1. 追記ですが、LightModeについては場合によってはVertex指定だとまずいこともあるっぽいので、Alwaysとかいろいろ必要に応じて設定を変える必要がありそうです。

    返信削除