inmensia |
Ray Tracing: Cálculo de Intersecciones y Normales
Juan Mellado, 19 Octubre, 2005 - 11:55
Intersección con una Primitiva TransformadaComo ya apuntábamos en el apartado anterior, los objetos que forman parte de una escena se definen normalmente con una primitiva geométrica, como esferas, cilindros o planos. Sin embargo, en la práctica no basta con indicar el número de objetos que se desea, sino que se debe caracterizar cada objeto con al menos una posición, y muy posiblemente con un tamaño concreto o una orientación determinada. Es decir, no basta con indicar que se quieren dibujar dos esferas, hay que indicar que una de ellas estará centrada, por ejemplo, en el punto (-5, 2, -3) y la otra en (3, 2, -1), y que la primera tendrá un radio 3 y la segunda un radio 1. Para conseguir esto, los objetos se definen con una primitiva y un conjunto de transformaciones. Las primitivas son formas básicas, como esferas, cilindros o conos, en su forma canónica, es decir, centradas en el origen de coordenadas y con radio la unidad. Y las transformaciones consisten en escalados, rotaciones y traslaciones que se deben aplicar a las primitivas para dimensionarlas, girarlas y ubicarlas dentro de la escena. Cada transformación en particular se representa matemáticamente mediante una matriz numérica. Y todas las matrices se combinan en una única. El orden en que se combinan las transformaciones es importante. No es lo mismo trasladar primero y rotar luego, que rotar primero y trasladar luego. El orden que consideraremos en esta serie de artículos será primero el escalado, segundo la rotación sobre el eje X, tercero la rotación sobre el eje Y, cuarto la rotación sobre el eje Z, y quinto la traslación. Aplicar a una primitiva una matriz de transformación hace que se instancie un objeto en el mismo espacio de coordenadas en el que se encuentra definido el rayo, lo que permite estudiar las posibles intersecciones entre ambos. Sin embargo, para hallar las intersecciones con los objetos de la escena, no se trabaja con la primitiva transformada, sino que se aplica la inversa de las transformaciones al rayo, y se calcula su intersección con la primitiva canónica. La principal razón por la que se realiza así es que se simplifica significativamente los cálculos, las expresiones que describen las primitivas en su forma canónica son más sencillas de tratar que las correspondientes expresiones de las primitivas transformadas. Si aplicar la matriz de transformaciones a la primitiva canónica transporta la primitiva al espacio donde se encuentra el rayo, aplicar la inversa de dicha matriz al rayo transporta al rayo al espacio de coordenadas de la primitiva canónica. ![]() Denotaremos con M la matriz de transformaciones, y con INV(M) la inversa de M. Sean O' y D' los componentes del rayo resultantes de multiplicar los componentes originales, O y D, por INV(M): O' = (ox', oy', oz') = O * INV(M)
D' = (dx', dy', dz') = D * INV(M)
Aunque la operación realizada a ambos componentes es la misma, el origen O es un punto y la dirección D es un vector, por lo que la traslación no tiene que tener efecto en el vector D, algo que puede resolverse fácilmente utilizando coordenadas homogéneas. La ecuación del rayo en el espacio del objeto es igual que la original, pero reemplazando en ella sus componentes originales por sus equivalentes transformados: R'(t) = O' + D' * t
Algo muy importante que ha de tenerse en cuenta es el hecho de que habitualmente el vector D' es normalizado, o sea, dividido por su longitud: D' = (D * INV(M) ) / |D * INV(M)|
Esta operación se lleva a cabo para realizar ciertas optimizaciones basadas en el hecho de que dicho vector es unitario. Sin embargo, esta normalización rompe la naturaleza de las transformaciones realizadas, el escalado original se pierde. Por ello, para restaurar el orden de las cosas, se debe dividir los valores de t encontrados por dicha longitud, para que todos los elementos involucrados en el cálculo se vean igualmente afectados. Así, si ti' es el valor de t para el que se produce la intersección en el espacio del objeto, considerando el vector dirección D' normalizado, entonces el valor ti en el espacio del rayo será: ti = ti' / |D * INV(M)|
La construcción de la matriz de transformaciones, su inversa, y el uso de coordenadas homogéneas, queda fuera del alcance de este artículo, puede encontrarse en cualquier texto básico de gráficos por ordenador. A modo de referencia he añadido un apéndice a este artículo con las matrices de transformación más comunes y una breve introducción al uso de coordenadas homogéneas. Normal al Punto de Intersección con una Primitiva TransformadaEl escalado es una de las operaciones que se llevan a cabo al aplicar una matriz de transformación a una primitiva canónica, provocando un cambio en la forma de la misma. Por ejemplo, si se aplica un escalado uniforme a una esfera se obtiene otra esfera de distinto tamaño, pero si el escalado se aplica sólo en uno de los ejes se obtiene una elipse. Lo realmente importante a tener en cuenta es que el escalado provoca un cambio de la posición relativa de los puntos de la superficie sobre la que se aplica, lo que conlleva una variación de los ángulos que forman los puntos entre sí, y que estos cambios afectan a los vectores característicos sobre la superficie, como son las tangentes y las normales. Por ejemplo, si se escala verticalmente un polígono triangular como el de la imagen, el ángulo que forma el vértice superior con el inferior derecho varía, haciendo que la dirección de la normal en dicho lado varíe también. ![]() Las tangentes de las primitivas transformadas pueden construirse aplicando la matriz de transformación a las tangentes de las primitivas canónicas, porque se calculan mediante las diferencias de las posiciones de los puntos, y estas se mantienen. Las normales, por el contrario, no pueden construirse así, porque se calculan en base a los ángulos que forman entre sí las posiciones, y estos no se mantienen. Resumiendo, dada una tangente de una primitiva canónica, podemos construir la tangente transformada aplicando la matriz de transformación. Sin embargo, dada una normal, no podemos construir la transformada aplicando la matriz de transformación. Debe aplicarse otra matriz. Y el objetivo del resto de este apartado es encontrar dicha matriz. Lo que sigue es un desarrollo matemático un poco largo que puede saltarse si lo único que le interesa es el resultado. Sea Ti' la tangente, y Ni' la normal en el espacio de coordenadas del objeto, es decir, en el espacio de la primitiva canónica. Y sea Ti la tangente al punto de intersección, y Ni la normal al mismo, en el espacio de coordenadas globales, es decir, en el espacio original del rayo. La tangente Ti es el resultado de aplicar la matriz de transformación a la tangente Ti'. Por su parte, la normal Ni será el resultado de aplicar otra matriz, llamémosla L, la matriz buscada, a la normal Ni': Ti = Ti' * M
Ni = Ni' * L
La tangente y la normal a un punto son perpendiculares entre sí, y esa relación se cumple siempre. Matemáticamente se caracteriza porque el producto escalar entre ambos vectores es cero: Ti · Ni = Ti' · Ni' = (Ti' * M) · (Ni' * L) = 0
Para desarrollar esta expresión, y encontrar el valor de L, hemos de tener en cuenta dos propiedades. Primera, que considerando los vectores como matrices fila, el producto escalar de dos vectores es equivalente al producto de la traspuesta de un vector por otro: A · B = A * TRAS(B)
Y segunda, que la traspuesta del producto de dos matrices es igual al producto de sus traspuestas cambiando el orden de los elementos: TRAS(A * B) = TRAS(B) * TRAS(A)
Aplicando esas dos propiedades en la expresión a desarrollar: (Ti' * M) · (Ni' * L) = 0
(Ti' * M) * TRAS(Ni' * L) = 0
Ti' * M * TRAS(L) * TRAS(Ni') = 0
Considerando sólo el primer y último término, y aplicando la primera propiedad, podemos reescribirlos como: Ti' * TRAS(Ni') = Ti' · Ni' = 0
Es decir, el producto del primer y último término es cero. Luego, la expresión que estamos desarrollando sólo será 0 si el producto de los otros dos factores es igual a la matriz identidad. Aunque no sea evidente a primera vista, debe quedar claro considerando que el producto de matrices no es conmutativo, por lo que al resolver la expresión de izquierda a derecha, justo antes de multiplicar por el último término, el resultado de los tres primeros factores debe ser igual al valor del primer término, para que el resultado total sea cero: Ti' * (M * TRAS(L) )* TRAS(Ni') = Ti' * I * TRAS(Ni') = Ti' * TRAS(Ni') = Ti'·Ni' = 0
Finalmente, podemos despejar el valor de la matriz L buscada: M * TRAS(L) = I
Teniendo en cuenta que, de forma general, A * INV(A) = INV(A) * A = I: TRAS(L) = INV(M)
Calculando la traspuesta en ambos lados de la expresión, sabiendo que TRAS( TRAS(A) ) = A: TRAS( TRAS(L) ) = TRAS( INV(M) )
L = TRAS( INV(M) )
Es decir, la normal en el espacio de coordenadas globales, se obtiene aplicándole la traspuesta de la inversa de la matriz de transformación, a la normal en el espacio de coordenadas de la primitiva canónica: Ni = Ni' * L = Ni' * TRAS( INV(M) )
|