Alguém pode explicar as (razões para) implicações de colum vs row major na multiplicação / concatenação?

11

Estou tentando aprender a construir matrizes de visão e projeção e continuo alcançando dificuldades na minha implementação devido à minha confusão sobre os dois padrões de matrizes.
Sei como multiplicar uma matriz e vejo que a transposição antes da multiplicação mudaria completamente o resultado, daí a necessidade de multiplicar em uma ordem diferente.

O que não entendo é o que se entende por apenas 'convenção notacional' - dos artigos aqui e aqui os autores parecem afirmar que não faz diferença a maneira como a matriz é armazenada ou transferida para a GPU, mas na segunda página que a matriz claramente não é equivalente a como ela seria apresentada na memória para linhas principais; e se eu olhar para uma matriz preenchida no meu programa, vejo os componentes de tradução ocupando os 4º, 8º e 12º elementos.

Dado que:

"pós-multiplicar com matrizes principais da coluna produz o mesmo resultado que pré-multiplicar com matrizes principais da linha".

Por que no seguinte trecho de código:

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

R! = R2 e por que pos3! = Pos para :

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

O processo de multiplicação muda dependendo se as matrizes são importantes em linha ou coluna , ou é apenas a ordem (para um efeito equivalente?)

Uma coisa que não ajuda a tornar isso mais claro é que, quando fornecida ao DirectX, a matriz WVP principal da minha coluna é usada com sucesso para transformar vértices com a chamada HLSL: mul (vetor, matriz) que deve resultar no tratamento do vetor como row-major , então como a matriz principal da coluna fornecida pela minha biblioteca de matemática funciona?

sebf
fonte

Respostas:

11

se eu olhar para uma matriz preenchida no meu programa, vejo os componentes de tradução ocupando os 4º, 8º e 12º elementos.

Antes de começar, é importante entender: isso significa que suas matrizes são muito importantes . Portanto, você responde a esta pergunta:

a matriz WVP principal da minha coluna é usada com sucesso para transformar vértices com a chamada HLSL: mul (vetor, matriz) que deve resultar no tratamento do vetor como linha principal, então como a matriz principal da coluna fornecida pela minha biblioteca de matemática funciona?

é bem simples: suas matrizes são muito importantes.

Muitas pessoas usam matrizes de linha principal ou transpostas, que esquecem que as matrizes não são naturalmente orientadas dessa maneira. Então eles veem uma matriz de tradução como esta:

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

Esta é uma matriz de tradução transposta . Não é assim que uma matriz de tradução normal se parece. A tradução está na quarta coluna , não na quarta linha. Às vezes, você até vê isso nos livros didáticos, que são lixo absoluto.

É fácil saber se uma matriz em uma matriz é de linha ou coluna principal. Se for maior da linha, a tradução será armazenada nos índices 3, 7 e 11. Se for maior da coluna, a tradução será armazenada nos índices 12, 13 e 14. Índices de base zero, é claro.

Sua confusão resulta de acreditar que você está usando matrizes principais da coluna quando, na verdade, está usando matrizes principais da linha.

A afirmação de que linha versus coluna principal é apenas uma convenção notacional é inteiramente verdadeira. A mecânica da multiplicação de matrizes e multiplicação de matrizes / vetores é a mesma, independentemente da convenção.

O que muda é o significado dos resultados.

Afinal, uma matriz 4x4 é apenas uma grade de números 4x4. Não precisa se referir a uma mudança no sistema de coordenadas. No entanto, depois de atribuir significado a uma matriz específica, agora você precisa saber o que está armazenado nela e como usá-lo.

Veja a matriz de tradução que mostrei acima. Essa é uma matriz válida. Você pode armazenar essa matriz de float[16]uma de duas maneiras:

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

No entanto, eu disse que essa matriz de tradução está errada, porque a tradução está no lugar errado. Eu disse especificamente que ele é transposto em relação à convenção padrão de como criar matrizes de tradução, que deve ser assim:

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

Vamos ver como eles são armazenados:

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

Observe que column_majoré exatamente o mesmo que row_major_t. Portanto, se pegarmos uma matriz de conversão adequada e a armazenamos como principal da coluna, é o mesmo que transpor essa matriz e armazená-la como principal da linha.

É isso que significa ser apenas uma convenção notacional. Na verdade, existem dois conjuntos de convenções: armazenamento e transposição de memória. O armazenamento de memória é principal em relação à coluna, enquanto a transposição é normal em comparação à transposta.

Se você tiver uma matriz que foi gerada na ordem principal da linha, poderá obter o mesmo efeito transpondo o equivalente da coluna principal dessa matriz. E vice versa.

A multiplicação de matrizes só pode ser feita de uma maneira: dadas duas matrizes, em uma ordem específica, você multiplica certos valores e armazena os resultados. Agora,, A*B != B*Amas o código-fonte real de A*Bé o mesmo que o código de B*A. Ambos executam o mesmo código para calcular a saída.

O código de multiplicação da matriz não se importa se as matrizes são armazenadas na ordem da coluna principal ou da linha principal.

O mesmo não pode ser dito para a multiplicação de vetores / matrizes. E aqui está o porquê.

A multiplicação de vetores / matrizes é uma falsidade; isso não pode ser feito. No entanto, você pode multiplicar uma matriz por outra matriz. Portanto, se você finge que um vetor é uma matriz, pode efetivamente fazer a multiplicação de vetores / matrizes, simplesmente fazendo a multiplicação de matrizes / matrizes.

Um vetor 4D pode ser considerado um vetor de coluna ou um vetor de linha. Ou seja, um vetor 4D pode ser pensado como uma matriz 4x1 (lembre-se: na notação da matriz, a contagem de linhas vem primeiro) ou uma matriz 1x4.

Mas eis o seguinte: dadas duas matrizes A e B, A*Bapenas é definido se o número de colunas de A for igual ao número de linhas de B. Portanto, se A é nossa matriz 4x4, B deve ser uma matriz com 4 linhas iniciar. Portanto, você não pode executar A*x, onde x é um vetor de linha . Da mesma forma, você não pode executar x*Aonde x é um vetor de coluna.

Por esse motivo, a maioria das bibliotecas matemáticas de matrizes faz essa suposição: se você multiplicar um vetor por matriz, realmente pretende fazer a multiplicação que realmente funciona , e não a que não faz sentido.

Vamos definir, para qualquer vetor 4D x, o seguinte. Cdeve ser a forma da matriz vetor-coluna de xe Rdeve ser a forma da matriz vetor-linha de x. Dado isso, para qualquer matriz 4x4 A, A*Crepresenta a multiplicação da matriz A pelo vetor da coluna x. E R*Arepresenta a matriz multiplicando o vetor de linha xpor A.

Mas se olharmos para isso usando matemática matricial estrita, veremos que elas não são equivalentes . R*A não pode ser o mesmo que A*C. Isso ocorre porque um vetor de linha não é a mesma coisa que um vetor de coluna. Eles não são da mesma matriz e, portanto, não produzem os mesmos resultados.

No entanto, eles estão relacionados de uma maneira. É verdade isso R != C. No entanto, também é verdade que , onde T é a operação de transposição. As duas matrizes são transpostas uma da outra.R = CT

Aqui está um fato engraçado. Como os vetores são tratados como matrizes, eles também têm uma questão de armazenamento de coluna versus linha principal. O problema é que ambos parecem iguais . A matriz de flutuadores é a mesma, então você não pode dizer a diferença entre R e C apenas olhando os dados. A única maneira de dizer a diferença é como eles são usados.

Se você tiver duas matrizes A e B, e A for armazenada como maior de linha e B como maior de coluna, multiplicá-las não fará sentido . Você fica sem sentido como resultado. Bem, na verdade não. Matematicamente, o que você recebe é o equivalente a fazer . Ou ; eles são matematicamente idênticos.AT*BA*BT

Portanto, a multiplicação de matrizes só faz sentido se as duas matrizes (e lembre-se: multiplicação de vetores / matrizes é apenas multiplicação de matrizes) são armazenadas na mesma ordem principal.

Então, um vetor é uma coluna principal ou uma linha principal? É ambos e nenhum, como afirmado anteriormente. Ele é importante na coluna somente quando é usado como uma matriz de colunas e é importante na linha quando usado como uma matriz de linhas.

Portanto, se você tiver uma matriz A que é a coluna maior, x*Asignifica ... nada. Bem, novamente, isso significa , mas não é isso que você realmente queria. Da mesma forma, a multiplicação transposta se é maior da linha.x*ATA*xA

Portanto, a ordem de vector multiplicação / matriz faz mudança, dependendo da sua grande ordem dos dados (e se você estiver usando matrizes transpostas).

Por que, no seguinte trecho de código, r! = R2

Porque seu código está quebrado e com erros. Matematicamente . Se você não obtiver esse resultado, seu teste de igualdade está errado (problemas de precisão de ponto flutuante) ou o código de multiplicação da matriz está quebrado.A * (B * C) == (CT * BT) * AT

por que pos3! = pos para

Porque isso não faz sentido. A única maneira de ser verdade seria se . E isso é verdade apenas para matrizes simétricas.A * t == AT * tA == AT

Nicol Bolas
fonte
@ Nicol, Tudo está começando a clicar agora. Houve confusão devido a uma desconexão entre o que eu estava vendo e o que eu pensava que deveria ser, pois minha biblioteca (retirada do Axiom) declara a coluna principal (e todas as ordens de multiplicação etc. estão em conformidade com isso), mas o layout da memória é de linha -major (a julgar pelos índices de tradução e o fato de o HLSL funcionar corretamente usando a matriz não transposta); Agora vejo agora como isso não está em conflito. Muito obrigado!
sebf
2
Eu quase lhe dei -1 por dizer coisas como "Não é assim que uma matriz de tradução normal se parece" e "que é lixo total". Então você continua e explica bem por que eles são completamente equivalentes e, portanto, nenhum deles é mais "natural" que o outro. Por que você não remove esse pequeno absurdo desde o começo? O restante da sua resposta é de fato bastante bom. (Também para os interessados: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
imre
2
@ imre: Porque não é um absurdo. As convenções são importantes, pois é confuso ter duas convenções. Os matemáticos se estabeleceram na convenção para matrizes há muito tempo. "Matrizes transpostas" (nomeadas porque são transpostas do padrão) são uma violação dessa convenção. Como são equivalentes, não oferecem nenhum benefício real ao usuário. E, como são diferentes e podem ser mal utilizados, isso cria confusão. Ou, em outras palavras, se matrizes transpostas não existissem, o OP nunca teria perguntado isso. E, portanto, essa convenção alternativa cria confusão.
Nicol Bolas
1
@ Nicol: Uma matriz com tradução em 12-13-14 ainda pode ser a linha principal - se usarmos vetores de linha com ela (e multiplicar como vM). Veja DirectX. OU pode ser visualizado como principal da coluna, usado com vetores de coluna (Mv, OpenGL). É realmente o mesmo. Por outro lado, se uma matriz tiver conversão em 3-7-11, poderá ser vista como uma matriz principal de linha com vetores de coluna ou OU principal de coluna com vetores de linha. A versão 12-13-14 é mais comum, de fato, mas na minha opinião 1) não é realmente um padrão e 2) chamá-lo de coluna principal pode ser enganosa, pois não é necessariamente isso.
Imre
1
@ imre: é padrão. Pergunte a qualquer matemático treinado de verdade para onde a tradução vai, e eles dirão que ela está na quarta coluna. Os matemáticos inventaram matrizes; são eles que estabelecem as convenções.
Nicol Bolas
3

Há duas opções diferentes de convenções em ação aqui. Um é se você usa vetores de linha ou vetores de coluna, e as matrizes para essas convenções são transpostas uma da outra.

O outro é se você armazena as matrizes na memória na ordem principal da linha ou na ordem principal da coluna. Observe que "linha principal" e "coluna principal" não são os termos corretos para discutir a convenção vetor de linha / vetor de coluna ... mesmo que muitas pessoas os usem mal como tal. Os layouts de memória principal e principal e coluna também diferem por uma transposição.

O OpenGL usa uma convenção de vetor de coluna e uma ordem de armazenamento principal da coluna, e o D3D usa uma convenção de vetor de linha e uma ordem de armazenamento principal da linha (bem - pelo menos o D3DX, a biblioteca de matemática, o faz), então as duas transposições se cancelam e acontece que o mesmo layout de memória funciona para o OpenGL e o D3D. Ou seja, a mesma lista de 16 carros alegóricos armazenados seqüencialmente na memória funcionará da mesma maneira nas duas APIs.

Pode ser isso que as pessoas dizem que "não faz diferença a maneira como a matriz é armazenada ou transferida para a GPU".

Quanto aos trechos de código, r! = R2 porque a regra para transpor um produto é (ABC) ^ T = C ^ TB ^ TA ^ T. A transposição distribui sobre a multiplicação com uma ordem de reverência. Portanto, no seu caso, você deve obter r.Transpose () == r2, não r == r2.

Da mesma forma, pos! = Pos3 porque você transpôs, mas não reverteu a ordem de multiplicação. Você deve obter wpvM * localPos == localPos * wvpM.Tranpose (). O vetor está sendo interpretado automaticamente como um vetor de linha quando multiplicado no lado esquerdo de uma matriz e como um vetor de coluna quando multiplicado no lado direito de uma matriz. Fora isso, não há mudança na maneira como a multiplicação é realizada.

Por fim, re: "a matriz WVP principal da minha coluna é usada com sucesso para transformar vértices com a chamada HLSL: mul (vetor, matriz)", não tenho certeza disso, mas talvez confusão / um bug tenha causado a saída da matriz a biblioteca de matemática já transposta.

Nathan Reed
fonte
1

Nos gráficos 3D, você usa a matriz para transformar vetor e pontos. Considerando o fato de você estar falando sobre matriz de tradução, falarei apenas de pontos (você não pode traduzir um vetor com uma matriz ou, melhor dizendo, pode, mas obterá o mesmo vetor).

Na multiplicação de matrizes, o número de colunas da primeira matriz deve ser igual ao número de linhas da segunda (você pode multiplicar a matriz de ansiedade por um mxk).

Um ponto (ou um vetor) é representado por 3 componentes (x, y, z) e pode ser considerado como uma linha ou uma coluna:

colum (dimensão 3 X 1):

| x |

| y |

| z |

ou

linha (dimensão 1 X 3):

| x, y, z |

Você pode escolher a convenção preferida, é apenas uma convenção. Vamos chamá-lo de matriz de tradução. Se você escolher a primeira convenção, para multiplicar um ponto p para uma matriz, você precisará usar uma pós-multiplicação:

T * v (dimensão 3x3 * 3x1)

de outra forma:

v * T (dimensão 1x3 * 3x3)

os autores parecem afirmar que não faz diferença a maneira como a matriz é armazenada ou transferida para a GPU

Se você usar sempre a mesma convenção, isso não fará diferença. Isso não significa que a matriz de convenção diferente terá a mesma representação de memória, mas que, ao transformar um ponto com as 2 convenções diferentes, você obterá o mesmo ponto transformado:

p2 = B * A * p1; // primeira convenção

p3 = p1 * A * B; // segunda convenção

p2 == p3;

Heisenbug
fonte
1

Vejo que os componentes de tradução que ocupam os 4º, 8º e 12º elementos significam que suas matrizes estão "erradas".

Os componentes de conversão são sempre especificados como entradas 13, 14 e 15 da matriz de transformação ( contando o primeiro elemento da matriz como elemento 1 ).

Uma matriz de transformação principal de linha se parece com isso:

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

Uma matriz de transformação principal da coluna se parece com isso:

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

As matrizes principais da linha são especificadas descendo as linhas .

Declarando a matriz principal da linha acima como uma matriz linear, eu escreveria:

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

Isso parece muito natural. Como aviso, o inglês é escrito "row-major" - a matriz aparece no texto acima exatamente como será em matemática.

E aqui está o ponto de confusão.

As matrizes principais da coluna são especificadas descendo as colunas

Isso significa que, para especificar a matriz de transformação principal da coluna como uma matriz linear no código, você precisará escrever:

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

Note que isso é completamente contra-intuitivo !! Uma matriz principal da coluna tem suas entradas especificadas nas colunas ao inicializar uma matriz linear; portanto, a primeira linha

COLUMN_MAJOR = { R00, R10, R20, 0,

Especifica a primeira coluna da matriz:

 R00
 R10
 R20
  0 

e não a primeira linha , como você acreditaria no layout simples do texto. Você precisa transpor mentalmente uma matriz principal da coluna quando a vir no código, porque os 4 primeiros elementos especificados realmente descrevem a primeira coluna. Suponho que seja por isso que muitas pessoas preferem matrizes de linha principal no código (GO DIRECT3D !! tosse.)

Portanto, os componentes de conversão estão sempre nos índices de matriz linear # 13, # 14 e # 15 (onde o primeiro elemento é # 1), independentemente se você estiver usando matrizes principais de linha ou coluna principal.

O que aconteceu com o seu código e por que ele funciona?

O que está acontecendo no seu código é que você possui uma matriz principal da coluna sim, mas coloca os componentes da tradução no local errado. Quando você transpõe a matriz, a entrada 4 passa para a entrada 13, a entrada 8 para 13 e a entrada 12 para 15. E aí está.

bobobobo
fonte
0

Simplificando, a razão da diferença é que a multiplicação da matriz não é comutativa . Com multiplicação regular de números, se A * B = C, segue-se que B * A também = C. Este não é o caso das matrizes. É por isso que escolher assuntos importantes na linha ou na coluna.

Por que não importa é que, em uma API moderna (e estou falando especificamente de shaders aqui), você pode escolher sua própria convenção e multiplicar suas matrizes na ordem correta para essa convenção em seu próprio código de shader. A API não aplica mais uma ou outra a você.

Maximus Minimus
fonte