syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

HLSLの行列乗算がmul()関数な理由

GLSLでは行列の乗算に*演算子を使います。

// GLSL
mat4 mTransformA;
mat4 mTransformB;
vec4 vIn;
vec4 vOut = mTransformB * mTransformA * vIn;

一方HLSLではmul()関数を使います。

// HLSL
float4x4 mTransformA;
float4x4 mTransformB;
float4 vIn;
float4 vOut = mul(mul(vIn, mTransformA), mTransformB);

ときどき「なんでHLSLは乗算が*演算子じゃなくてmul()関数なんだ? オブジェクト指向(というかジェネリックプログラミング)的に言えば*演算子を使うべきだろう」という議論を見かけるのですが、HLSLにも次元のまったく同じ行列 or ベクトル同士に対する*演算子はあります。しかしこれは実は「一般的な行列の乗算」ではなく、単純に各成分を掛け合わせるだけの演算になります。

// HLSL
float4 vPosA;
float4 vPosB;
float4 vStarAB = vPosA * vPosB;
float4x4 mTransformA;
float4x4 mTransformB;
float4x4 mStarAB = mTransformA * mTransformB;

つまり、HLSLで*演算子を使った結果は

vStarAB =
{
  vA.x * vB.x, vA.y * vB.y, vA.z * vB.z, vA.w * vB.w
};

mStarAB =
{
  mA._11 * mB._11, mA._12 * mB._12, mA._13 * mB._13, mA._14 * mB._14,
  mA._21 * mB._21, mA._22 * mB._22, mA._23 * mB._23, mA._24 * mB._24,
  mA._31 * mB._31, mA._32 * mB._32, mA._33 * mB._33, mA._34 * mB._34,
  mA._41 * mB._41, mA._42 * mB._42, mA._43 * mB._43, mA._44 * mB._44,
};

となります。これは一般的な代数学でよく使われる行列の乗算ではありません。また、この演算は結果がオペランド(被演算数)の順序に依存しません(交換法則が成り立つ)。
この成分ごとの乗算は、「アダマール積」(Hadamard product) または「シューア積」(Schur product) などと呼ばれているそうです。

おそらくHLSLの設計としては、順序依存性のある一般的な行列積は演算子で提供したくなかったのだと思われます。HLSLの原型となったNVIDIA Cg言語も行列の乗算はmul()関数です*1。一方、D3DXやXNAには、C++/C#向けの乗算演算子オーバーロードがありますが、使い方を誤ると混乱のもとになります。

GLSLを模倣したC++用の数学ライブラリGLM (OpenGL Mathematics) にも乗算演算子オーバーロードがあります。

ちなみに個人的な趣向としてはHLSLのほうが好みです。