Quelqu'un peut-il expliquer les (raisons des) implications de colum vs row majeur dans la multiplication / concaténation?

11

J'essaie d'apprendre à construire des matrices de vue et de projection, et je continue à rencontrer des difficultés dans ma mise en œuvre en raison de ma confusion au sujet des deux normes pour les matrices.
Je sais comment multiplier une matrice, et je peux voir que la transposition avant la multiplication changerait complètement le résultat, d'où la nécessité de multiplier dans un ordre différent.

Ce que je ne comprends pas cependant, c'est ce que l'on entend uniquement par `` convention de notation '' - à partir des articles ici et ici, les auteurs semblent affirmer que cela ne fait aucune différence dans la façon dont la matrice est stockée ou transférée au GPU, mais le deuxième page cette matrice n'est clairement pas équivalente à la façon dont elle serait disposée en mémoire pour la ligne principale; et si je regarde une matrice peuplée dans mon programme, je vois les composants de traduction occupant les 4e, 8e et 12e éléments.

Étant donné que:

"la post-multiplication avec les matrices de colonnes principales produit le même résultat que la pré-multiplication avec les matrices de lignes majeures."

Pourquoi dans l'extrait de code suivant:

        Matrix4 r = t3 * t2 * t1;
        Matrix4 r2 = t1.Transpose() * t2.Transpose() * t3.Transpose();

Est-ce que r! = R2 et pourquoi pos3! = Pos pour :

        Vector4 pos = wvpM * new Vector4(0f, 15f, 15f, 1);
        Vector4 pos3 = wvpM.Transpose() * new Vector4(0f, 15f, 15f, 1);

Le processus de multiplication change-t-il selon que les matrices sont des lignes ou des colonnes majeures , ou est-ce juste l'ordre (pour un effet équivalent?)

Une chose qui n'aide pas à rendre cela plus clair, c'est que lorsqu'elle est fournie à DirectX, ma matrice WVP principale de colonne est utilisée avec succès pour transformer les sommets avec l'appel HLSL: mul (vecteur, matrice), ce qui devrait entraîner le traitement du vecteur comme row-major , alors comment la matrice de colonne principale fournie par ma bibliothèque de mathématiques peut-elle fonctionner?

sebf
la source

Réponses:

11

si je regarde une matrice peuplée dans mon programme, je vois les composants de traduction occupant les 4e, 8e et 12e éléments.

Avant de commencer, il est important de comprendre: cela signifie que vos matrices sont des lignes majeures . Par conséquent, vous répondez à cette question:

ma matrice WVP majeure de colonne est utilisée avec succès pour transformer les sommets avec l'appel HLSL: mul (vecteur, matrice), ce qui devrait entraîner le traitement du vecteur comme ligne majeure, alors comment la matrice principale de colonne fournie par ma bibliothèque de mathématiques peut-elle fonctionner?

est assez simple: vos matrices sont des lignes majeures.

Tant de gens utilisent des matrices à lignes majeures ou transposées, qu'ils oublient que les matrices ne sont pas naturellement orientées de cette façon. Ils voient donc une matrice de traduction comme ceci:

1 0 0 0
0 1 0 0
0 0 1 0
x y z 1

Il s'agit d'une matrice de traduction transposée . Ce n'est pas ce à quoi ressemble une matrice de traduction normale. La traduction va dans la 4ème colonne , pas dans la quatrième ligne. Parfois, vous voyez même cela dans les manuels, ce qui est une pure ordure.

Il est facile de savoir si une matrice dans un tableau est une ligne ou une colonne majeure. S'il s'agit d'une ligne majeure, la traduction est stockée dans les index 3, 7 et 11. S'il s'agit d'une colonne majeure, la traduction est stockée dans les index 12, 13 et 14. Indices de base zéro bien sûr.

Votre confusion vient du fait de croire que vous utilisez des matrices à colonnes majeures alors que vous utilisez en fait des matrices à lignes majeures.

L'affirmation selon laquelle la ligne par rapport à la colonne principale n'est qu'une convention de notation est entièrement vraie. La mécanique de la multiplication matricielle et de la multiplication matrice / vecteur est la même quelle que soit la convention.

Ce qui change, c'est la signification des résultats.

Une matrice 4x4, après tout, n'est qu'une grille de nombres 4x4. Il n'a pas a faire référence à un changement de système de coordonnées. Cependant, une fois que vous attribuez un sens à une matrice particulière, vous devez maintenant savoir ce qui y est stocké et comment l'utiliser.

Prenez la matrice de traduction que je vous ai montrée ci-dessus. C'est une matrice valide. Vous pouvez stocker cette matrice de l'une float[16]des deux manières suivantes:

float row_major_t[16] =    {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};
float column_major_t[16] = {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};

Cependant, j'ai dit que cette matrice de traduction est erronée, car la traduction est au mauvais endroit. J'ai dit spécifiquement qu'il est transposé par rapport à la convention standard sur la façon de construire des matrices de traduction, qui devrait ressembler à ceci:

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

Voyons comment ils sont stockés:

float row_major[16] =    {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};
float column_major[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};

Notez que column_majorc'est exactement la même chose que row_major_t. Donc, si nous prenons une matrice de traduction appropriée et la stockons en tant que colonne majeure, cela revient à transposer cette matrice et à la stocker comme ligne majeure.

C'est ce que l'on entend par n'être qu'une convention de notation. Il existe en réalité deux ensembles de conventions: le stockage en mémoire et la transposition. Le stockage en mémoire est une colonne par rapport à une ligne principale, tandis que la transposition est normale ou transposée.

Si vous avez une matrice qui a été générée dans l'ordre des lignes principales, vous pouvez obtenir le même effet en transposant l'équivalent des colonnes majeures de cette matrice. Et vice versa.

La multiplication des matrices ne peut se faire que dans un sens: étant donné deux matrices, dans un ordre spécifique, vous multipliez certaines valeurs ensemble et stockez les résultats. Maintenant, A*B != B*Amais le code source réel de A*Best le même que le code de B*A. Ils exécutent tous les deux le même code pour calculer la sortie.

Le code de multiplication de matrice ne se soucie pas de savoir si les matrices se trouvent être stockées dans l'ordre des colonnes principales ou des lignes principales.

La même chose ne peut pas dire pour la multiplication vecteur / matrice. Et voici pourquoi.

La multiplication vecteur / matrice est un mensonge; cela ne peut pas être fait. Cependant, vous pouvez multiplier une matrice par une autre matrice. Donc, si vous prétendez qu'un vecteur est une matrice, vous pouvez effectivement faire une multiplication vecteur / matrice, simplement en faisant une multiplication matrice / matrice.

Un vecteur 4D peut être considéré comme un vecteur colonne ou un vecteur ligne. Autrement dit, un vecteur 4D peut être considéré comme une matrice 4x1 (rappelez-vous: en notation matricielle, le nombre de lignes vient en premier) ou une matrice 1x4.

Mais voici la chose: étant donné deux matrices A et B, A*Bn'est définie que si le nombre de colonnes de A est le même que le nombre de lignes de B.Par conséquent, si A est notre matrice 4x4, B doit être une matrice à 4 lignes en elle. Par conséquent, vous ne pouvez pas effectuer A*x, où x est un vecteur ligne . De même, vous ne pouvez pas effectuer x*Aoù x est un vecteur de colonne.

Pour cette raison, la plupart des bibliothèques mathématiques matricielles font cette hypothèse: si vous multipliez un vecteur fois une matrice, vous voulez vraiment faire la multiplication qui fonctionne réellement , pas celle qui n'a aucun sens.

Définissons, pour tout vecteur 4D x, ce qui suit. Cdoit être la forme matricielle vecteur colonne de x, et Rdoit être la forme matricielle vecteur ligne de x. Compte tenu de cela, pour toute matrice 4x4 A, A*Creprésente la matrice multipliant A par le vecteur colonne x. Et R*Areprésente la matrice multipliant le vecteur ligne xpar A.

Mais si nous regardons cela en utilisant des mathématiques matricielles strictes, nous voyons que ceux-ci ne sont pas équivalents . R*A ne peut pas être le même que A*C. En effet, un vecteur ligne n'est pas la même chose qu'un vecteur colonne. Ce ne sont pas la même matrice, donc ils ne produisent pas les mêmes résultats.

Cependant, ils sont liés d'une manière. C'est vrai ça R != C. Cependant, il est également vrai que , où T est l'opération de transposition. Les deux matrices sont transposées l'une de l'autre.R = CT

Voici un fait amusant. Étant donné que les vecteurs sont traités comme des matrices, ils ont également une question de stockage colonne contre ligne principale. Le problème est qu'ils se ressemblent tous les deux . Le tableau de flottants est le même, vous ne pouvez donc pas faire la différence entre R et C simplement en regardant les données. La seule façon de faire la différence est de savoir comment ils sont utilisés.

Si vous avez deux matrices A et B et que A est stocké en tant que ligne principale et B en tant que colonne principale, leur multiplication n'a aucun sens . Vous obtenez un non-sens en conséquence. Eh bien pas vraiment. Mathématiquement, ce que vous obtenez est l'équivalent de le faire . Ou ; ils sont mathématiquement identiques.AT*BA*BT

Par conséquent, la multiplication matricielle n'a de sens que si les deux matrices (et rappelez-vous: la multiplication vecteur / matrice n'est qu'une multiplication matricielle) sont stockées dans le même ordre majeur.

Alors, un vecteur colonne-majeur ou ligne-majeur? Ce n'est ni l'un ni l'autre, comme indiqué précédemment. Il n'est majeur de colonne que lorsqu'il est utilisé comme matrice de colonne, et il est majeur de ligne lorsqu'il est utilisé comme matrice de ligne.

Par conséquent, si vous avez une matrice A qui est une colonne majeure, cela x*Asignifie ... rien. Eh bien, encore une fois, cela signifie , mais ce n'est pas ce que vous vouliez vraiment. De même, la multiplication transposée est-elle si la ligne est majeurex*ATA*xA

Par conséquent, l'ordre de la multiplication du vecteur / matrice ne change, en fonction de votre commande majeure des données (et si vous utilisez des matrices transposés).

Pourquoi dans l'extrait de code suivant, r! = R2

Parce que votre code est cassé et bogué. Mathématiquement, . Si vous n'obtenez pas ce résultat, votre test d'égalité est incorrect (problèmes de précision en virgule flottante) ou votre code de multiplication de matrice est rompu.A * (B * C) == (CT * BT) * AT

pourquoi pos3! = pos pour

Parce que ça n'a pas de sens. La seule façon d'être vrai serait si . Et cela n'est vrai que des matrices symétriques.A * t == AT * tA == AT

Nicol Bolas
la source
@Nicol, tout commence à cliquer maintenant. Il y avait une confusion due à une déconnexion entre ce que je voyais et ce que je pensais devoir être, car ma bibliothèque (tirée d'Axiom) déclare la colonne principale (et tous les ordres de multiplication, etc. sont conformes à cela), mais la disposition de la mémoire est une ligne -major (à en juger par les indices de traduction et le fait que HLSL fonctionne correctement en utilisant la matrice non transposée); Je vois maintenant cependant comment cela n'est pas en conflit. Merci beaucoup!
sebf
2
Je vous ai presque donné un -1 pour avoir dit des choses comme "Ce n'est pas ce à quoi ressemble une matrice de traduction normale" et "ce qui est une ordure totale". Ensuite, vous continuez et expliquez bien pourquoi ils sont complètement équivalents et donc ni l'un ni l'autre n'est plus "naturel" que l'autre. Pourquoi ne supprimez-vous pas ce petit non-sens depuis le début? Le reste de votre réponse est en fait plutôt bon. (Aussi, pour les intéressés: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
imre
2
@imre: Parce que ce n'est pas un non-sens. Les conventions sont importantes car il est déroutant d'avoir deux conventions. Les mathématiciens se sont installés il y a longtemps sur la convention des matrices . Les "matrices transposées" (nommées parce qu'elles sont transposées à partir de la norme) sont une violation de cette convention. Puisqu'ils sont équivalents, ils ne procurent aucun avantage réel à l'utilisateur. Et comme ils sont différents et peuvent être mal utilisés, cela crée de la confusion. Autrement dit, si les matrices transposées n'existaient pas, le PO n'aurait jamais posé cette question. Et donc, cette convention alternative crée de la confusion.
Nicol Bolas
1
@Nicol: Une matrice ayant une traduction dans 13/12/14 peut toujours être majeure en ligne - si nous utilisons ensuite des vecteurs de ligne avec elle (et multiplions en vM). Voir DirectX. OU il peut être considéré comme une colonne majeure, utilisé avec des vecteurs de colonne (Mv, OpenGL). C'est vraiment pareil. Inversement, si une matrice a une traduction en 3-7-11, alors elle peut être considérée comme une matrice de ligne principale avec des vecteurs de colonne, OU une colonne de majeure avec des vecteurs de ligne. La version 12-13-14 est en effet plus courante, mais à mon avis 1) ce n'est pas vraiment une norme, et 2) l'appeler colonne majeure peut être trompeur, car ce n'est pas nécessairement cela.
2011
1
@imre: C'est standard. Demandez à n'importe quel mathématicien formé où va la traduction, et ils vous diront que cela va dans la quatrième colonne. Les mathématiciens ont inventé les matrices; ce sont eux qui fixent les conventions.
Nicol Bolas
3

Il y a deux choix de convention différents à l'œuvre ici. La première est de savoir si vous utilisez des vecteurs de ligne ou des vecteurs de colonne, et les matrices de ces conventions sont transposées les unes des autres.

L'autre est de savoir si vous stockez les matrices en mémoire dans l'ordre des lignes principales ou des colonnes. Notez que "row-major" et "column-major" ne sont pas les termes corrects pour discuter de la convention ligne-vecteur / colonne-vecteur ... même si beaucoup de gens les utilisent à mauvais escient comme telles. Les dispositions de mémoire des lignes principales et des colonnes principales diffèrent également par une transposition.

OpenGL utilise une convention de vecteur de colonne et un ordre de stockage de colonne principale, et D3D utilise une convention de vecteur de ligne et un ordre de stockage de ligne principale (enfin - au moins D3DX, la bibliothèque mathématique), donc les deux transpositions s'annulent et il s'avère la même disposition de mémoire fonctionne pour OpenGL et D3D. Autrement dit, la même liste de 16 flottants stockés séquentiellement en mémoire fonctionnera de la même manière dans les deux API.

C'est peut-être ce que veulent dire les gens qui disent que "cela ne change rien à la façon dont la matrice est stockée ou transférée au GPU".

Quant à vos extraits de code, r! = R2 car la règle de transposition d'un produit est (ABC) ^ T = C ^ TB ^ TA ^ T. La transposition distribue la multiplication avec un revirement d'ordre. Donc, dans votre cas, vous devriez obtenir r.Transpose () == r2, pas r == r2.

De même, pos! = Pos3 car vous avez transposé mais n'avez pas inversé l'ordre de multiplication. Vous devriez obtenir wpvM * localPos == localPos * wvpM.Tranpose (). Le vecteur est automatiquement interprété comme un vecteur ligne lorsqu'il est multiplié sur le côté gauche d'une matrice, et comme un vecteur colonne lorsqu'il est multiplié sur le côté droit d'une matrice. À part cela, il n'y a aucun changement dans la façon dont la multiplication est effectuée.

Enfin, re: "ma colonne matrice WVP principale est utilisée avec succès pour transformer les sommets avec l'appel HLSL: mul (vecteur, matrice)," je ne suis pas sûr de cela, mais peut-être une confusion / un bug a provoqué la sortie de la matrice la bibliothèque mathématique déjà transposée.

Nathan Reed
la source
1

Dans les graphiques 3D, vous utilisez la matrice pour transformer à la fois le vecteur et les points. Compte tenu du fait que vous parlez de matrice de traduction, je ne parlerai que de points (vous ne pouvez pas traduire un vecteur avec une matrice, ou, mieux dit, vous pouvez mais vous obtiendrez le même vecteur).

Dans la multiplication matricielle, le nombre de colonnes de la première matrice doit être égal au nombre de lignes de la seconde (vous pouvez multiplier une matrice anxm pour un mxk).

Un point (ou un vecteur) est représenté par 3 composantes (x, y, z) et peut être considéré à la fois comme une ligne ou une colonne:

colum (dimension 3 X 1):

| x |

| y |

| z |

ou

ligne (dimension 1 X 3):

| x, y, z |

Vous pouvez choisir la convention préférée, c'est juste une convention. Appelons-le T la matrice de traduction. Si vous choisissez la première convention, pour multiplier un point p pour une matrice, vous devez utiliser une post-multiplication:

T * v (dimension 3x3 * 3x1)

autrement:

v * T (dimension 1x3 * 3x3)

les auteurs semblent affirmer que la façon dont la matrice est stockée ou transférée au GPU ne fait aucune différence

Si vous utilisez toujours la même convention, cela ne fait aucune différence. Cela ne signifie pas que la matrice de convention différente aura la même représentation en mémoire, mais qu'en transformant un point avec les 2 conventions différentes, vous obtiendrez le même point transformé:

p2 = B * A * p1; // première convention

p3 = p1 * A * B; // deuxième convention

p2 == p3;

Heisenbug
la source
1

Je vois que les composants de traduction occupant les 4e, 8e et 12e éléments signifient que vos matrices sont "incorrectes".

Les composants de traduction sont toujours spécifiés comme entrées # 13, # 14 et # 15 de la matrice de transformation (en comptant le tout premier élément du tableau comme élément # 1 ).

Une matrice de transformation principale de ligne ressemble à ceci:

[ 2 2 2 1 ]   R00 R01 R02 0  
              R10 R11 R12 0 
              R20 R21 R22 0 
              t.x t.y t.z 1 

Une matrice de transformation majeure de colonne ressemble à ceci:

 R00 R01 R02 t.x   2  
 R10 R11 R12 t.y   2 
 R20 R21 R22 t.z   2 
  0   0   0   1    1 

Les matrices principales des lignes sont spécifiées en descendant les lignes .

Déclarant la matrice principale de la ligne ci-dessus comme un tableau linéaire, j'écrirais:

ROW_MAJOR = { R00, R01, R02, 0,  // row 1 // very intuitive
              R10, R11, R12, 0,  // row 2
              R20, R21, R22, 0,  // row 3
              t.x, t.y, t.z, 1 } ; // row 4

Cela semble très naturel. Parce que remarquez, l'anglais est écrit "ligne majeure" - la matrice apparaît dans le texte ci-dessus exactement comme elle le sera en mathématiques.

Et voici le point de confusion.

Les matrices principales des colonnes sont spécifiées en descendant les colonnes

Cela signifie que pour spécifier la matrice de transformation principale de la colonne comme un tableau linéaire dans le code, vous devez écrire:

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // très contre-intuitif
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

Notez que c'est complètement contre-intuitif !! Une matrice principale de colonne a ses entrées spécifiées dans les colonnes lors de l'initialisation d'un tableau linéaire, donc la première ligne

COLUMN_MAJOR = { R00, R10, R20, 0,

Spécifie la première colonne de la matrice:

 R00
 R10
 R20
  0 

et non la première ligne , comme vous le croiriez dans la simple mise en page du texte. Vous devez transposer mentalement une matrice principale de colonne lorsque vous la voyez dans le code, car les 4 premiers éléments spécifiés décrivent en fait la première colonne. Je suppose que c'est pourquoi beaucoup de gens préfèrent les matrices de lignes principales dans le code (GO DIRECT3D !! toux.)

Ainsi, les composants de traduction sont toujours aux indices de tableau linéaire # 13, # 14 et # 15 (où le premier élément est # 1), que vous utilisiez des matrices de lignes majeures ou de colonnes majeures.

Qu'est-il arrivé à votre code et pourquoi cela fonctionne-t-il?

Ce qui se passe dans votre code, c'est que vous avez une colonne major-matrix oui, mais vous placez les composants de traduction au mauvais endroit. Lorsque vous transposez la matrice, l'entrée # 4 passe à l'entrée # 13, l'entrée # 8 à # 13 et l'entrée # 12 à # 15. Et voila.

bobobobo
la source
0

En termes simples, la raison de la différence est que la multiplication matricielle n'est pas commutative . Avec une multiplication régulière des nombres, si A * B = C alors il s'ensuit que B * A aussi = C. Ce n'est pas le cas avec les matrices. C'est pourquoi le choix des lignes principales ou des colonnes est important.

Ce qui n'a pas d' importance, c'est que, dans une API moderne (et je parle spécifiquement des shaders ici), vous pouvez choisir votre propre convention et multiplier vos matrices dans le bon ordre pour cette convention dans votre propre code de shader. L'API ne vous applique plus ni l'un ni l'autre.

Maximus Minimus
la source