koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

シェーダーにおけるゼロベクトルの正規化

シェーダーにおいて,組み込み関数である normalize() にゼロベクトルを渡した場合,NaN になる. 正規化ベクトルの定義からも当然である.

\begin{equation} normalize(\boldsymbol{v}) = \dfrac{\boldsymbol{v}}{\| \boldsymbol{v} \|} \end{equation}

シェーダーとしては normalize() は下記の実装と同等である. float3 を一例としているが,float2float4 等でも同様である.

float3 normalize(float3 v)
{
    return rsqrt(dot(v, v)) * v;
}

rsqrt() は逆数平方根の関数であり,高速に逆数平方根を計算する rsq 命令となるが,この命令は 0 に対し, Inf を返す. その結果, Inf0 の積となるため, normalize() の計算結果は NaN となる.

ゼロベクトルに対してはゼロベクトルを返すnormalizeを定義する

NaN は取り扱いづらいため,ゼロベクトルを正規化しようとした場合は特殊扱いでゼロベクトルを返すようにした方が都合が良いこともある. そこで,前述の normalize() の実装を少し変更する.

float3 normalizeEx(float3 v)
{
    const float vdotV = dot(v, v);
    return vdotV == 0.0 ? v : rsqrt(vdotV) * v;
}

ただ,基本的に浮動小数点数の等値比較は避けるべきであるのと,Unity C#Vector2, Vecotr3, Vector4normalized は長さが微小値(1.0e-5 以下)のベクトルに対してはゼロベクトルを返す実装となっているので,それに習い下記のようにした方がよいかもしれない.

float3 normalizeEx(float3 v)
{
    const float vdotV = dot(v, v);
    return vdotV <= 1.0e-10 ? float3(0.0, 0.0, 0.0) : rsqrt(vdotV) * v;
}

参考文献