ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

【Unity】【URP】接空間まとめ

接空間とは

ポリゴンの表面を基準とした座標空間です。

ローカル空間の法線を全て面に対して垂直にした時、ポリゴンの向きによって法線の値が変わりますが、接空間ではポリゴンの向きに影響されません。

うにばな(ノーマル(法線)基本 と ノーマルマップ合成) : Yaminabe引用

行列変換

NormalMapの法線は接空間に存在するので、ローカルやワールド空間にあるベクトルと計算する際は同じ座標空間に合わせる必要があります。

接空間の座標変換には以下の3つベクトルを使用します。

  • 法線(normal) 正面方向
  • 接線(tangent) 右手方向
  • 従法線(binormal) 頭上方向

うにばな(ノーマル(法線)基本 と ノーマルマップ合成) : Yaminabe引用

接空間からワールド(ローカル)空間への変換行列

【Unity】接空間について - コポうぇぶろぐ引用

ワールド(ローカル)空間から接空間への変換行列(逆行列にしただけ)

【Unity】接空間について - コポうぇぶろぐ引用

NormalMapを取得する流れ

①法線、接線、従法線をワールド空間に変換

VertexNormalInputs normalInput = GetVertexNormalInputs(input.normal, input.tangent);
VertexNormalInputs GetVertexNormalInputs(float3 normalOS, float4 tangentOS)
{
    VertexNormalInputs tbn;

    real sign = tangentOS.w * GetOddNegativeScale();
    tbn.normalWS = TransformObjectToWorldNormal(normalOS);
    tbn.tangentWS = TransformObjectToWorldDir(tangentOS.xyz);
    tbn.binormal WS = cross(tbn.normalWS, tbn.tangentWS) * sign;
    return tbn;
}

従法線の変換は、法線と接線の外積で求められますが、反転するケースがあるので、必要に応じて-1を乗算します。

real sign = tangentOS.w * GetOddNegativeScale();
tangentOS.w

外積の性質上、左手系のプラットフォームだと頭上方向とは反対を向いてしまいます。

tangentOS.wはプラットフォームの右手系/左手系を示す1or-1が入っているので、これを乗算します。

marupeke296.com

GetOddNegativeScale

TransformのScaleによって反転するケースがあります。 unity_WorldTransformParams.wにはスケールのXYZのいずれかが反転していた場合は -1 が入ります。

https://cdn-ak.f.st-hatena.com/images/fotolife/c/coposuke/20201221/20201221055351.gif

【Unity】接空間について - コポうぇぶろぐ引用

real GetOddNegativeScale()
{
    return unity_WorldTransformParams.w >= 0.0 ? 1.0 : -1.0;
}

②ノーマルマップ取得

half3 normal = UnpackNormal(tex2D(_NormalMap, i.uv));

テクスチャに0〜1で書き込まれている法線情報を -1〜1 の値に変換します。

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#else
    return UnpackNormalDXT5nm(packednormal);
#endif
}

③ノーマルマップの法線をワールド空間に変換

前述した接線、従法線、法線で作った変換行列を使います。

normalWS = TransformTangentToWorld(normal, half3x3(tangentWS, binormalWS, normalWS));
real3 TransformTangentToWorld(real3 dirTS, real3x3 tangentToWorld)
{
    return mul(dirTS, tangentToWorld);
}

参考

coposuke.hateblo.jp