¿Alguien puede explicar las (razones para) las implicaciones de la columna vs fila mayor en la multiplicación / concatenación?


11

Estoy tratando de aprender a construir matrices de vista y proyección, y sigo encontrando dificultades en mi implementación debido a mi confusión sobre los dos estándares para matrices.
cómo multiplicar una matriz, y puedo ver que la transposición antes de la multiplicación cambiaría completamente el resultado, de ahí la necesidad de multiplicar en un orden diferente.

Sin embargo, lo que no entiendo es qué se entiende solo por 'convención de notación' : a partir de los artículos aquí y aquí, los autores parecen afirmar que no hay diferencia en cómo se almacena o transfiere la matriz a la GPU, sino en el segundo página que la matriz claramente no es equivalente a cómo se presentaría en la memoria para la fila mayor; y si miro una matriz poblada en mi programa, veo los componentes de traducción que ocupan los elementos 4º, 8º y 12º.

Dado que:

"la multiplicación posterior con matrices de columnas principales produce el mismo resultado que la multiplicación previa con matrices de filas principales".

Por qué en el siguiente fragmento de código:

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

¿R! = R2 y por qué pos3! = Pos para :

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

¿El proceso de multiplicación cambia dependiendo de si las matrices son mayores de fila o columna , o es solo el orden (para un efecto equivalente?)

Una cosa que no ayuda a que esto sea más claro es que, cuando se proporciona a DirectX, mi matriz WVP principal de columna se usa con éxito para transformar vértices con la llamada HLSL: mul (vector, matriz) que debería dar como resultado que el vector sea tratado como row-major , entonces, ¿cómo puede funcionar la matriz de columnas principales proporcionada por mi biblioteca de matemáticas?



Respuestas:


11

Si miro una matriz poblada en mi programa, veo los componentes de traducción que ocupan los elementos 4º, 8º y 12º.

Antes de comenzar, es importante entender: esto significa que sus matrices son de fila mayor . Por lo tanto, responde a esta pregunta:

mi matriz WVP de columna principal se utiliza con éxito para transformar vértices con la llamada HLSL: mul (vector, matriz), lo que debería dar como resultado que el vector sea tratado como fila mayor, entonces, ¿cómo puede funcionar la matriz principal de columna proporcionada por mi biblioteca matemática?

es bastante simple: sus matrices son de fila mayor.

Tantas personas usan matrices de fila mayor o transpuestas, que olvidan que las matrices no están orientadas naturalmente de esa manera. Entonces ven una matriz de traducción como esta:

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

Esta es una matriz de traducción transpuesta . Así no es como se ve una matriz de traducción normal. La traducción va en la cuarta columna , no en la cuarta fila. A veces, incluso ves esto en los libros de texto, que es basura absoluta.

Es fácil saber si una matriz en una matriz es fila o columna mayor. Si es mayor de fila, entonces la traducción se almacena en los índices 3, 7 y 11. Si se trata de una columna principal, la traducción se almacena en los índices 12, 13 y 14. Índices de base cero, por supuesto.

Su confusión surge de creer que está usando matrices de columnas principales cuando de hecho está usando las de filas principales.

La afirmación de que fila vs. columna mayor es una convención de notación solamente es completamente cierta. La mecánica de la multiplicación matricial y la multiplicación matriz / vector son las mismas independientemente de la convención.

Lo que cambia es el significado de los resultados.

Una matriz 4x4 después de todo es solo una cuadrícula de números 4x4. No tiene que referirse a un cambio de sistema de coordenadas. Sin embargo, una vez que asigna significado a una matriz particular, ahora necesita saber qué está almacenado en ella y cómo usarla.

Tome la matriz de traducción que le mostré arriba. Esa es una matriz válida. Puede almacenar esa matriz en una float[16]de dos maneras:

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

Sin embargo, dije que esta matriz de traducción está mal, porque la traducción está en el lugar equivocado. Dije específicamente que se transpone en relación con la convención estándar sobre cómo construir matrices de traducción, que debería verse así:

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

Veamos cómo se almacenan estos:

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

Tenga en cuenta que column_majores exactamente lo mismo que row_major_t. Entonces, si tomamos una matriz de traducción adecuada y la almacenamos como columna mayor, es lo mismo que transponer esa matriz y almacenarla como fila mayor.

Eso es lo que significa ser solo una convención de notación. Realmente hay dos conjuntos de convenciones: almacenamiento de memoria y transposición. El almacenamiento de memoria es la columna frente a la fila principal, mientras que la transposición es normal frente a la transpuesta.

Si tiene una matriz que se generó en orden de fila mayor, puede obtener el mismo efecto transponiendo el equivalente de columna mayor de esa matriz. Y viceversa.

La multiplicación de matrices solo se puede hacer de una manera: dadas dos matrices, en un orden específico, se multiplican ciertos valores y se almacenan los resultados. Ahora, A*B != B*Apero el código fuente real para A*Bes el mismo que el código para B*A. Ambos ejecutan el mismo código para calcular la salida.

Al código de multiplicación de matrices no le importa si las matrices están almacenadas en orden de columna mayor o de fila mayor.

El mismo no puede decirse de vector / matriz de la multiplicación. Y he aquí por qué.

La multiplicación de vectores / matrices es una falsedad; No se puede hacer. Sin embargo, puede multiplicar una matriz por otra matriz. Entonces, si finges que un vector es una matriz, entonces puedes hacer efectivamente la multiplicación de vector / matriz, simplemente haciendo la multiplicación de matriz / matriz.

Un vector 4D puede considerarse un vector de columna o un vector de fila. Es decir, un vector 4D puede considerarse como una matriz 4x1 (recuerde: en notación matricial, el recuento de filas es lo primero) o una matriz 1x4.

Pero aquí está la cosa: dadas dos matrices A y B, A*Bsolo se define si el número de columnas de A es el mismo que el número de filas de B. Por lo tanto, si A es nuestra matriz 4x4, B debe ser una matriz con 4 filas en eso. Por lo tanto, no puede realizar A*x, donde x es un vector de fila . Del mismo modo, no puede realizar x*Adonde x es una columna-vector.

Debido a esto, la mayoría de las bibliotecas de matemáticas de matriz hacen esta suposición: si multiplica un vector por una matriz, realmente quiere hacer la multiplicación que realmente funciona , no la que no tiene sentido.

Definamos, para cualquier vector 4D x, lo siguiente. Cserá el vector columna de matriz forma de x, y Rserá la fila-vector matriz de forma de x. Dado esto, para cualquier matriz 4x4 A, A*Crepresenta la matriz que multiplica A por el vector columna x. Y R*Arepresenta la matriz multiplicando el vector fila xpor A.

Pero si miramos esto usando matemática matricial estricta, vemos que no son equivalentes . R*A no puede ser lo mismo que A*C. Esto se debe a que un vector fila no es lo mismo que un vector columna. No son la misma matriz, por lo que no producen los mismos resultados.

Sin embargo, están relacionados de una manera. Es verdad eso R != C. Sin embargo, también es cierto que , donde T es la operación de transposición. Las dos matrices son transposiciones una de la otra.R = CT

Aquí hay un hecho curioso. Como los vectores se tratan como matrices, también tienen una pregunta de almacenamiento columna vs. fila principal. El problema es que ambos se ven iguales . La matriz de flotadores es la misma, por lo que no puede distinguir la diferencia entre R y C simplemente mirando los datos. La única forma de notar la diferencia es por cómo se usan.

Si tiene dos matrices A y B, y A está almacenada como mayor de fila y B como mayor de columna, multiplicarlas no tiene sentido . Obtienes tonterías como resultado. Bueno en realidad no. Matemáticamente, lo que obtienes es el equivalente a hacer . o ; Son matemáticamente idénticos.AT*BA*BT

Por lo tanto, la multiplicación de matrices solo tiene sentido si las dos matrices (y recuerde: la multiplicación de vectores / matrices es solo una multiplicación de matrices) se almacenan en el mismo orden principal.

Entonces, ¿es un vector columna mayor o mayor fila? Es ambos y ninguno, como se dijo antes. Es mayor de columna solo cuando se usa como matriz de columna, y es mayor de fila cuando se usa como matriz de fila.

Por lo tanto, si tiene una matriz A que es la columna mayor, x*Asignifica ... nada. Bueno, de nuevo, significa , pero eso no es lo que realmente querías. Del mismo modo, se transpone la multiplicación si es mayor de fila.x*ATA*xA

Por lo tanto, el orden de los vectores / multiplicación de matrices hace el cambio, dependiendo de su gran orden de los datos (y si está utilizando matrices transpuestas).

¿Por qué en el siguiente fragmento de código r! = R2

Porque tu código está roto y tiene errores. Matemáticamente, . Si no obtiene este resultado, entonces su prueba de igualdad está mal (problemas de precisión de punto flotante) o su código de multiplicación de matriz está roto.A * (B * C) == (CT * BT) * AT

¿Por qué pos3! = pos para

Porque eso no tiene sentido. La única forma de ser verdad sería si . Y eso solo es cierto para las matrices simétricas.A * t == AT * tA == AT


@Nicol, Todo comienza a hacer clic ahora. Hubo confusión debido a una desconexión entre lo que estaba viendo y lo que pensé que debería ser, ya que mi biblioteca (tomada de Axiom) declara la columna principal (y todas las órdenes de multiplicación, etc., se ajustan a esto) pero el diseño de la memoria es fila -mayor (a juzgar por los índices de traducción y el hecho de que HLSL funciona correctamente utilizando la matriz no transpuesta); Ahora veo, sin embargo, cómo esto no está en conflicto. ¡Muchas gracias!
sebf

2
Casi le doy un -1 por decir cosas como "Eso no es lo que parece una matriz de traducción normal" y "que es basura absoluta". Luego continúas y explicas amablemente por qué son completamente equivalentes y, por lo tanto, ninguno es más "natural" que el otro. ¿Por qué no eliminas esas pequeñas tonterías desde el principio? El resto de su respuesta es bastante buena. (También, para los interesados: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
Imre

2
@imre: Porque no es una tontería. Las convenciones son importantes ya que es confuso tener dos convenciones. Los matemáticos se establecieron en la convención para matrices hace mucho tiempo. Las "matrices transpuestas" (nombradas porque se han transpuesto del estándar) son una violación de esa convención. Como son equivalentes, no brindan ningún beneficio real al usuario. Y dado que son diferentes y pueden ser mal utilizados, crea confusión. O para decirlo de otra manera, si las matrices transpuestas no existieran, el OP nunca habría preguntado esto. Y por lo tanto, esta convención alternativa crea confusión.
Nicol Bolas

1
@Nicol: una matriz que tenga traducción en 13-12-14 todavía puede ser fila mayor, si luego usamos vectores de fila con ella (y multiplicamos como vM). Ver DirectX. O puede verse como columna mayor, utilizada con vectores de columna (Mv, OpenGL). Realmente es lo mismo. Por el contrario, si una matriz tiene traducción en 3-7-11, entonces se puede ver como una matriz de fila mayor con vectores de columna, o columna mayor con vectores de fila. La versión 12-13-14 es más común, pero en mi opinión 1) no es realmente un estándar, y 2) llamarla columna mayor puede ser engañosa, ya que no es necesariamente eso.
Imre

1
@imre: es estándar. Pregúntele a cualquier matemático capacitado real dónde va la traducción, y le dirán que va en la cuarta columna. Los matemáticos inventaron matrices; ellos son los que establecen las convenciones.
Nicol Bolas

3

Hay dos opciones diferentes de convención en el trabajo aquí. Una es si usa vectores de fila o de columna, y las matrices para estas convenciones son transposiciones entre sí.

La otra es si almacena las matrices en la memoria en orden de fila mayor o en orden de columna mayor. Tenga en cuenta que "mayor-fila" y "mayor-columna" no son los términos correctos para discutir la convención fila-vector / columna-vector ... a pesar de que muchas personas los usan incorrectamente como tales. Los diseños de memoria de fila principal y columna principal también se diferencian por una transposición.

OpenGL usa una convención de vectores de columnas y un orden de almacenamiento de columnas principales, y D3D usa una convención de vectores de filas y un orden de almacenamiento de filas principales (bueno, al menos D3DX, la biblioteca matemática, lo hace), por lo que las dos transposiciones se cancelan y resulta el mismo diseño de memoria funciona tanto para OpenGL como para D3D. Es decir, la misma lista de 16 flotantes almacenados secuencialmente en la memoria funcionará de la misma manera en ambas API.

Esto puede ser lo que quieren decir las personas que dicen que "no importa cómo se almacena o transfiere la matriz a la GPU".

En cuanto a los fragmentos de código, r! = R2 porque la regla para la transposición de un producto es (ABC) ^ T = C ^ TB ^ TA ^ T. La transposición se distribuye sobre la multiplicación con una revelación de orden. Entonces, en su caso, debería obtener r.Transpose () == r2, no r == r2.

Del mismo modo, pos! = Pos3 porque transpuso pero no invirtió el orden de multiplicación. Debería obtener wpvM * localPos == localPos * wvpM.Tranpose (). El vector se interpreta automáticamente como un vector de fila cuando se multiplica en el lado izquierdo de una matriz, y como un vector de columna cuando se multiplica en el lado derecho de una matriz. Aparte de eso, no hay cambio en cómo se lleva a cabo la multiplicación.

Finalmente, re: "la matriz WVP principal de mi columna se utiliza con éxito para transformar vértices con la llamada HLSL: mul (vector, matriz)," no estoy seguro de esto, pero tal vez la confusión / un error ha causado que la matriz salga de La biblioteca de matemáticas ya se ha transpuesto.


1

En los gráficos en 3D, se usa la matriz para transformar vectores y puntos. Teniendo en cuenta el hecho de que estás hablando de una matriz de traducción, hablaré solo de puntos (no puedes traducir un vector con una matriz o, mejor dicho, puedes pero obtendrás el mismo vector).

En la multiplicación de matrices, el número de columna de la primera matriz debe ser igual al número de fila de la segunda (puede multiplicar la matriz ansm por un mxk).

Un punto (o un vector) está representado por 3 componentes (x, y, z) y puede considerarse como una fila o una columna:

Columna (dimensión 3 X 1):

| x |

| y |

| z |

o

fila (dimensión 1 X 3):

| x, y, z |

Puede elegir la convención preferida, es solo una convención. Llamémosle T la matriz de traducción. Si elige la primera convención, para multiplicar un punto p para una matriz, debe usar una multiplicación posterior:

T * v (dimensión 3x3 * 3x1)

de otra manera:

v * T (dimensión 1x3 * 3x3)

los autores parecen afirmar que no importa cómo se almacena o transfiere la matriz a la GPU

Si usa siempre la misma convención, no hay diferencia. No significa que la matriz de diferentes convenciones tendrá la misma representación de memoria, sino que al transformar un punto con las 2 convenciones diferentes obtendrá el mismo punto transformado:

p2 = B * A * p1; // primera convención

p3 = p1 * A * B; // segunda convención

p2 == p3;


1

Veo que los componentes de traducción que ocupan los elementos 4º, 8º y 12º significan que sus matrices están "equivocadas".

Los componentes de traducción siempre se especifican como entradas # 13, # 14 y # 15 de la matriz de transformación ( contando el primer elemento de la matriz como elemento # 1 ).

Una matriz de transformación de fila principal se ve así:

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

Una columna de matriz de transformación principal se ve así:

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

Las matrices principales de filas se especifican bajando las filas .

Declarando la matriz principal de la fila anterior como una matriz lineal, escribiría:

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

Eso parece muy natural. Como aviso, el inglés se escribe "row-major": la matriz aparece en el texto de arriba exactamente como lo será en matemáticas.

Y aquí está el punto de confusión.

Las matrices principales de columna se especifican bajando las columnas

Eso significa que para especificar la matriz de transformación principal de la columna como una matriz lineal en el código, tendría que escribir:

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

Tenga en cuenta que esto es completamente contra-intuitivo !! Una matriz principal de columna tiene sus entradas especificadas en las columnas al inicializar una matriz lineal, por lo que la primera línea

COLUMN_MAJOR = { R00, R10, R20, 0,

Especifica la primera columna de la matriz:

 R00
 R10
 R20
  0 

y no la primera fila , ya que el diseño simple del texto te haría creer. Debe transponer mentalmente una matriz principal de columna cuando la vea en código, porque los primeros 4 elementos especificados en realidad describen la primera columna. Supongo que esta es la razón por la cual mucha gente prefiere las matrices de filas principales en el código (¡¡¡DIRECT3D !! tos!).

Por lo tanto, los componentes de traducción siempre están en los índices de matriz lineal # 13, # 14 y # 15 (donde el primer elemento es el # 1), independientemente de si está utilizando matrices de fila mayor o columna mayor.

¿Qué pasó con tu código y por qué funciona?

Lo que sucede en su código es que tiene una columna de matriz principal, sí, pero coloca los componentes de traducción en el lugar equivocado. Cuando transpone la matriz, la entrada n. ° 4 va a la entrada n. ° 13, la entrada n. ° 8 a la n. ° 13 y la entrada n. ° 12 a la n. ° 15. Y ahí lo tienes.


0

En pocas palabras, la razón de la diferencia es que la multiplicación de matrices no es conmutativa . Con la multiplicación regular de números, si A * B = C, entonces se deduce que B * A también = C. Este no es el caso con las matrices. Es por eso que elegir ya sea mayor de fila o mayor de columna es importante.

Por qué no importa es que, en una API moderna (y estoy hablando específicamente de sombreadores aquí), puede elegir su propia convención y multiplicar sus matrices en el orden correcto para esa convención en su propio código de sombreador. La API ya no impone uno u otro en usted.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.