Géométrie (Vecteur majeur de rangée vs Vecteur majeur de colonne)

Géométrie

Plus tôt dans cette leçon, nous avons expliqué que les vecteurs (ou points) peuvent être écrits comme des matrices (une rangée, trois colonnes). Notez cependant que nous aurions également pu les écrire sous forme de matrices (trois lignes, une colonne). Techniquement, ces deux façons d’exprimer les points et les vecteurs sous forme de matrices sont parfaitement valables et choisir un mode ou l’autre n’est qu’une question de convention.

Vecteur écrit sous forme de matrice : \N( V=\begin{bmatrix}x & y & z\end{bmatrix}\)

Vecteur écrit comme une matrice : \(V=\begin{bmatrix}x\\y\\z\end{bmatrix})

Dans le premier exemple ( matrice), nous avons exprimé notre vecteur ou notre point dans ce que nous appelons l’ordre rang-majeur : le vecteur (ou le point) est écrit comme une ligne de trois nombres. Dans le deuxième exemple, nous disons que que les points ou les vecteurs sont écrits dans l’ordre colonne-majeur : nous écrivons les trois coordonnées du vecteur ou du point verticalement, comme une colonne.

Rappellez-vous que nous exprimons les points et les vecteurs comme des matrices pour les multiplier par des matrices de transformation (pour des raisons de simplicité, nous travaillerons avec plutôt que des matrices). Nous avons également appris que nous ne pouvons multiplier les matrices que lorsque le nombre de colonnes de la matrice de gauche et le nombre de lignes de la matrice de droite sont les mêmes. En d’autres termes, les matrices et peuvent être multipliées entre elles, mais les matrices et ne le peuvent pas. Notez que si nous écrivons un vecteur sous forme de matrice, nous pouvons le multiplier par une matrice (en supposant que cette matrice se trouve à droite dans la multiplication), mais si nous écrivons ce vecteur sous forme de matrice, nous ne pouvons pas le multiplier par une matrice. Ceci est illustré dans les exemples suivants. Les dimensions intérieures (3 et 3) des matrices impliquées dans la multiplication sont les mêmes (en vert) donc cette multiplication est valide (et le résultat est un point transformé écrit sous la forme d’une matrice):

$$* = \begin{bmatrix}x & y & z\end{bmatrix}. * \begin{bmatrix}c_{00}&c_{01}&{c_{02}}\\c_{10}&c_{11}&{c_{12}}\\c_{20}&c_{21}&{c_{22}}\\\end{bmatrix} =\begin{bmatrix}x’&y’&z’\end{bmatrix}$$

The dimensions intérieures (1 et 3) des matrices impliquées dans la multiplication ne sont pas les mêmes (en rouge) donc cette multiplication n’est pas possible :

$$* \rightarrow \begin{bmatrix}x\\ y\\z\end{bmatrix} * \begin{bmatrix} c_{00}&c_{01}&{c_{02}}\\\ c_{10}&c_{11}&{c_{12}}\\\ c_{20}&c_{21}&{c_{22}}\\\ \end{bmatrix}$$

Alors, que faisons-nous ? La solution à ce problème ne consiste pas à multiplier le vecteur ou le point par la matrice, mais la matrice M par le vecteur V. En d’autres termes, nous déplaçons le point ou le vecteur vers la droite à l’intérieur de la multiplication :

$$* \rightarrow \begin{bmatrix} c_{00}&c_{01}&{c_{02}\\\\ c_{10}&c_{11}&{c_{12}}\\\ c_{20}&c_{21}&{c_{22}}\\ \end{bmatrix} * \begin{bmatrix}x\\\\y\\z\end{bmatrix} = \begin{bmatrix}x’\y’\z’\end{bmatrix}$

Notez que le résultat de cette opération est un point transformé écrit sous forme de matrice. Nous obtenons donc un point au départ et nous terminons avec un point transformé, ce qui est ce que nous voulons. Problème résolu. Pour résumer, lorsque par convention nous décidons d’exprimer des vecteurs ou des points dans l’ordre rangée-majeur (), nous devons placer le point à gauche de la multiplication et le point à droite à l’intérieur du signe de multiplication. C’est ce qu’on appelle en mathématiques une multiplication à gauche ou une prémultiplication. Si vous décidez d’écrire les vecteurs dans l’ordre des colonnes majeures (), la matrice doit se trouver à gauche de la multiplication et le vecteur ou le point à droite. C’est ce qu’on appelle une multiplication droite ou post-multiplication.

Nous devons faire attention à la façon dont ces termes sont réellement utilisés. Par exemple la documentation de Maya dit « les matrices sont post-multipliées dans Maya. Par exemple, pour transformer un point P de l’espace-objet à l’espace-monde (P’), vous devez effectuer une post-multiplication par la worldMatrix. (P’ = P x WM) », ce qui prête à confusion car il s’agit en fait d’une pré-multiplication, mais ils parlent de la position de la matrice par rapport au point dans ce cas particulier. C’est en fait une utilisation incorrecte de la terminologie. Il aurait fallu écrire que dans Maya, les points et les vecteurs sont exprimés comme des vecteurs majeurs de rangée et qu’ils sont donc pré-multipliés (ce qui signifie que le point ou le vecteur apparaît avant la matrice dans la multiplication).

Le tableau suivant résume les différences entre les deux conventions (où P, V et M signifient respectivement Point, Vecteur et Matrice).

Ordre majeur des lignes

\(P/V=\begin{bmatrix}x & y & z\end{bmatrix}\)

Multiplication gauche ou prémultiplication

P/V * M

Colonne-ordre majeur

\(P/V=\begin{bmatrix}x \\\ y \\end{bmatrix}\)

Droit ou post-multiplication

M * P/V

Maintenant que nous avons appris ces deux conventions, vous pourriez vous demander  » est-ce que ce n’est pas juste pour écrire des choses sur du papier ? ». Nous savons comment calculer le produit de deux matrices A et B : multiplier chaque coefficient de la ligne courante de A par les éléments associés de la colonne courante de B et additionner le résultat. Appliquons cette formule en utilisant les deux conventions et comparons les résultats :

Row-major order

$${ \begin{bmatrix}x & y & z\end{bmatrix}. * \begin{bmatrix}a & b & c \d & e & f \g & h & i\end{bmatrix} }$$

${ \begin{array}{l}x’ = x * a + y * d + z * g\\\\y’ = x * b + y * e + z * h\\\\z’ = x * c + y * f + z * i\end{array} }$$

Colonne d’ordre majeur

${ \begin{bmatrix} a & b & c \\\d & e & f \g & h & i \end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$

${\begin{array}{l}x’ = a * x + b * y + c * z\\\\y’ = d * x + e * y + f * z\\\\z’ = g * x + h * y + i * z\end{array} }$$

Multiplier un point ou un vecteur par une matrice devrait nous donner le même résultat, que nous utilisions l’ordre majeur des lignes ou des colonnes. Si vous utilisez une application 3D pour faire pivoter un point d’un certain angle autour de l’axe z, vous vous attendez à ce que le point soit dans une certaine position après la rotation, quelle que soit la convention interne utilisée par le développeur pour représenter les points et les vecteurs. Cependant, comme vous pouvez le voir dans le tableau ci-dessus, la multiplication d’un point (ou d’un vecteur) de rangée majeure et de colonne majeure par la même matrice ne nous donnerait pas le même résultat. Pour nous remettre sur pied, nous aurions en fait besoin de transposer la matrice utilisée dans la multiplication en colonne-major pour être sûrs que x’, y’ et z’ sont les mêmes (si vous avez besoin de vous rappeler ce qu’est la transposition d’une matrice, consultez le chapitre sur les opérations matricielles). Voici ce que nous obtenons:

Ordre de rangée majeur

$${ \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\end{bmatrix} }$$

${\begin{array}{l}x’ = x * a + y * d + z * g\\\y’ = x * b + y * e + z * h\\\z’ = x * c + y * f + z * i\end{array} }$$

Colonne d’ordre majeur

${ \begin{bmatrix} a & d & g \\b &e & h \c & f & i\end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$

${\begin{array}{l}x’ = a * x + d * y + g * z\\\\y’ = b * x + e * y + h * z\\\z’ = c * x + f * y + i * z\end{array} }$$

En conclusion, passer de l’ordre ligne-majeur à l’ordre colonne-majeur implique non seulement de permuter le point ou le vecteur et la matrice dans la multiplication mais aussi de transposer la matrice, pour garantir que les deux conventions donnent le même résultat (et vice versa).

À partir de ces observations, nous pouvons voir que toute série de transformations appliquées à un point ou à un vecteur lorsqu’une convention rang-majeur est utilisée peut être écrite dans un ordre séquentiel (ou ordre de lecture). Imaginons par exemple que l’on veuille translater le point P avec la matrice T puis le faire tourner autour de l’axe des z avec Rz puis autour de l’axe des y avec Ry. Vous pouvez écrire:

$$P’=P * T * R_z * R_y$$

Si vous deviez utiliser une notation en colonne majeure, vous devriez appeler la transformation dans l’ordre inverse (ce que l’on pourrait trouver contre-intuitif):

$P’=R_y * R_z * T * P$$

Donc, vous pourriez penser : « il doit y avoir une raison de préférer un système à un autre ». En fait, les deux conventions sont correctes et nous donnent le même résultat, mais pour certaines raisons techniques, les textes de Maths et de Physique traitent généralement les vecteurs comme des vecteurs colonnes.

L’ordre de transformation lorsque nous utilisons des matrices colum-major est plus similaire en mathématiques à la façon dont nous écrivons l’évaluation et la composition des fonctions.

La convention de matrice row-major rend cependant les matrices plus faciles à enseigner ce qui est la raison pour laquelle nous l’utilisons pour Scratchapixel (ainsi que Maya, DirectX. Elles sont également définies comme la norme dans les spécifications de RenderMan). Cependant, certaines API 3D, comme OpenGL, utilisent une convention de type colonne-major.

Implication dans le codage : Est-ce que cela a un impact sur les performances?

Il y a un autre aspect potentiellement très important à prendre en considération si vous devez choisir entre row-major et column-major, mais cela n’a pas vraiment à voir avec les conventions elles-mêmes et la façon dont l’une est pratique par rapport à l’autre. Cela a davantage à voir avec l’ordinateur et son mode de fonctionnement. N’oubliez pas que nous allons traiter des matrices. Typiquement, l’implémentation d’une matrice en C++ ressemble à ceci:

classe Matrix44{ … float m;};

Comme vous pouvez le voir, les 16 coefficients de la matrice sont stockés dans un tableau bidimensionnel de floats (ou de doubles selon la précision dont vous avez besoin. Notre classe C++ Matrix est un template). Cela signifie qu’en mémoire, les 16 coefficients seront disposés de la manière suivante : c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. En d’autres termes, ils sont disposés de manière contiguë dans la mémoire. Voyons maintenant comment on accède à ces coefficients dans une multiplication vecteur-matrice où les vecteurs sont écrits dans l’ordre majeur des lignes :

// ordre row-majorx’ = x * c00 + y * c10 + z * c20y’ = x * c01 + y * c11 + z * c21z’ = x * c02 + y * c12 + z * c22

Comme vous pouvez le voir les éléments de la matrice pour x’ ne sont pas accessibles séquentiellement. En d’autres termes, pour calculer x’, nous avons besoin du 1er, du 5ème et du 9ème flotteur de la matrice 16 flotteurs. Pour calculer y’, nous devons accéder aux 2ème, 6ème et 10ème flottants de ce tableau. Et enfin pour z’ nous avons besoin des 3ème, 7ème et 11ème flottants du tableau. Dans le monde de l’informatique, accéder aux éléments d’un tableau dans un ordre non séquentiel n’est pas nécessairement une bonne chose. En fait, cela peut potentiellement dégrader les performances du cache de l’unité centrale. Nous n’entrerons pas dans les détails ici, mais disons simplement que la mémoire la plus proche à laquelle le CPU peut accéder s’appelle un cache. Ce cache est très rapide d’accès mais ne peut stocker qu’un nombre très limité de données. Lorsque le CPU a besoin d’accéder à des données, il vérifie d’abord si elles existent dans le cache. Si c’est le cas, le CPU accède immédiatement à ces données (cache hit), mais dans le cas contraire (cache miss), il doit d’abord créer une entrée dans le cache, puis copier à cet emplacement les données de la mémoire principale. Ce processus prend évidemment plus de temps que lorsque les données existent déjà dans le cache. L’idéal est donc d’éviter autant que possible les erreurs de cache. En plus de copier les données particulières de la mémoire principale, le CPU copie également une partie des données qui se trouvent juste à côté (par exemple les 24 octets suivants), parce que les ingénieurs en matériel ont compris que si votre code avait besoin d’accéder à un élément d’un tableau par exemple, il était susceptible d’accéder aux éléments qui le suivent peu après. En effet, dans les programmes, nous bouclons souvent sur les éléments d’un tableau dans un ordre séquentiel et cette hypothèse est donc susceptible d’être vraie. Appliqué à notre problème de matrice, l’accès aux coefficients de la matrice dans un ordre non séquentiel peut donc poser un problème. En supposant que le CPU charge le flottant demandé dans le cache plus les 3 flottants qui le suivent, notre implémentation actuelle pourrait conduire à de nombreux manques de cache, puisque les coefficients utilisés pour calculer x’ y’ et z’ sont séparés par 5 flottants dans le tableau. D’autre part, si vous utilisez une notation d’ordre colonne-majeur, le calcul de x’ par exemple nécessite d’accéder aux 1er, 2ème et 3ème éléments de la matrice.

// column-major orderx’ = c00 * x + c01 * y + c02 * zy’ = c10 * x + c11 * y + c12 * zz’ = c20 * x + c21 * y + c22 * z

Les coefficients sont accédés dans un ordre séquentiel ce qui signifie également que nous faisons un bon usage du mécanisme de cache du CPU (seulement 3 manques de cache au lieu de 9 dans notre exemple). En conclusion, nous pouvons dire que, du point de vue de la programmation, la mise en œuvre de notre multiplication matricielle par points ou par vecteurs en utilisant une convention d’ordre colum-major pourrait être meilleure, en termes de performances, que la version utilisant la convention d’ordre row-major. En pratique cependant, nous n’avons pas été en mesure de démontrer que c’était réellement le cas (lorsque vous compilez votre programme en utilisant les drapeaux d’optimisation -O, -O2 ou -O3, le compilateur peut faire le travail pour vous en optimisant les boucles sur les tableaux multidimensionnels) et nous avons utilisé avec succès la version d’ordre row-major sans aucune perte de performance par rapport à une version du même code utilisant une implémentation d’ordre column-major.

template<typename T>class Vec3{public : Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} T x, y, z, w;} ; template<typename T>classe Matrix44{public : T m ; Vec3<T> multVecMatrix(const Vec3<T> &v) {#ifdef ROWMAJOR return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#else return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#endif }} ; #include <cmath>#include <cstdlib>#include <cstdio>#include <ctime> #define MAX_ITER 10e8 int main(int argc, char **argv){ clock_t start = clock() ; Vec3<float> v(1, 2, 3) ; Matrix44<float> M ; float *tmp = &M.m ; for (int i = 0 ; i < 16 ; i++) *(tmp + i) = drand48() ; for (int i = 0 ; i < MAX_ITER ; ++i) { Vec3<float> vt = M.multVecMatrix(v) ; } fprintf(stderr, « Clock time %f\n », (clock() – start) / float(CLOCKS_PER_SEC)) ; return 0 ;}

Ordre ligne-majeur et colonne-majeur en informatique

Pour être complet, mentionnons également que les termes ordre ligne-majeur et ordre colonne-majeur peuvent aussi être utilisés en informatique pour décrire la façon dont les éléments des tableaux multidimensionnels sont disposés en mémoire. Dans l’ordre majeur des rangées, les éléments d’un tableau multidimensionnel sont disposés les uns après les autres, de gauche à droite et de haut en bas. C’est la méthode utilisée par C/C++. Par exemple, la matrice :

$$M = \begin{bmatrix}1&2&3\\\4&5&6\end{bmatrix}$$

pourrait être écrite en C/C++ comme :

float m={1, 2, 3}, {4, 5, 6}};

et les éléments de ce tableau seraient disposés de manière contiguë dans la mémoire linéaire comme :

1 2 3 4 5 6

Dans l’ordre majeur de la colonne, qui est utilisé par des langages tels que FORTRAN et MATLAB, les éléments de la matrice sont stockés en mémoire de haut en bas, de gauche à droite. En reprenant le même exemple de matrice, les éléments de la matrice seraient stockés (et accédés) en mémoire de la manière suivante :

1 4 2 5 3 6

Savoir comment les éléments d’une matrice sont disposés en mémoire est important notamment lorsque vous essayez d’y accéder en utilisant le décalage des pointeurs et l’optimisation des boucles for (nous avons expliqué précédemment dans ce chapitre que cela pouvait affecter les performances du cache du CPU). Cependant, étant donné que nous ne considérerons que le langage de programmation C/C++, l’ordre colonne-majeur (appliqué à l’informatique) ne présente pas un grand intérêt pour nous. Nous ne faisons que mentionner la signification de ces termes en informatique, afin que vous sachiez qu’ils peuvent décrire deux choses différentes selon le contexte dans lequel ils sont utilisés. Vous devez faire attention à ne pas les confondre. Dans le contexte des mathématiques, ils décrivent si vous traitez les vecteurs (ou les points) comme des lignes de coordonnées ou comme des colonnes et la seconde, et dans le contexte de l’informatique, ils décrivent la façon dont un certain langage de programmation stocke et accède aux éléments de tableaux multidimensionnels (ce que sont les matrices) en mémoire.

OpenGL est un cas intéressant à cet égard. Lorsque GL a été créé initialement, les développeurs ont choisi la convention de vecteur rang-majeur. Les développeurs qui ont étendu OpenGL ont cependant pensé qu’ils devaient revenir à column-major vector, ce qu’ils ont fait. Cependant, pour des raisons de compatibilité, ils n’ont pas voulu modifier le code de la multiplication point-matrice et ont décidé à la place de changer l’ordre dans lequel les coefficients de la matrice étaient stockés en mémoire. En d’autres termes, OpenGL stocke les coefficients dans l’ordre colonne-majeur, ce qui signifie que les coefficients de traduction m03, m13 et m23 d’une matrice utilisant un vecteur colonne-majeur ont les indices 13, 14, 15 dans le tableau de flottants, comme le feraient les coefficients de traduction m30, m31 et m32 d’une matrice utilisant un vecteur ligne-majeur.

Sommaire

Les différences entre les deux conventions sont résumées dans le tableau suivant :

.

Vecteur majeur de ligne (Mathématiques) Colonne-colonne (mathématiques)

\(P/V=\begin{bmatrix}x & y & z\end{bmatrix}\)

\(P/V=\begin{bmatrix}x &y & z\end{bmatrix}\)

Pré-multiplication \(vM\)

Post-multiplication \(Mv\)

L’ordre d’appel et l’ordre d’application des transformations sont les mêmes : « prendre P, transformer par T, transformer par Rz, transformer par Ry » s’écrit \(P’=P*T*R_z*R_y\)

L’ordre d’appel est l’inverse de l’ordre d’application des transformations : « prendre P, transformer par T, transformer par Rz, transformer par Ry » s’écrit \(P’=R_y*R_z*T*P\)

API : Direct X, Maya

API : OpenGL, PBRT, Blender

Les lignes de la matrice représentent les bases (ou axes) d’un système de coordonnées (rouge : axe x, vert : axe y, bleu :axe z)

${\begin{bmatrix}. \color{red}{c_{00}}& \color{red}{c_{01}}&\color{red}{c_{02}}&0\\ \color{green}{c_{10}}& \color{green}{c_{11}}&\color{green}{c_{12}}&0\\ \color{blue}{c_{20}}& \color{blue}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1 \end{bmatrix} } $$

Les colonnes de la matrice représentent les bases (ou axes) d’un système de coordonnées (rouge : axe x, vert : axe y, bleu:axe z)

${ \begin{bmatrix} \color{red}{c_{00}}& \color{green}{c_{01}}&\color{blue}{c_{02}}&0\\ \color{red}{c_{10}}& \color{green}{c_{11}}&\color{blue}{c_{12}}&0\\ \color{red}{c_{20}}& \color{green}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1\end{bmatrix} }$$

Les valeurs de traduction sont stockées dans les éléments c30, c31 et c32.

$${\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\Tx&Ty&Tz&1\end{bmatrix} }$$

Les valeurs de traduction sont stockées dans les éléments c03, c13 et c23.

$${\begin{bmatrix}1&0&0&Tx\0&1&0&Ty\0&0&1&Tz\0&0&0&0&1\end{bmatrix}. }$$

Transposer la matrice pour l’utiliser comme une matrice ordonnée majeure en colonne

Transposer la matrice pour l’utiliser comme une matrice ordonnée majeure en ligne

.majeure ordonnée

Matrice rangée-major (informatique) Matrice colonne-major (informatique)

API : Direct X, Maya, PBRT

API : OpenGL

Un lecteur a posté une question sur Stackoverflow suggérant que le tableau ci-dessus était confus. Le sujet est confus et malgré notre meilleure tentative de faire la lumière sur le sujet, de nombreuses personnes s’y perdent encore. Nous avons pensé que notre réponse sur Stackoverflow pourrait, nous l’espérons, apporter un autre éclairage sur la question.

Vous avez la théorie (ce que vous faites en mathématiques avec un stylo et du papier) et ce que vous faites avec votre implémentation (C++). Ce sont deux problèmes différents.

Mathématiques : vous pouvez utiliser deux notations, soit colonne ou ligne majeure. Avec le vecteur majeur de rangée, sur le papier, vous devez écrire la multiplication vecteur-matrice vM où v est le vecteur de rangée (1×4) et M votre matrice 4×4. Pourquoi ? Parce que vous ne pouvez mathématiquement écrire que *, et pas l’inverse. De même, si vous utilisez la colonne, alors le vecteur doit être écrit verticalement, ou en notation (4 lignes, 1 colonne). Ainsi, la multiplication avec une matrice ne peut être écrite que de la manière suivante : . Notez que la matrice est placée devant le vecteur : Mv. La première notation est appelée une gauche ou pré-multiplication (parce que le vecteur est du côté gauche du produit) et la seconde (Mv) est appelée une droite ou post-multiplication (parce que le vecteur est du côté droit du produit). Comme vous le voyez, les termes dérivent du fait que le vecteur est sur le côté gauche (devant, ou « pré ») ou sur le côté droit (après, ou « post ») de la matrice.

Maintenant, si vous avez besoin de transformer un vecteur (ou un point), alors vous devez faire attention à l’ordre de la multiplication, lorsque vous les écrivez sur papier. Si vous voulez traduire quelque chose avec la matrice T et ensuite faire une rotation avec R et ensuite mettre à l’échelle avec S, alors dans un monde majeur en colonne, vous devez écrire v’ = S * R * T * v. Dans un monde majeur en ligne, vous devez écrire v’ = v * T * R * S.

C’est pour la théorie. Appelons cela la convention de vecteur ligne/colonne.

Ordinateur : vient alors le moment où vous décidez d’implémenter cela en C++ disons. La bonne chose à ce sujet est que le C++ ne vous impose rien sur rien. Vous pouvez mapper les valeurs des coefficients de votre matrice en mémoire comme vous le souhaitez, et vous pouvez écrire le code pour effectuer une multiplication matricielle par une autre matrice comme vous le souhaitez. Vous devez faire une distinction claire entre la façon dont vous mappez vos coefficients en mémoire et les conventions que vous devez utiliser d’un point de vue mathématique pour représenter vos vecteurs. Il s’agit de deux problèmes indépendants. Appelons cette partie le row/column-major layout.

Par exemple, vous pouvez déclarer une classe de matrice comme un tableau de disons 16 floats contigus. C’est très bien. Où les coefficients m14, m24, m34 représentent la partie translation de la matrice (Tx, Ty, Tz), donc vous supposez que votre « convention » est row-major même si on vous dit d’utiliser la convention de matrice OpenGL qui est dite column-major. Ici, la confusion possible vient du fait que le mappage des coefficients en mémoire est différent de la représentation mentale que vous vous faites d’une matrice « column-major ». Vous codez « ligne » mais on a dit que vous utilisiez (d’un point de vue mathématique) « colonne », d’où votre difficulté à donner du sens pour savoir si vous faites les choses bien ou mal.

Ce qui est important, c’est de voir une matrice comme une représentation d’un système de coordonnées défini par trois axes, et une translation. Où et comment vous stockez ces données en mémoire est complètement libre à vous. En supposant que les trois vecteurs représentant les trois axes du système de coordonnées sont nommés AX(x,y,z), AY(x,y,z), AZ(x,y,z), et que le vecteur de translation est désigné par (Tx, Ty, Tz), alors mathématiquement, si vous utilisez un vecteur colonne, vous avez:

$M = \begin{bmatrix} AXx & AYx & AZx & Tx\\\\ AXy & AYy & AZy & Ty \\ AXz & AYz & AZz & Tz \\\ 0 & 0 & 1 & 1\end{bmatrix}$

Les axes du système de coordonnées sont écrits verticalement. Maintenant si vous avez si vous utilisez le row-major:

$M = \begin{bmatrix} AXx & AXy & AXz & 0 \\\\\N AYx & AYy & AYz & 0 \N AZx & AZy & AZz & 0 \N Tx & Ty & Tz & 1\Nend{bmatrix}$

Les axes du système de coordonnées sont écrits horizontalement. Donc le problème maintenant quand il s’agit du monde informatique, est comment votre stocker ces coefficients en mémoire. Vous pouvez aussi bien faire :

float m = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

Est-ce que cela vous dit cependant quelle convention vous utilisez ? Non. Vous pouvez aussi écrire :

float m = { AXx, AXy, AXz, Tx, AYx, AYy, AYz, Ty, AZx, AZy, AZz, Tz, 0, 0, 0, 1};

ou :

float m = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

Encore, cela ne vous donne pas d’indication particulière sur la convention « mathématique » que vous utilisez. Vous stockez simplement 16 coefficients en mémoire de différentes manières et c’est parfaitement bien tant que vous savez quelle est cette manière, afin que vous puissiez y accéder de manière appropriée par la suite. Gardez à l’esprit qu’un vecteur multiplié par une matrice devrait vous donner le même vecteur, que vous utilisiez une notation mathématique en ligne ou en colonne. Ainsi, ce qui est vraiment important est que vous multipliez les coordonnées (x,y,z) de votre vecteur par les bons coefficients de la matrice, ce qui nécessite la connaissance de la façon dont « vous » avez décidé de stocker le coefficient de la matrice en mémoire :

Vector3 vecMatMult ( Vector3 v, float AXx, float AXy, float AXz, float Tx, float AYx, float AYy, float AYz, float Ty, float AZz, float AZy, float AZz, float Tz) { return Vector3( v.x * AXx + v.y * AYx + v.z * AZx + Tx, v.x * AXy + v.y * AYy + v.z * AZy + Ty, v.x * AXz + v.y * AZz + v.z * AZz + Tz}

Nous avons écrit cette fonction pour souligner le fait que, quelle que soit la convention que vous utilisez, le résultat de la multiplication vecteur * matrice est juste une multiplication et une addition entre les coordonnées d’entrée du vecteur et les coordonnées d’axe AX, AY et AZ du système de coordonnées (quelle que soit la notation que vous utilisez, et quelle que soit la façon dont vous les stockez en mémoire). Si vous utilisez:

float m = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

Vous devez appeler:

vecMatMult(v, m, m, m, m, m, m, m, m, m, ….

Si vous utilisez:

float m = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

Vous devez appeler:

vecMatMult(v, m, m, m, m, m, m, m, m, ….

Est-ce que cela vous dit quelle convention vous utilisez ? Non. Vous avez juste besoin d’appeler les bons coefficients aux bons endroits lorsque vous faites une multiplication vec * mat. Et c’est tout ce qu’il y a à faire, aussi déconcertant que cela puisse paraître.Maintenant, les choses sont légèrement différentes lorsqu’il s’agit de la multiplication mat * mat. Vous pouvez supposer que l’ordre dans lequel vous multipliez les matrices n’est pas le même. Ainsi, R * S * T n’est pas la même chose que T * S * R. L’ordre importe en effet. Maintenant, encore une fois, si vous utilisez la « ligne majeure » alors mathématiquement vous devez écrire:

mt11 = ml11 * mr11 + ml12 * mr21 + ml13 * mr31 + ml14 * mr41

où ml est la matrice de gauche et mr celle de droite : mt = ml * mr. Cependant, notez que nous n’avons pas utilisé de parenthèses pour les indices d’accès parce que nous ne voulons pas suggérer que nous accédons à des éléments stockés dans un tableau 1D ici. Nous parlons simplement des coefficients de matrices tels qu’ils sont écrits sur papier. Si vous voulez écrire cela en C++, alors tout dépend de la façon dont vous avez stocké vos coefficients en mémoire comme suggéré ci-dessus.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.