土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

UnityのシェーダーでBINORMAL入力セマンティクスが機能しない話と回避する方法の話

 技術書典4合わせで「ユニティちゃんトゥーンシェーダーをひたすら読む本」というコピー本を作りました。時間の関係で調べ切れなかった事を1個書いておきます。ああ、これも書きたかったな……。シェーダー本2に収録しよう……。

 Unityの頂点シェーダーの入力に、頂点の従法線ベクトルである": BINORMAL"セマンティクスを指定すると、Unity上で以下のようにエラーが発生し、シェーダーがピンクになってしまいます。

Shader error in 'xxxx': Vertex program 'vert': unknown input semantics BINORMAL/0  (on d3d11)

 BINORMAL入力セマンティクスはHLSLの仕様上は存在しており(ただしUnityのドキュメントには書いてありません)、コンパイルも通っているので、これは恐らくUnityの内部処理的な事情(例えば、プラットフォーム間の差異吸収の為とか)なのでしょう。
 接線ベクトルと従法線ベクトルについて、Unityドキュメントには以下のように書かれています。公式訳に微妙な訳し漏れがあるので、私訳を掲載しておきます。

https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html
原文:Tangent and binormal vectors are used for normal mapping. In Unity only the tangent vector is stored in vertices, and the binormal is derived from the normal and tangent values.
公式訳:接線および従法線ベクトルは、法線マッピングに使用されます。Unity では、接線ベクトルは、頂点に格納され、従法線ベクトルは、法線および接線から派生します。
私訳:接線ベクトル及び従法線ベクトルは、法線マッピングを行う際に使用されます。Unityでは、頂点には接線ベクトルのみが格納されており、従法線については、法線と接線の値から導出します。

 法線(float3 normal)と接線(float4 tangent)から従法線(binormal ちなみに従接線(bitangent)と言った場合も同じ物を指すそうです)を算出するのは以下の様な式になります。

float3 binormal = cross( normal, tangent.xyz ) * tangent.w;

 crossは二つのベクトルから外積を取ります。法線と接線の外積を取れば従法線を取得できます。tangentのw要素には、プラットフォームの右手系/左手系を示す1or-1が入っているので、これを乗算して反映させます(OpenGLベースなのか、DirectXベースなのかで右手系/左手系が変わります)。
 このtangent.wのことをhandedness(利き手)と呼ぶようです。これは右手系/左手系を両方扱うUnity固有の概念で、ドキュメントでは下記ページのコードのコメントに書いてあります。
https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

// in Unity tangents are 4D vectors, with the .w component used to
// indicate direction of the bitangent vector.

私訳:Unityにおいては接線(tangent)は.wを持つ4次元ベクトルで、従接線(bitangent)ベクトルの方向を示すために使用されます。

参考:tangent.wについて海外フォーラムでの議論
https://forum.unity.com/threads/what-is-tangent-w-how-to-know-whether-its-1-or-1-tangent-w-vs-unity_worldtransformparams-w.468395/