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.
Sé 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?
fuente
Respuestas:
Antes de comenzar, es importante entender: esto significa que sus matrices son de fila mayor . Por lo tanto, responde a esta pregunta:
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:
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: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í:
Veamos cómo se almacenan estos:
Tenga en cuenta que
column_major
es exactamente lo mismo querow_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*A
pero el código fuente real paraA*B
es el mismo que el código paraB*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*B
solo 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 realizarA*x
, donde x es un vector de fila . Del mismo modo, no puede realizarx*A
donde 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.
C
será el vector columna de matriz forma dex
, yR
será la fila-vector matriz de forma dex
. Dado esto, para cualquier matriz 4x4 A,A*C
representa la matriz que multiplica A por el vector columnax
. YR*A
representa la matriz multiplicando el vector filax
por A.Pero si miramos esto usando matemática matricial estricta, vemos que no son equivalentes .
R*A
no puede ser lo mismo queA*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*B
A*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*A
significa ... 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*AT
A*x
A
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).
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
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 * t
A == AT
fuente
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.
fuente
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:
o
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:
de otra manera:
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:
fuente
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í:
Una columna de matriz de transformación principal se ve así:
Las matrices principales de filas se especifican bajando las filas .
Declarando la matriz principal de la fila anterior como una matriz lineal, escribiría:
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:
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
Especifica la primera columna de la matriz:
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.
fuente
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.
fuente