9

The parameters for HLSL's mul( x, y) indicated here: say that

  • if x is a vector, it is treated as a row vector.
  • if y is a vector, it is treated as a column vector.

Does this then follow through meaning that:

a.

  • if x is a vector, y is treated as a row-major matrix
  • if y is a vector, x is treated as a column-major matrix

b.

since ID3DXBaseEffect::SetMatrix() passes in a row-major matrix, hence I'd use the matrix passed into the shader in following order:

ex. Output.mPosition = mul( Input.mPosition, SetMatrix()value ); ?

I'm just starting out with shaders and current relearning my matrix math. It would be nice if someone could clarify this.

dk123
  • 18,684
  • 20
  • 70
  • 77

2 Answers2

22

No. The terms "row-major" and "column-major" refer purely to the order of storage of the matrix components in memory. They have nothing to do with the order of multiplication of matrices and vectors. In fact, the D3D9 HLSL mul call interprets matrix arguments as column-major in all cases. The ID3DXBaseEffect::SetMatrix() call interprets its matrix argument as row-major, and transposes behind the scenes to mul's expected column-major order.

If you have a matrix that abstractly looks like this:

[ a b c d ]
[ e f g h ]
[ i j k l ]
[ m n o p ]

then when stored in row-major order, its memory looks like this:

a b c d e f g h i j k l m n o p

i.e. the elements of a row are all contiguous in memory. If stored in column-major order, its memory would look like this:

a e i m b f j n c g k o d h l p

with the elements of a column all contiguous. However, this has precisely zero effect on which element is which. Element b is still in the first row and second column, either way. The labeling of the elements has not changed, only the way they're mapped to memory.

If you declare an array like float matrix[rows][cols] in C, then you are using row-major storage. However, some other languages, like FORTRAN, use column-major storage for their multidimensional arrays by default; and OpenGL also uses column-major storage.

Now, entirely separately, there is another choice of convention, which is whether to use row-vector or column-vector math. This has nothing at all to do with the memory layout of matrices, but it affects how you build your matrices, and the order of multiplication. If you use row vectors, you'll do vector-matrix multiplication:

            [ a b c d ]
[x y z w] * [ e f g h ] = [x*a + y*e + z*i + w*m, ... ]
            [ i j k l ]
            [ m n o p ]

and if you use column vectors, then you'll do matrix-vector multiplication:

[ a b c d ]   [ x ]
[ e f g h ] * [ y ] = [x*a + y*b + z*c + w*d, ... ]
[ i j k l ]   [ z ]
[ m n o p ]   [ w ]

This is because in row-vector math, a vector is really a 1×n matrix (a single row), and in column-vector math it's an n×1 matrix (a single column), and the rule about what sizes of matrices are allowed to be multiplied together determines the order. (You can't multiply a 4×4 matrix by a 1×4 matrix, but you can multiply a 4×4 matrix with a 4×1 one.)

Note that the matrix didn't change between the two equations above; only the interpretation of the vector changed.

So, to get back to your original question:

When you pass a vector to HLSL's mul, it automatically interprets it "correctly" according to which argument it is. If the vector is on the left, it's a row vector, and if it's on the right, it's a column vector.

However, the matrix gets interpreted the same way always. A matrix is a matrix, regardless of whether it's being multiplied with a row vector on the left or a column vector on the right. You can freely decide whether to use row-vector or column-vector math in your code, as long as you're consistent about it. HLSL is agnostic on this point, although the D3DX math library uses row vectors.

And it turns out that for some reason, in D3D9 HLSL, mul always expects matrices to be stored in column-major order. However, the D3DX math library stores matrices in row-major order, and as the documentation says, ID3DXBaseEffect::SetMatrix() expects its input in row-major order. It does a transpose behind the scenes to prepare the matrix for use with mul.

BTW, D3D11 HLSL defaults to column-major order, but allows you to use a compiler directive to tell it to use row-major order instead. It is still agnostic as to row-vector versus column-vector math. And OpenGL GLSL also uses column-major order, but does not (as far as I know) provide a way to change it.

Further reading on these issues:

Nathan Reed
  • 3,583
  • 1
  • 26
  • 33
  • OpenGL allows you to specify row/column-major ordering per-uniform using a `layout` qualifier. It is not often useful since the GL API already lets you transpose matrix uniforms when you send them, but it can be useful for uniform buffers. Unfortunately it ***only*** applies to uniforms. A `mat4` constructor, for instance, will always take its components in column-major order and there is no layout qualifier or anything that can currently alter that behavior. – Andon M. Coleman Jun 16 '14 at 03:52
  • By the way, are you sure `ID3DXBaseEffect::SetMatrix()` actually transposes the matrix before sending it to HLSL? If it did that, then the matrix would arrive in column-major order and then post-multiplication (matrix * column vector) would be the appropriate course of action. – Andon M. Coleman Jun 17 '14 at 19:25
  • @AndonM.Coleman Don't confuse row-major vs column-major storage with row-vector vs column-vector math. They're two distinct choices of convention. Row-vector vs column-vector affects the order of multiplication; row-major vs column-major affects the storage of matrix components in memory. As I said, the default storage order in HLSL is column-major, while that in the CPU-side D3DX math library is row-major. Therefore, `SetMatrix()` must transpose the matrix, irrespective of whether you use pre- or post-multiplication in your code. – Nathan Reed Jun 17 '14 at 20:03
  • Well, if you pass a row-major matrix to something that expects column-major element order that is the same thing as passing the transpose. Thus, if you transpose a row-major matrix, you have the equivalent matrix stored in column-major layout. And pre-multiplying the transpose of a matrix is the same as post-multiplying the original. – Andon M. Coleman Jun 17 '14 at 20:05
  • @AndonM.Coleman Yes, if you flip *both* choices of convention at once, then everything works - which is why this topic tends to be so confusing. :) However, I'm saying that the order in which HLSL `mul()` reads the matrix components from memory is consistent with column-major storage, regardless of the order of multiplication (matrix * vector or vector * matrix). – Nathan Reed Jun 17 '14 at 20:11
  • Oh, I thought you were saying that `ID3DXBaseEffect::SetMatrix ()` actually transposes the matrix before it sends it to the shader. You mean that because it is passed in row-major storage, but `mul (...)` reads it column-major that it is implicitly transposed. – Andon M. Coleman Jun 17 '14 at 22:20
  • @AndonM.Coleman No! It *does* actually transpose the matrix. :) That way you can keep the same multiplication order between D3DX and HLSL. If it didn't transpose, you would have to change the multiplication order to compensate. – Nathan Reed Jun 18 '14 at 01:33
  • Really? Where is this documented? I do not doubt your word mind you, I just never came across any mention of that behavior. The closest I could find was `ID3DXBaseEffect::SetMatrixTranspose()`. – Andon M. Coleman Jun 18 '14 at 01:34
  • 1
    @AndonM.Coleman You can look at the source code for the D3D11 effects library to see it. You can also look at the disassembly for HLSL shaders that do matrix multiplication, to see that `mul()` treats the matrix as being in column-major order. – Nathan Reed Jun 18 '14 at 01:35
  • Yeah, I know that much for `mul ()` already, but I guess I will have to dig around in the source code as you suggest - thank you, I'll poke around in it some tomorrow. – Andon M. Coleman Jun 18 '14 at 01:36
  • 1
    @NathanReed I believe what you said "If the vector is on the right, it's a row vector, and if it's on the left, it's a column vector." is wrong. The converse is correct. In fact, mul(x,y)'s documentation says: "If x is a vector it is treated as a row vector". Yeah, I'm not talking about the handedness; I'm talking about the memory representation as you have explained. – KeyC0de Sep 17 '20 at 13:44
  • @Nikos Thanks, you're right, I misspoke in that sentence. Fortunately I said it the right way around in the next paragraph! – Nathan Reed Sep 17 '20 at 20:31
7

Yes, if x is a vector then x is treated as a row major vector and y is treated as a row major matrix; vice versa for column major so for a row-major matrix system:

float4 transformed = mul(position, world);

and for column-major:

float4 transformed = mul(world, position);

Because of the way that matrix multiplication works, if the matrix is column-major then you must post multiply by a column vector to get the correct result. If the matrix is row-major you must pre multiply by a row vector.

So really, hlsl doesn't care whether your matrix is row or column major, it is up to you to apply the vector multiplication in the correct order to get the correct result.

gareththegeek
  • 2,362
  • 22
  • 34
  • +1 Thanks, I guess it's all about ordering then. Great reply layout! – dk123 May 16 '13 at 10:33
  • 3
    This isn't quite right. There's a difference between "row-major" (which is about the order matrix components are stored in memory) and "row vector convention" (which is about the order of multiplication). The matrix here is *stored* in row-major order in both cases. The first case is using a row vector and the second case a column vector (and the matrix would have to have been built appropriately for each). – Nathan Reed Dec 07 '13 at 06:03