Qualcuno può spiegare le (ragioni delle) implicazioni di colum vs row major nella moltiplicazione / concatenazione?


11

Sto cercando di imparare come costruire matrici di visualizzazione e proiezione e continuare a raggiungere difficoltà nella mia implementazione a causa della mia confusione riguardo ai due standard per le matrici.
So come moltiplicare una matrice, e posso vedere che trasposizione prima della moltiplicazione cambierebbe completamente il risultato, qui la necessità di moltiplicare in un ordine diverso.

Quello che non capisco però è cosa si intende solo per "convenzione notazionale" - dagli articoli qui e qui gli autori sembrano affermare che non fa differenza sul modo in cui la matrice viene archiviata o trasferita sulla GPU, ma sulla seconda pagina che matrice non è chiaramente equivalente a come sarebbe disposto in memoria per riga maggiore; e se osservo una matrice popolata nel mio programma vedo i componenti di traduzione che occupano il 4 °, 8 ° e 12 ° elemento.

Dato che:

"la post moltiplicazione con le matrici principali di colonna produce lo stesso risultato della pre-moltiplicazione con le matrici principali di riga."

Perché nel seguente frammento di codice:

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

Fa r! = R2 e perché pos3! = Pos per :

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

Il processo di moltiplicazione cambia a seconda che le matrici siano le righe o le colonne principali o è solo l'ordine (per un effetto equivalente?)

Una cosa che non aiuta questo a diventare più chiaro, è che quando fornito a DirectX, la mia matrice WVP principale di colonna viene utilizzata con successo per trasformare i vertici con la chiamata HLSL: mul (vettore, matrice) che dovrebbe comportare che il vettore venga trattato come riga maggiore , quindi come può funzionare la matrice maggiore colonna fornita dalla mia libreria matematica?



Risposte:


11

se osservo una matrice popolata nel mio programma vedo i componenti di traduzione che occupano il 4 °, 8 ° e 12 ° elemento.

Prima di iniziare, è importante capire: questo significa che le tue matrici sono le righe principali . Pertanto, rispondi a questa domanda:

la mia matrice WVP principale di colonna viene utilizzata con successo per trasformare i vertici con la chiamata HLSL: mul (vettore, matrice) che dovrebbe comportare che il vettore venga trattato come maggiore di riga, quindi come può funzionare la matrice di maggiore colonna fornita dalla mia libreria matematica?

è abbastanza semplice: le tue matrici sono in ordine di riga.

Così tante persone usano matrici di riga maggiore o trasposte, che dimenticano che le matrici non sono naturalmente orientate in quel modo. Quindi vedono una matrice di traduzione come questa:

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

Questa è una matrice di traduzione trasposta . Questo non è l'aspetto di una normale matrice di traduzione. La traduzione va nella quarta colonna , non nella quarta riga. A volte, lo vedi persino nei libri di testo, che è completamente spazzatura.

È facile sapere se una matrice in un array è di tipo riga o colonna. Se è la riga maggiore, la traduzione viene memorizzata negli indici 3, 7 e 11. Se è la colonna maggiore, la traduzione viene memorizzata negli indici 12, 13 e 14. Indici a base zero ovviamente.

La tua confusione deriva dal credere che stai usando le matrici principali di colonna quando in realtà stai usando quelle principali.

L'affermazione che riga vs. colonna maggiore è solo una convenzione notazionale è del tutto vera. La meccanica della moltiplicazione matrice e la moltiplicazione matrice / vettore sono le stesse indipendentemente dalla convenzione.

Ciò che cambia è il significato dei risultati.

Una matrice 4x4 dopo tutto è solo una griglia di numeri 4x4. Non deve fare riferimento a un cambio di sistema di coordinate. Tuttavia, una volta assegnato significato a una matrice particolare, ora è necessario sapere cosa è memorizzato in essa e come usarlo.

Prendi la matrice di traduzione che ti ho mostrato sopra. Questa è una matrice valida. Puoi memorizzare quella matrice float[16]in uno dei due modi seguenti:

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};

Tuttavia, ho detto che questa matrice di traduzione è sbagliata, perché la traduzione è nel posto sbagliato. Ho detto espressamente che è stato trasposto rispetto alla convenzione standard su come costruire matrici di traduzione, che dovrebbe apparire così:

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

Diamo un'occhiata a come sono memorizzati:

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};

Si noti che column_majorè esattamente lo stesso di row_major_t. Quindi, se prendiamo una matrice di traduzione adeguata e la memorizziamo come maggiore di colonna, è lo stesso che trasporre quella matrice e memorizzarla come maggiore di riga.

Questo è ciò che si intende per essere solo una convenzione notazionale. Esistono davvero due serie di convenzioni: memoria e trasposizione. La memoria è la colonna contro la riga maggiore, mentre la trasposizione è normale o trasposta.

Se hai una matrice che è stata generata in ordine di riga maggiore, puoi ottenere lo stesso effetto trasponendo l'equivalente colonna principale di quella matrice. E viceversa.

La moltiplicazione delle matrici può essere fatta in un solo modo: date due matrici, in un ordine specifico, moltiplichi determinati valori insieme e memorizzi i risultati. Ora, A*B != B*Ama il codice sorgente effettivo per A*Bè lo stesso del codice per B*A. Entrambi eseguono lo stesso codice per calcolare l'output.

Il codice di moltiplicazione matrice non importa se le matrici si trovano in ordine di colonna maggiore o di riga maggiore.

Lo stesso non si può dire per la moltiplicazione vettoriale / matrice. Ed ecco perché.

La moltiplicazione vettore / matrice è una falsità; non si può fare. Tuttavia, puoi moltiplicare una matrice per un'altra matrice. Quindi, se fai finta che un vettore sia una matrice, puoi effettivamente fare la moltiplicazione vettore / matrice, semplicemente facendo la moltiplicazione matrice / matrice.

Un vettore 4D può essere considerato un vettore colonna o un vettore riga. Cioè, un vettore 4D può essere pensato come una matrice 4x1 (ricorda: nella notazione della matrice, il conteggio delle righe viene prima) o una matrice 1x4.

Ma ecco la cosa: date due matrici A e B, A*Bè definita solo se il numero di colonne di A è uguale al numero di righe di B. Pertanto, se A è la nostra matrice 4x4, B deve essere una matrice con 4 righe dentro. Pertanto, non è possibile eseguire A*x, dove x è un vettore riga . Allo stesso modo, non è possibile eseguire x*Adove x è un vettore colonna.

Per questo motivo, la maggior parte delle librerie matematiche di matrice fa questo assunto: se moltiplichi un vettore per una matrice, intendi davvero fare la moltiplicazione che funziona davvero , non quella che non ha senso.

Definiamo, per ogni vettore 4D x, quanto segue. Cdeve essere la forma di matrice vettore-colonna di xe Rdeve essere la forma di matrice vettore-riga di x. Detto questo, per qualsiasi matrice 4x4 A, A*Crappresenta la matrice che moltiplica A per il vettore colonna x. E R*Arappresenta una matrice che moltiplica il vettore riga xper A.

Ma se guardiamo a questo usando una matrice matematica rigorosa, vediamo che questi non sono equivalenti . R*A non può essere uguale a A*C. Questo perché un vettore di riga non è la stessa cosa di un vettore di colonna. Non sono la stessa matrice, quindi non producono gli stessi risultati.

Tuttavia, sono correlati in un modo. È vero quello R != C. Tuttavia, è anche vero che , dove T è l'operazione di trasposizione. Le due matrici sono trasposte l'una dall'altra.R = CT

Ecco un fatto divertente. Poiché i vettori sono trattati come matrici, anche loro hanno una domanda di archiviazione tra colonna e riga. Il problema è che entrambi sembrano uguali . L'array di float è lo stesso, quindi non si può dire la differenza tra R e C solo guardando i dati. L' unico modo per distinguere è da come vengono utilizzati.

Se hai due matrici A e B e A viene memorizzata come riga maggiore e B come colonna maggiore, moltiplicarle è completamente insignificante . Di conseguenza ottieni sciocchezze. Beh, non proprio. Matematicamente, ciò che ottieni equivale a fare . Oppure ; sono matematicamente identici.AT*BA*BT

Pertanto, la moltiplicazione di matrici ha senso solo se le due matrici (e ricordate: la moltiplicazione di vettori / matrici è solo una moltiplicazione di matrici) sono memorizzate nello stesso ordinamento principale.

Quindi, è un vettore colonna maggiore o riga maggiore? È entrambi e nessuno dei due, come affermato in precedenza. È maggiore della colonna solo quando viene utilizzato come matrice di colonne ed è maggiore della riga quando viene utilizzato come matrice di righe.

Pertanto, se hai una matrice A che è la colonna maggiore, x*Asignifica ... niente. Bene, ancora una volta, significa , ma non è quello che volevi davvero. Allo stesso modo, traspone la moltiplicazione se è maggiore di riga.x*ATA*xA

Pertanto, l'ordine del vettore / moltiplicazione di matrici fa il cambiamento, a seconda della maggiore ordinamento dei dati (e se si sta utilizzando matrici trasposte).

Perché nel seguente frammento di codice r! = R2

Perché il tuo codice è rotto e difettoso. Matematicamente, . Se non si ottiene questo risultato, il test di uguaglianza è errato (problemi di precisione in virgola mobile) o il codice di moltiplicazione della matrice è interrotto.A * (B * C) == (CT * BT) * AT

perché pos3! = pos per

Perché non ha senso. L'unico modo per essere veri sarebbe se . E questo è vero solo per le matrici simmetriche.A * t == AT * tA == AT


@Nicol, Tutto inizia a fare clic ora. C'era confusione a causa di una disconnessione tra ciò che stavo vedendo e ciò che pensavo che avrei dovuto essere, poiché la mia libreria (tratta da Axiom) dichiara colonna maggiore (e tutti gli ordini di moltiplicazione ecc. Conformi a questo) ma il layout di memoria è fila -major (a giudicare dagli indici di traduzione e dal fatto che HLSL funziona correttamente usando la matrice non trasposta); Vedo ora tuttavia come ciò non sia in conflitto. Grazie mille!
sebf

2
Ti ho quasi dato un -1 per dire cose come "Non è quello che assomiglia a una normale matrice di traduzione" e "che è immondizia assoluta". Quindi vai avanti e spieghi bene perché sono completamente equivalenti e quindi nessuno dei due è più "naturale" dell'altro. Perché non rimuovi questa piccola assurdità dall'inizio? Il resto della tua risposta è in effetti piuttosto buono. (Inoltre, per gli interessati: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
imre

2
@imre: perché non ha senso. Le convenzioni sono importanti in quanto è confuso avere due convenzioni. I matematici si sono stabiliti molto tempo fa sulla convenzione per le matrici . Le "matrici trasposte" (chiamate perché trasposte dallo standard) violano tale convenzione. Dal momento che sono equivalenti, non danno alcun beneficio reale all'utente. E poiché sono diversi e possono essere usati in modo improprio, crea confusione. O per dirla in altro modo, se non esistessero matrici trasposte, il PO non lo avrebbe mai chiesto. E quindi, questa convenzione alternativa crea confusione.
Nicol Bolas,

1
@Nicol: una matrice con traduzione in 12-13-14 può essere ancora riga-maggiore - se poi utilizziamo i vettori di riga con essa (e moltiplichiamo come vM). Vedi DirectX. OPPURE può essere visualizzato come maggiore di colonna, utilizzato con i vettori di colonna (Mv, OpenGL). È davvero lo stesso. Viceversa, se una matrice ha una traduzione in 3-7-11, allora può essere vista come una matrice maggiore di riga con vettori di colonna, o maggiore di colonna con vettori di riga. La versione 12-13-14 è davvero più comune, ma a mio avviso 1) non è proprio uno standard e 2) chiamarla colonna maggiore può essere fuorviante, poiché non è necessariamente così.
imre

1
@imre: è standard. Chiedi a qualsiasi matematico effettivamente addestrato dove va la traduzione e ti diranno che va nella quarta colonna. I matematici inventarono le matrici; sono quelli che stabiliscono le convenzioni.
Nicol Bolas,

3

Ci sono due diverse scelte di convention al lavoro qui. Uno è se si usano vettori di riga o vettori di colonna e le matrici per queste convenzioni sono trasposte l'una dall'altra.

L'altro è se si memorizzano le matrici in memoria in ordine di riga o ordine di colonna. Nota che "riga maggiore" e "colonna maggiore" non sono i termini corretti per discutere della convenzione vettore riga / vettore riga ... anche se molte persone li usano in modo improprio. Anche i layout di memoria delle righe principali e delle colonne differiscono per una trasposizione.

OpenGL utilizza una convenzione di vettore di colonna e un ordine di archiviazione principale di colonna, e D3D usa una convenzione di vettore di riga e un ordine di archiviazione maggiore di riga (beh - almeno D3DX, la libreria matematica, lo fa), quindi i due trasposi si annullano e risulta lo stesso layout di memoria funziona sia per OpenGL che per D3D. Cioè, lo stesso elenco di 16 float memorizzati in sequenza in memoria funzionerà allo stesso modo in entrambe le API.

Questo potrebbe essere ciò che la gente dice che "non fa differenza nel modo in cui la matrice viene memorizzata o trasferita nella GPU".

Per quanto riguarda i frammenti di codice, r! = R2 perché la regola per la trasposizione di un prodotto è (ABC) ^ T = C ^ TB ^ TA ^ T. La trasposizione si distribuisce sulla moltiplicazione con un riverbero dell'ordine. Quindi nel tuo caso dovresti ottenere r.Transpose () == r2, non r == r2.

Allo stesso modo, pos! = Pos3 perché hai trasposto ma non hai invertito l'ordine di moltiplicazione. Dovresti ottenere wpvM * localPos == localPos * wvpM.Tranpose (). Il vettore viene automaticamente interpretato come vettore riga quando viene moltiplicato sul lato sinistro di una matrice e come vettore colonna quando viene moltiplicato sul lato destro di una matrice. Oltre a ciò, non vi è alcun cambiamento nel modo in cui viene eseguita la moltiplicazione.

Infine, ri: "la mia matrice WVP principale di colonna viene utilizzata con successo per trasformare i vertici con la chiamata HLSL: mul (vettore, matrice)," Non sono sicuro di questo, ma forse la confusione / un bug ha causato la fuoriuscita della matrice da la libreria matematica già trasposta.


1

Nella grafica 3d usi la matrice per trasformare sia il vettore che i punti. Considerando il fatto che stai parlando della matrice di traduzione, parlerò solo di punti (non puoi tradurre un vettore con una matrice o, dicendolo meglio, puoi ma otterrai lo stesso vettore).

Nella moltiplicazione di matrici il numero di colonne della prima matrice dovrebbe essere uguale al numero di righe della seconda (è possibile moltiplicare la matrice anxm per un mxk).

Un punto (o un vettore) è rappresentato da 3 componenti (x, y, z) e può essere considerato sia come una riga che una colonna:

colum (dimensione 3 X 1):

| X |

| Y |

| Z |

o

riga (dimensione 1 X 3):

| X, y, z |

Puoi scegliere la convenzione preferita, è solo una convenzione. Chiamiamola T la matrice di traduzione. Se scegli la prima convenzione, per moltiplicare un punto p per una matrice devi utilizzare una post moltiplicazione:

T * v (dimensione 3x3 * 3x1)

altrimenti:

v * T (dimensione 1x3 * 3x3)

gli autori sembrano affermare che non fa alcuna differenza sul modo in cui la matrice viene archiviata o trasferita alla GPU

Se usi sempre la stessa convenzione non fa differenza. Ciò non significa che la matrice di convenzione diversa avrà la stessa rappresentazione di memoria, ma che trasformando un punto con le 2 convenzioni diverse otterrai lo stesso punto trasformato:

p2 = B * A * p1; // prima convenzione

p3 = p1 * A * B; // seconda convenzione

p2 == p3;


1

Vedo che i componenti di traduzione che occupano il 4 °, 8 ° e 12 ° elemento indicano che le tue matrici sono "sbagliate".

I componenti di traduzione sono sempre specificati come voci # 13, # 14 e # 15 della matrice di trasformazione ( contando il primo elemento dell'array come elemento # 1 ).

Una matrice di trasformazione principale di riga è simile alla seguente:

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

Una matrice di trasformazione principale di colonna si presenta così:

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

Le matrici delle righe principali vengono specificate scendendo le righe .

Dichiarando la matrice principale di riga sopra come una matrice lineare, scriverei:

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

Sembra molto naturale. Perché notate, l'inglese è scritto "row-major" - la matrice appare nel testo sopra esattamente come sarà in matematica.

Ed ecco il punto di confusione.

Le matrici principali della colonna vengono specificate scendendo lungo le colonne

Ciò significa che per specificare la matrice di trasformazione principale della colonna come una matrice lineare nel codice, dovresti scrivere:

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // molto contro-intuitivo
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

Nota che questo è completamente contro-intuitivo !! Una matrice maggiore di colonna ha le sue voci specificate nelle colonne durante l'inizializzazione di un array lineare, quindi la prima riga

COLUMN_MAJOR = { R00, R10, R20, 0,

Specifica la prima colonna della matrice:

 R00
 R10
 R20
  0 

e non la prima riga , come vorrebbe far credere il semplice layout del testo. Devi trasporre mentalmente una matrice maggiore di colonna quando la vedi nel codice, perché i primi 4 elementi specificati descrivono effettivamente la prima colonna. Suppongo che questo sia il motivo per cui molte persone preferiscono le matrici delle righe principali nel codice (GO DIRECT3D !! tosse).

Pertanto, i componenti di traduzione sono sempre agli indici di array lineari n. 13, n. 14 e n. 15 (dove il primo elemento è n. 1), indipendentemente dal fatto che si utilizzino matrici di riga maggiore o di colonna maggiore.

Che cosa è successo con il tuo codice e perché funziona?

Quello che sta succedendo nel tuo codice è che hai una colonna matrice maggiore sì, ma metti i componenti di traduzione nel punto sbagliato. Quando si traspone la matrice, la voce # 4 passa alla voce # 13, le voci da 8 a 13 e le voci da 12 a 15. E il gioco è fatto.


0

In parole povere, la ragione della differenza è che la moltiplicazione della matrice non è commutativa . Con la moltiplicazione regolare dei numeri, se A * B = C, allora anche B * A = C. Questo non è il caso delle matrici. Ecco perché scegliere le questioni principali o di colonna.

Perché non importa è che, in una moderna API (e sto parlando in particolare di shader qui), puoi scegliere la tua convenzione e moltiplicare le tue matrici nell'ordine corretto per quella convenzione nel tuo codice shader. L'API non applica più l'uno o l'altro su di te.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.