Matrices

2.0 ¿Que es Matrix?

¿Te gustaria saber lo que es Matrix?. Matrix nos rodea. Esta por todas partes, incluso ahora, en esta misma web, puedes verla si miras por la ventana o al encender el ordenador. Puedes sentirla, cuando vas a cine, cuando programas, cuando juegas a tu juegos. Es la relidad que a sido colocada ante ti para ocultarte que vives una mentira: todo lo que ves no es 3D, y es gracias a Matrix.

¿Por que usar matrices? Gracias a ellas podremos trasladar, escalar y rotar en una misma operacion. Asi, si debemos aplicar a una geometria compleja una serie de transformaciones, no tendremos que calcularlas para cada vertice de esta, solo tendremos que hacerlo una vez, y aplicar el resultador a la geometria.

Esto, ademas de simplificar mucho el calculo, es muy interesante a la hora de optimizar. Para hacer esas tres operaciones en una, bastara con usar la multiplicacion de matrices. Si quieres optimizar tus calculos, solo tendras que preocuparte de hacer una multiplacion rapida.

Ademas, la mayoria de las tarjetas actuales realizan los calculos sobre matrices por hardware, liberando a la CPU de esa costosa tarea.

La manera mas simple de describir que es una matriz seria una tabla de elementos ordenadas en filas y columnas. Si una matriz A tiene n filas y m columnas, diremos que A es una matriz de mxn:

En este caso, tenemos una matriz de 3x3. Para referirnos a un elemento de una matriz, indicaremos la fila y la columna del elemento, asi, M10 valdria 8.

Referirnos a un elemento cualquiera de la matriz seria Mij, donde i representa las filas y j las columnas. Esta forma de identificar elementos se conoce como Row Major, y es el sistema que usa DirectX (lo contrario seria Column Major).

Otra cosa a tener encuenta, seria si empezar en 0 (zero-based) los indices o no. Nuestras matrices seran zero-based:

Las que nos interesan a nosotros son principalmente las de 4x4 (aunque tambien tendremos de 3x3). Las definiremos asi:

3×3

 union
{
struct
{
real m00, m01, m02,
m10, m11, m12,
m20, m21, m22;
};
struct
{
real row0[3], row1[3], row2[3];
};
real ij[3][3];
real e[9];
};
4×4

 union
{
struct
{
real m00, m01, m02, m03,
m10, m11, m12, m13,
m20, m21, m22, m23,
m30, m31, m32, m33;
};
struct
{
real row0[4], row1[4], row2[4], row3[4];
};
real ij[4][4];
real e[16];
};

2.1 Matrices especiales

Si el numero de filas es igual al numero de columnas diremos que es una matriz cuadrada. Si todos sus elementos son cero, diremos que es una matriz nula.
A la serie de elementos de una matriz que cumplen i = j se le llama diagonal de la matriz. Una matriz no nula, con todos los elementos de su diagonal a cero, es una matriz diagonal.

Si cojemos una matriz nula y cambiamos su diagonal por unos, tendremos la llamada matriz identidad, llamada I:

2.2 Operaciones basicas

Las operaciones basicas son suma, resta y multiplicacion por escalar de dos matrices n x m. La suma de dos matrices, consiste en sumar el elemento de la primera con el de la segunda: Cij = Aij + Bij

La forma de restar es similar: Cij = AijBij. Multiplicar por un escalar una matriz, es tambien muy sencillo: Cij = aAij. Para cualquier matriz, A, se cumple que: 0A = 0.

La multiplicacion de matrices se usa constantemente y es muy importante. Para multiplicar 2 matrices, A y B, el numero de
columnas de A debe ser igual al numero de filas de B. Asi, si A es una matriz de n x m y B es de m x p, el resultado de hacer AB sera una matriz de n x p, Cnp = Anm x Bmp.

Basicamente consiste en multiplicar los elementos de las columnas de la primera, por los elementos de las filas de la segunda:

La letra al final de la formula (sigma) significa sumatorio, y se podria entender como “la suma de todos los AikBkj para k = 1 hasta n“, osea, como el FOR de C.

mat4 mat4::operator * (const mat4& m) const
{
mat4 prod;

for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++)
prod[row][col] = ij[row][0]*m[0][col] + ij[row][1]*m[1][col] + ij[row][2]*m[2][col] + ij[row][3]*m[3][col];

return prod;
}

Esta forma de multiplicar es facil de entender, pero no es la mas optima. La multiplicacion de matrices la usaremos constantemente en nuestros calculos, y seria mas rapido si desenrrollamos (quitar los bucles, unroll en ingles). Quedaria asi:

mat4 mat4::operator * (const mat4& m) const
{
mat4 prod;

prod[0][0] = ij[0][0]*m[0][0] + ij[0][1]*m[1][0] + ij[0][2]*m[2][0] + ij[0][3]*m[3][0];
prod[0][1] = ij[0][0]*m[0][1] + ij[0][1]*m[1][1] + ij[0][2]*m[2][1] + ij[0][3]*m[3][1];
prod[0][2] = ij[0][0]*m[0][2] + ij[0][1]*m[1][2] + ij[0][2]*m[2][2] + ij[0][3]*m[3][2];
prod[0][3] = ij[0][0]*m[0][3] + ij[0][1]*m[1][3] + ij[0][2]*m[2][3] + ij[0][3]*m[3][3];

prod[1][0] = ij[1][0]*m[0][0] + ij[1][1]*m[1][0] + ij[1][2]*m[2][0] + ij[1][3]*m[3][0];
prod[1][1] = ij[1][0]*m[0][1] + ij[1][1]*m[1][1] + ij[1][2]*m[2][1] + ij[1][3]*m[3][1];
prod[1][2] = ij[1][0]*m[0][2] + ij[1][1]*m[1][2] + ij[1][2]*m[2][2] + ij[1][3]*m[3][2];
prod[1][3] = ij[1][0]*m[0][3] + ij[1][1]*m[1][3] + ij[1][2]*m[2][3] + ij[1][3]*m[3][3];

prod[2][0] = ij[2][0]*m[0][0] + ij[2][1]*m[1][0] + ij[2][2]*m[2][0] + ij[2][3]*m[3][0];
prod[2][1] = ij[2][0]*m[0][1] + ij[2][1]*m[1][1] + ij[2][2]*m[2][1] + ij[2][3]*m[3][1];
prod[2][2] = ij[2][0]*m[0][2] + ij[2][1]*m[1][2] + ij[2][2]*m[2][2] + ij[2][3]*m[3][2];
prod[2][3] = ij[2][0]*m[0][3] + ij[2][1]*m[1][3] + ij[2][2]*m[2][3] + ij[2][3]*m[3][3];

prod[3][0] = ij[3][0]*m[0][0] + ij[3][1]*m[1][0] + ij[3][2]*m[2][0] + ij[3][3]*m[3][0];
prod[3][1] = ij[3][0]*m[0][1] + ij[3][1]*m[1][1] + ij[3][2]*m[2][1] + ij[3][3]*m[3][1];
prod[3][2] = ij[3][0]*m[0][2] + ij[3][1]*m[1][2] + ij[3][2]*m[2][2] + ij[3][3]*m[3][2];
prod[3][3] = ij[3][0]*m[0][3] + ij[3][1]*m[1][3] + ij[3][2]*m[2][3] + ij[3][3]*m[3][3];

return prod;
}

La multiplicion de matrices, no es conmutativa. Por lo general, AB no es igual que BA. Una excepcion seria el caso de multiplicar una matriz, por la matriz identidad, resultando la misma matriz original, AI = IA = A.

Multiplicar una matriz por la matriz nula, resulta la matriz nula.

Si a una matriz le cambiamos los elementos Aij por Aji (cambiar filas por columnas), habremos obtenido la traspuesta de A, que notaremos como AT. Como resulta logico imaginar, se cumple que (AT)T = A.

Teorema 1[2.2]
Dados dos escalares, a y b, y tres matrices de nxm A, B y C, se cumple lo siguiente:

(a) A + B = B + A

(b) (A + B) + C = A + (B + C)

(c) a(bA) = (ab)A

(d) a(A + B) = aA + aB

(e) (a + b)A = aA + bB

(f) (AB)T = ATBT

Una matriz M de n x n (cuadrada) diremos que tiene inversa, llamada M-1, si MM-1 = M-1M = I. No todas las matrices tienen inversa. A aquellas matrices que no tienen inversa se las llama singulares. Un ejemplo de matriz singular seria aquella que tubiera una fila y/o columna llena de ceros. Una matriz M es invertible (que tiene inversa) si y solo si MT es invertible.

Llamaremos matriz ortogonal a aquellas que su inversa es igual a su traspuesta, osea, que una matriz es ortogonal, si y solo si M-1 = MT.

Una propiedad interesante de estas matrices, es que mantienen el modulo y el angulo de un vector cuando son usadas para trasformaciones.

Existen otras muchas operaciones, como el determinante, vectores eigen, diagonalizar, etc que no veremos por no ser muy utiles por ahora.

2.3 Transformaciones

El conjunto de operaciones que vamos a necesitar hacer sobre vectores 3D podemos llamarlas transformaciones. Escalar un vector seria un ejemplo de transformacion lineal, osea que el resultado es proporcional a los datos iniciales.

Trasladar vectores no es una transformacion lineal, pero haciendo unos cambios podremos hacerlo con matrices (que solo sirven para transformaciones lineales).

Esta es la razon de usar vectores y matrices de 4 dimensiones y no de 3. A esta 4 coordenada en nuestros vectores la llamaremos coordenada homogenea (comunmente designada como w, que normalmente podremos olvidar y que suele valer 1) y al proceso de dividir las 3 coordenadas (x, y y z) por w, lo llamaremos homogeneizar. Podriamos pensar que este proceso seria como proyectar un vector de dimension N en N-1.

Para realizar estar operaciones, nos apoyaremos en las matrices, ya que, cualquier transformacion lineal de Rn a Rm puede ser representada por una matriz de m x n.

Para transformar un vector, que puede ser representado como una matriz de nx1, calculamos Lv, donde L es la matriz de transformacion linear (llamada matriz de induccion) y v es el vector. El resultado seria un vector en Rm.

Las tipicas operaciones de un motor 3D sobre vectores pueden realizarse con matrices de 3×3, menos la traslacion que requiere una de 4×4. Es conveniente usar el mismo tamaño para todas, asi que usaremos matrices de 4×4. Esto significa que todos nuestros vectores deben ser de 4 dimensiones, y tener la cuarta componente a 1, de lo contrario los calculos de traslacion fallarian. A esta cuarta coordenada, la llamaremos coordenada w.

2.4 Matriz de escalado

La funcion de escalado multiplica las componentes x, y y z de un vector por un escalar. Podriamos definirla como S(v) = sxv1i + syv2j + szv3k + v41, donde s es un vector, cuyas componentes son lo que queremos escalar en cada eje. La matriz de induccion de esta transformacion seria:

Podremos escalar cualquier vector haciendo simplemente Sv. Veamoslo con un ejemplo, tenemos el vector v = <2, 3, 4> y queremos escalarlo por s = <0, 1, 2>:

El resultado seria el vector <0, 3, 8>, que corresponde con lo que vimos en la entrega anterior sobre escalado de vectores.

2.5 Matriz de traslacion

La funcion de traslacion mueve en el espacio un vector. Podriamos definirla como T(v) = (v1 + txv4)i + (v2 + tyv4)j + (v3 + tzv4)k + v41. La matriz de induccion de esta transformacion seria:

Podremos traladar cualquier vector calculando Tv. Si quisieramos trasladar, por ejemplo, el vector v = <1, 4, 2> y queremos trasladarlo por t = <1, 1, 1>:

Obtenemos el vector <3, 6, 4>, que seria lo que hubieramos obtenido usando las formulas del tutorial anterior.

2.6 Matriz de rotacion

En el tutorial anterior, vimos que para rotar un vector sobre los ejes de coordenadas, se calculaba en el eje x:

ynuevo = yviejocos(ß) – zviejosin(ß)

znuevo = zviejosin(ß) + yviejocos(ß)

en el eje y:

xnuevo = xviejocos(ß) – zviejosin(ß)

znuevo = zviejosin(ß) + xviejocos(ß)

y en el eje z:

xnuevo = xviejocos(ß) – yviejosin(ß)

ynuevo = yviejosin(ß) + xviejocos(ß)

Para estos calculos, es util cumplir con la regla de la mano derecha, que viene a decir que apuntes con el dedo gordo de tu mano derecha en la direccion positiva del eje en el que quieres rotar, y el sentido de tus dedos al cerrarse sobre la mano, indicaran el sentido positivo en la rotacion.

Tal como estan expresadas estas formulas, cumplen justamente lo contrario. Arreglarlas para que cumplan la norma, es facil. En el eje x:

ynuevo = yviejocos(ß) + zviejosin(ß)

znuevo = zviejosin(ß) – yviejocos(ß)

en el eje y:

xnuevo = xviejocos(ß) + zviejosin(ß)

znuevo = zviejosin(ß) – xviejocos(ß)

y en el eje z:


xnuevo = xviejocos(ß) + yviejosin(ß)

ynuevo = yviejosin(ß) – xviejocos(ß)

A partir de estas nuevas formulas, podemos definir las rotaciones en el eje x como:

Rx(v) = v1i + (v2cos(ß) + v3sin(ß))j + (v2sin(ß) – v3cos(ß))k + v41


en el eje y:

Ry(v) = (v1cos(ß) + v3sin(ß))i + v2j +(v3sin(ß) – v1cos(ß))k + v41

en el eje z:

Rz(v) = (v1cos(ß) + v2sin(ß))i + (v2sin(ß) – v1cos(ß))j + v3k + v41

Con lo que la matriz de induccion para la rotacion en el eje x seria:

para el eje y:

y para el eje z:

Lo comprobaremos con un ejemplo. Tenemos el vector v = <4, 0, 0> y queremos rotarlo en el eje z 90º:

Teniendo en cuenta la regla de la mano derecha, que nos indica que angulo crece en el sentido de las agujas del reloj, era facil adivinar que el resultado no podria ser otro que <0, -4, 0>.

2.7 Matriz de proyeccion

Entendemos por proyectar pasar una geometria 3D a otra 2D (R3 -> R2). Osea de nuestro imaginario mundo 3D a las tristes 2 dimensiones de nuestras pantallas. Para simplificar el proceso situaremos al observador (nosotros) en el origen de coordenadas y mirando hacia el eje z un solo un punto 3D.
Pensemos en un rayo que una este punto con nuestro ojo pasando por el monitor. El punto donde corta este rayo el monitor, seria el punto proyectado que buscamos:

Gracias a Thales sabemos que:

xs = x * d / z

ys = y * d / z

Osea que a medida que z crece, x e y disminiyen proporcionalmente. Su funcion de proyeccion es P(v) = v1d/v3i + v2d/v3j + dk. Esta formula no nos vale como dijimos antes. Despues de una serie de transformaciones, obtenemos una que si nos vale P(v) = v1i + v2j + v3k + v3/d1 de la que ya podemos extraer la matriz de proyeccion:

2.8 Matrix te posee…

Visto todo lo anterior, veamos un sencillo ejemplo para entender la necesidad de usar matrices para nuestros calculos 3D. Imaginemos que tenemos un modelo 3D al que queremos hacerle n transformaciones.

La unidad basica de cualquier geometria 3D, son los puntos (osea vectores) asi que imaginaremos esa geometria 3D como un conjunto, mas o menos bonito, de vectores. Sin usar matrices, solo tendriamos las formulas de transformaciones del tutorial anterior.
Deberiamos calcular las n transformaciones a cada vector del modelo.

Usando matrices, si queremos hacer n transformaciones, usaremos para ello n matrices de induccion : T1, T2, …, Tn.

Para calcular la primera transformacion, y siendo v el primer punto, haremos T1v. Luego aplicaremos la segunda transformacion, T2(T1v) y asi con todas las demas, Tn(Tn-1…(T2(T1v))…) que viene a ser lo mismo que TnT2T1v.

La multiplicacion de matrices es asociativa, osea, podemos mover los parentesis a nuestro antojo. Asi que tambien podemos escribir lo anterior como (TnT2T1)v. Asi tenemos (TnT2T1) por un lado, y v por otro.
Lo primero, despues de multiplarlas (tambien llamado concatenar) todas, es una matriz que multiplandola por v realizara todos los cambios que queriamos sobre el vector y sobre todos los demas de nuestra geometria. Y lo mejor es que solo habremos calculado (TnT2T1) una vez.

Cuidado con cambiar el orden de las transformaciones, ya que la multiplicacion de matrices no es conmutativa.

2.9 Espacios

Cuando usemos un modelo, normalmente lo habremos construido a partir de un fichero en el que estaran todos los datos de su geometria (entre otras cosas). Este fichero lo conseguimos a gracias a un exportador que posiblemente tendremos que construir nosotros mismos (y que por si solo seria un tema interesante para otros articulos).

Los datos de esta geometria (por simplificar solo tendremos vertices) tienen como eje de coordenadas un punto especifico, generalmente el centro, o la base del objeto (el pivot del 3DSMax por ejemplo). Diremos que estos vertices estan en espacio de objeto (object space)

Si tenemos varios objetos, cada uno tendra su geometria haciendo referencia a su propio espacio. Necesitamos crear un espacio comun a todos para poder situar nuestros objetos en el y crear un entorno.

A este espacio lo llamaremos espacio mundo, world espace (la mayoria de estos terminos seria mejor ni traducirlos…). Para pasar nuestros puntos de un espacio local al espacio mundo, usaremos una matriz homogenea de 4x4 que llamaremos matriz de transformacion mundo (world transformation matrix). Cada objeto tendra una, y almacenara cada rotacion, escalado o movimiento.

Ademas de pasar nuestros objetos de un espacio local a otro global, tenemos que pasar nuestros objetos a otro sistema distinto, el del punto de vista del usuario. Podemos imaginarnos que el usuario es una camara que se va desplazando y rotando.

Esto modificara la forma de proyectar en mundo en nuestras pantallas. Mas arriba vimos como hacerlo cuando la camara esta en el origen de coordenadas y mira al eje z, ¿ que pasa cuando el usuario se mueve ?

Lo primero que se nos ocurriria seria movernos hasta ese punto y calcular unas nuevas formulas de proyeccion. En cambio el profesor Hubert J. Farnsworth (de Futurama) nos diria que lo mas sencillo es dejar la camara donde esta, y mover el mundo en torno a ella (como hace el para mover su nave).

Asi mantendremos la camara en el origen de coordenadas, lo que simplifica los calculos. Si movemos la cabeza hacia arriba, veremos lo mismo que si rotamos el mundo hacia abajo.

Supongamos que tenemos la camara en la posicion v, y que esta orientada segun r (siendo rx, ry, rz los angulos en cada eje, y siendo
rx = ry = rz = 0 la direccion que indica el eje z positivo). Una vez que tenemos todos nuestros objetos en world space, para pasarlos
al espacio vista (view space) tendremos que transladar todo en el world space por -v y rotar todo en el world space por -rx, -ry y -rz.
Una vez hecho esto, solo tendramos que proyectar todo a nuestras pantallas como vimos mas arriba. Usando matrices, todo esto quedaria asi:

P x S x Rz x Ry x Rx x T

El orden en el que hemos hecho las rotaciones, es porque la camara apunta al eje z positivo. No siempre esta claro como elegir estos angulos para una determinada orientacion, pero eso lo veremos mas profundamente en el siguiente capitulo sobre quaterniones.
Con esto se acaba el tema de las matrices. Podeis bajaros la nueva version GML, que incluye las clases mat3 y mat4.
En la siguiente entrega: quaternions, hasta pronto!!

Popularity: 39%

  1. #1 por Llorenç Marti el 07/11/2009 - 9:00 pm

    Holas

    Estaba mirando tus tutoriales para parender Quaterniones y de paso repasar matrices y me he dado cuenta que algunas de las representaciones de matrices, (en Chrome como en Explorer 8.0) no se ven.

    Un saludo

(no sera publicado)