👹

法線のブレンドについて調べてみた

2023/03/19に公開

動機

UnityのShaderGraphにはNormal Blend NodeというNodeが存在します。
法線をブレンドしてくれるということは名前から想像できるのですが、同ブレンドするのか?
ブレンドしたらなにが嬉しいのか?がわからなかったので調べてみました。

そもそもNormal Blend Nodeには二つのブレンド方法があります。
一つはDefaultです。中身はDocumentsに記載の通り下記のようになっています。

defaultBlend
void Unity_NormalBlend_float(float3 A, float3 B, out float3 Out)
{
    Out = normalize(float3(A.rg + B.rg, A.b * B.b));
}

それほど、複雑なことはしてないみたいです。二つのベクトルのr,g成分(xy成分)を加算して、b成分(z成分)を乗算してできたベクトルを正規化しているだけです。

続いてReorientedです。同様にドキュメントより下記の処理を行っています。

reoriented
void Unity_NormalBlend_Reoriented_float(float3 A, float3 B, out float3 Out)
{
    float3 t = A.xyz + float3(0.0, 0.0, 1.0);
    float3 u = B.xyz * float3(-1.0, -1.0, 1.0);
    Out = (t / t.z) * dot(t, u) - u;
}

まず、AベクトルにZ成分だけ1を加算します。そのあと、BベクトルのXY成分の向きを逆にしています。最後に、新しくできたtベクトルを自身のz成分で除算して、その値にtベクトルとuベクトルの内積を乗算した後にuベクトルを減算して、新しくベクトルを作成しています。

実際のブレンドの仕方は理解できましたが、それぞれどういう使い分けをしたらよいか正直わからなかったです。

もう少し調べてみると、法線のブレンドの手法には他にもたくさんあるみたいなのでそれを見てみます。

Linear Blending

float3 LinearBlending(float3 A, float3 B) 
{
  float3 n1 = A *2 - 1;
  float3 n2 = B *2 - 1;
  float3 r  = normalize(n1 + n2);
  return r;
}

正規化された2つの法線A,Bをそれぞれ-1~1の範囲に変換します。それを加算して、正規化することで新しい法線を作成します。
とても簡単ですが、平坦化することもあるので良い結果が得られないことも。。

Overlay Blending

float3 OverlayBlending(float3 A, float3 B) 
{
  float3 r  = A < 0.5 ? 2 * A * B : 1 - 2 * (1 - A) * (1 - B);
  r = normalize(r * 2 - 1);
  return r;
}

Linear Blendingよりは全体的に改善するために、二つの法線が重なってそうな部分は強い影響が与えられるように工夫されています。

Partial Derivative Blending

float2 pd = n1.xy/n1.z + n2.xy/n2.z; 
float3 r  = normalize(float3(pd, 1));
return r;

二つの法線の勾配をそれぞれもとめて加算した値を法線として利用します。
これにより、元の法線の詳細が担保されてブレンドされます。

White Out Blending

float3 r = normalize(float3(n1.xy + n2.xy, n1.z * n2.z));

UnityのShaderGraphのDefaultはWhiteOutBlendingと呼ばれるブレンド方法のことみたいだと分かりました。ひとつ前のPdブレンドの発展してできたみたいだが、どのような目的や経緯があったのかまではわからなかったです。

UDN Blending

float3 r = normalize(float3(n1.xy + n2.xy, n1.z));

これはWhiteOutBlendingのZ成分同志の乗算やめただけです。その分、パフォーマンスを節約できるが、クオリティもその分下がる可能性があります。(調べてないので分かりませんが、UはUnrealEngineのUなのでUEではこの方法でブレンドする機能があるのだと思う。)

Re-Oriented Normal Blending

float3 blend_rnm(float4 n1, float4 n2)
{
    float3 t = n1.xyz * float3( 2,  2, 2) + float3(-1, -1,  0);
    float3 u = n2.xyz * float3(-2, -2, 2) + float3( 1,  1, -1);
    float3 r = t * dot(t, u) - u * t.z;
    return normalize(r);
}

UDNやWhiteOutBlendingには平坦化してしまうケースなどが存在したり、数字的な根拠がないBlendingだったのでこれが改善できるみたいです。
これは、接空間の法線ベクトルを別の座標系に変換してから法線ベクトルをブレンドします。ブレンド後、接空間に再度法線を戻すことで実現できるみたいです。

調べてみてわかったこと

  • 法線のブレンドには手法がたくさん存在するものの、パフォーマンスと質の良さから、よく使用されるのはUDNBlendingとWhiteOutBlending
  • LinearBlendingとOverlayBlendingは昔の手法であんま使われていない。
  • ReorientedNormalBlendingはクオリティ重視のブレンディングだが、パフォーマンスは一番悪い。ShaderGraphのReoritendモードも細かいコード内容は違ったがこのアプローチなはず。

参考

https://docs.unity3d.com/Packages/com.unity.shadergraph@6.9/manual/Normal-Blend-Node.html
https://blog.selfshadow.com/publications/blending-in-detail/

Discussion