同次座標の成分の指数部を補正する
3Dなどの座標計算のプログラミングをしていてこんなことを考えました。3D座標(x, y, z)を同次座標「(X, Y, Z) / W」として、可能な限り同次座標で計算を続けるのがよいのか、たびたびW = 1の普通の座標に戻して計算をするのがよいのかということです。
仮に同次座標を保った方がよい場合があるとして、長い計算をするうちに、「(X, Y, Z) / W」の分子と分母が共に小さな値や大きな値に偏って(実数の指数部の大きな負数や正数になって)しまう恐れがあるのではないかと気になりました。
そんなことから、どの程度のコードとなるか、同次3Dベクトルの成分の指数部だけを補正するメソッドを書いてみました。W = 1にする計算より高速であれば、使いどころがあるかも知れません。
C++ (C++/CLI)
#include "stdafx.h" using namespace System; #include <math.h> struct H { float X, Y, Z, W; H(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } void ExponentNormalize() { int ex, ey, ez, ew; float fx = frexp(X, &ex); float fy = frexp(Y, &ey); float fz = frexp(Z, &ez); float fw = frexp(W, &ew); X = ldexp(fx, ex - ew); Y = ldexp(fy, ey - ew); Z = ldexp(fz, ez - ew); W = ldexp(fw, 0); } }; int main(array<System::String ^> ^args) { H v = H(3.14159265f * 1e+20f, -1e+20f, 0, 2 * 1e+20f); Console::WriteLine(L"source: ({0}, {1}, {2}) / {3} = ({4}, {5}, {6})", v.X, v.Y, v.Z, v.W, v.X / v.W, v.Y / v.W, v.Z / v.W); v.ExponentNormalize(); Console::WriteLine(L"result: ({0}, {1}, {2}) / {3} = ({4}, {5}, {6})", v.X, v.Y, v.Z, v.W, v.X / v.W, v.Y / v.W, v.Z / v.W); return 0; }
実行結果
source: (3.141593E+20, -1E+20, 0) / 2E+20 = (1.570796, -0.5, 0)
result: (1.064413, -0.3388132, 0) / 0.6776264 = (1.570796, -0.5, 0)
HLSL
float4 hexponentnormalize(float4 value) { float4 e; float4 f = frexp(value, e); return ldexp(f, float4(e.xyz - e.www, 0)); }