MASTER EN INFORMÁTICA GRÁFICA, JUEGOS Y REALIDAD VIRTUAL Técnicas Avanzadas de Gráficos 3D Práctica 2: Trazador de rayos clásico básico Carlos Garre del Olmo Álvaro Pérez Molero Índice 1 2 3 4 5 6 7 8 Introducción.............................................................................................................. 3 Cámara...................................................................................................................... 3 2.1 Generación de rayos ......................................................................................... 3 2.2 Transformación de coordenadas de pantalla a coordenadas del mundo........... 3 Intersecciones ........................................................................................................... 4 3.1 Intersección rayo-esfera.................................................................................... 4 3.2 Intersección rayo-triángulo............................................................................... 5 Materiales ................................................................................................................. 6 4.1 Lambertian........................................................................................................ 6 4.2 Phong ................................................................................................................ 6 4.3 ShinyPhong....................................................................................................... 8 Luces......................................................................................................................... 8 Renderizado de la escena.......................................................................................... 8 Resultados................................................................................................................. 9 Bibliografía............................................................................................................. 11 1 Introducción La práctica consiste en realizar un trazador de rayos clásico, de manera que se pongan en práctica los conocimientos teóricos adquiridos en la asignatura. Para ello, se ha proporcionado un esqueleto sobre el que hay que realizar todas las tareas pedidas para lograr el objetivo de implementar dicho trazador de rayos. Sobre el código proporcionado, hay que generar rayos desde el origen de la cámara, ver cómo intersecan con los distintos objetos de la escena, calcular cómo se refleja la luz en dichos objetos, dependiendo tanto de las fuentes de luz presentes en la escena como de los materiales de los que están compuestos los objetos de la escena. Todo ello con el objetivo final de calcular el color final de cada píxel, que será el que el usuario vea en la imagen generada como imagen de salida del trazador de rayos. 2 Cámara 2.1 Generación de rayos El primer paso es generar rayos desde el origen de la cámara hacia cada uno de los píxeles de la imagen. En el caso de muestreo sencillo, se lanzará un solo rayo por cada píxel de la imagen resultante, mientras que en el caso de súper muestreo, se lanzarán n2 rayos por cada píxel de la imagen, siendo n un parámetro configurable a la función de render con súper muestreo. En nuestro caso, siempre hemos hecho el súper muestreo con n = 3, por lo que hemos lanzado 9 rayos por cada píxel. El proceso de generar un rayo desde el origen de la cámara hacia un determinado píxel de la imagen, que viene dado por sus coordenadas de pantalla (u, v), consiste en: - El origen del rayo es el origen de la cámara en coordenadas del mundo - Calcular las coordenadas (x, y, z) del píxel hacia el que se lanza el rayo, coordenadas del mundo, a partir de sus coordenadas de pantalla (u, v) - Calcular la dirección del rayo como la resta del punto (x, y, z) obtenido menos el origen de la cámara en coordenadas del mundo, normalizando el resultado 2.2 Transformación de coordenadas de pantalla a coordenadas del mundo Para transformar las coordenadas de pantalla (u, v) en coordenadas del mundo (x, y, z) se han realizado los siguientes pasos: - Se calcula la distancia de la cámara al plano de la imagen como: d = - 1 cos( yfov) 1 1 0 .5 = cot an( yfov) = = 2 sin( yfov) 2 2 tan( yfov) tan( yfov) Con esta distancia se obtiene en coordenadas del mundo el punto que está en el centro de la imagen (este punto tiene como coordenadas de pantalla (0.5, 0.5), ya que las coordenadas de la imagen están normalizadas entre 0 y 1), a partir del origen de la cámara y el vector z de la misma, de la siguiente manera: r p _ screen _ center=origin − dz - A partir del punto central de la imagen en coordenadas del mundo, se obtiene la esquina superior izquierda en coordenadas del mundo (este punto tiene como coordenadas de pantalla (0.0, 0.0)), restando el vector x de la cámara (escalado 0.5 unidades) y restando el vector y de la cámara (escalado 0.5 unidades): r r p _ screen _ origin= p _ screen _ center − 0.5 x − 0.5 y - El punto final se obtiene a partir de la esquina superior izquierda en coordenadas del mundo, sumando los vectores x e y de la cámara, escalados por u y v, respectivamente, como se muestra a continuación: r r p = p _ screen _ origin + ux + vy 3 Intersecciones 3.1 Intersección rayo-esfera La primera intersección que se ha calculado ha sido entre un rayo y una esfera, para lo que se ha consultado el libro de Matt Pharr, “Physical Based Rendering From Theory to Implementation”. Para calcular la intersección, se ha obtenido la ecuación del rayo en forma paramétrica: p = ray.origin + t ·ray.direction y se ha sustituido en la ecuación de la esfera: ( x − sphere. position.x) 2 + ( y − sphere. position. y ) 2 + ( z − sphere. position.z ) 2 = radius 2 de tal forma que se llega a la ecuación de segundo grado: ( p − sphere. position)T ( p − sphere. position) − radius 2 = 0 y resolviendo esta ecuación, se obtienen 0, 1 ó 2 soluciones de la misma. Interpretando estas soluciones, se sabe si el rayo interseca la esfera en un punto (es tangente), en dos puntos (corta a la esfera) o en ninguno (el rayo no interseca con la esfera). Además, nos quedamos con la solución positiva que sea menor, para obtener el primer punto de intersección del rayo con la esfera (la intersección en la parte frontal de la misma. Por tanto, en caso de haberla, la intersección del rayo con la esfera se calcula evaluando el rayo a la distancia t obtenida como solución de la ecuación de segundo grado propuesta, la normal de la intersección es la normal de la esfera en ese punto, la distancia es la solución obtenida t, y el material es el material de la esfera. 3.2 Intersección rayo-triángulo Posteriormente, se ha calculado la intersección entre un rayo y un triángulo, consultando también el libro de Matt Pharr, “Physical Based Rendering From Theory to Implementation”. En este caso, se han obtenido las coordenadas baricéntricas, de tal manera que un punto p está dentro del triángulo si éstas están entre 0 y 1. Se han seguido los siguientes pasos: 1. Se obtienen las dos aristas del triangulo que inciden en v0: v1-v0 y v2-v0 2. Se calcula el producto vectorial entre la dirección del rayo y la segunda arista 3. A su vez, se calcula el producto escalar entre el producto vectorial que se acaba de obtener en el punto 2, y la primera arista. Si el producto escalar es 0, el triángulo es degenerado, por lo que no se produce intersección 4. En caso contrario, se obtiene el vector que va desde el origen del rayo hasta v0 5. Se calcula la primera coordenada baricéntrica como el producto escalar del vector que se acaba de obtener en el punto 4, con el vector obtenido mediante el producto vectorial descrito en el punto 2, todo ello dividido por el producto escalar calculado en el punto 3 6. Si la coordenada baricéntrica es menor que 0, o es mayor que 1, el punto esta fuera del triángulo y no se produce, por tanto, intersección 7. A continuación, se obtiene el producto vectorial del vector calculado en el punto 4 y la primera arista del triángulo 8. Se calcula la segunda coordenada baricéntrica como el producto escalar entre la dirección del rayo y el vector obtenido mediante el producto vectorial descrito en el punto 7, todo ello dividido por el producto escalar calculado en el punto 3 9. Si la coordenada baricéntrica es menor que 0, o la suma de las dos coordenadas baricéntricas es mayor que 1, el punto esta fuera del triángulo y no se produce, por tanto, intersección 10. Finalmente, se obtiene el punto de intersección t del rayo como el producto escalar entre la segunda arista del triángulo y el vector obtenido mediante el producto vectorial descrito en el punto 7 11. Se comprueba que el punto de intersección esta en los límites del rayo y se calcula la intersección del rayo con el triángulo evaluando el rayo a la distancia t obtenida como punto de intersección, la normal de la intersección es la normal del triángulo, la distancia es la solución obtenida t, y el material es el material del triángulo 4 Materiales 4.1 Lambertian En el caso de los materiales lambertianos, se calcula únicamente la iluminación directa, no habiendo en este caso reflexiones tipo espejo. La iluminación directa de los materiales lambertianos no depende del punto vista, sino solamente del vector de dirección de la luz y de la normal del punto al que llega dicha luz. Fig. 1 – Iluminación difusa (Transparencias de TAG3D, curso 2007/2008) Por tanto, la iluminación directa de este tipo de materiales se calcula como el producto escalar del vector de dirección de la luz, L, y la normal del punto al que llega esta luz, N, multiplicado por el color difuso del material. 4.2 Phong En el caso de los materiales de tipo Phong, se calcula únicamente la iluminación directa, no habiendo en este caso reflexiones tipo espejo. La iluminación directa de los materiales de tipo Phong sí depende del punto vista, no solamente del vector de dirección de la luz y de la normal del punto al que llega dicha luz. Por tanto, la iluminación directa de este tipo de materiales se calcula como la suma de la componente difusa (calculada como se acaba de explicar en apartado anterior) y la componente especular. Fig. 2 – Iluminación especular (Transparencias de TAG3D, curso 2007/2008) Para calcular la componente especular, inicialmente se había calculado el vector bisectriz H, calculado como la suma del vector de visualización V (este vector es igual, pero de sentido contrario, que el vector de dirección de visualización I) y el vector de dirección de la luz L, y normalizando el resultado. Después, se calculaba el término especular como el producto escalar entre el vector normal del punto al que llega la luz, N, y el vector bisectriz H, elevado al exponente especular, y multiplicado todo ello por el color especular del material. Fig. 3 – Iluminación especular (Enunciado de la práctica 2 de GPGPU, curso 2007/2008) Sin embargo, haciendo los cálculos de esta manera, obteníamos resultados diferentes de los proporcionados como material de apoyo a esta práctica, como se ve en la figura: Fig. 4 – Resultados obtenidos inicialmente (izq) y resultados obtenidos finalmente (dcha) Finalmente, se ha calculado la componente especular utilizando el vector de reflexión R, calculado como R = 2(N·L)N – L, donde · denota el producto escalar. Después, se calcula el término especular como el producto escalar entre el vector de reflexión R, y el vector de visualización V, calculado negando el vector I, elevado al exponente especular, y multiplicado todo ello por el color especular del material. 4.3 ShinyPhong Puesto que la clase ShinyPhong es una clase que extiende la clase Phong, la iluminación directa la obtiene de esta última clase. En este caso, sí se produce reflexión, siendo ésta el atributo de color reflection de la clase, definido en el fichero xml con la información del material correspondiente. 5 Luces La dirección de la luz se calcula como el vector resultante de la resta entre la posición de la luz y el punto de la superficie del objeto donde llega esta luz, y normalizando. La intensidad de la luz viene dada por el atributo de color intensity de la clase Light. La distancia de la sombra se calcula como la longitud del vector resultante de la resta entre la posición de la luz y el punto de la superficie del objeto donde llega esta luz. 6 Renderizado de la escena A la hora de renderizar la escena, en el caso de un rayo por cada píxel de la imagen, se inicializa una imagen de la resolución especificada, y se recorren todos los píxeles mediante dos bucles, generando un rayo por cada uno de estos píxeles, y calculando el color final de dichos píxeles, dependiendo de los resultados devueltos al lanzar el rayo. En el caso de súper muestreo, donde se lanzan n2 rayos por cada píxel de la imagen, se inicializa una imagen de la resolución especificada, y se recorren todos los píxeles mediante dos bucles y, a su vez, se recorren las n2 divisiones de cada píxel mediante otros dos bucles, generando un rayo por cada una de estas divisiones, calculando y acumulando el color final de dichas divisiones, dependiendo de los resultados devueltos al lanzar el rayo. Finalmente, se hace la media del color acumulado de todas las divisiones de un píxel, para obtener el color final de dicho píxel. La forma de lanzar los rayos, tanto en el caso de un rayo por cada píxel de la imagen, como en el caso de súper muestreo, se puede observar en la siguiente figura: Fig. 5 – 1 rayo por píxel (izq), 22 rayos por píxel (centro) y 32 rayos por píxel (dcha) Para comprobar si un rayo interseca un objeto de la escena (sea una esfera o un triángulo), se ha implementado un bucle que recorre todas las superficies de la escena y, en caso de que un rayo interseque con algún objeto de la misma, se queda con la intersección de menor distancia. Para calcular el color de un determinado píxel (o una determinada división de un píxel, en el caso de súper muestreo) se utiliza la función computeColor. Primero se mira si el rayo lanzado para ese píxel o para esa división de un píxel interseca con algún objeto de la escena. Si no interseca, el color final es negro, mientras que si interseca hay que comprobar, para cada luz de la escena, si el punto de intersección con un objeto está en sombra con respecto a cada luz. Si está en sombra, no se suma nada al color final, mientras que si no lo está, se va acumulando el color de la iluminación directa (difuso o difuso + especular) obtenido en el punto de intersección entre el rayo lanzado y el objeto con el que ha intersecado, multiplicado por la intensidad de la luz correspondiente. Además, hay que comprobar si el material tiene reflexiones (materiales ShinyPhong) y, en caso afirmativo, generar un rayo (rayo reflejado) con origen el punto de la intersección y dirección la dirección del rayo lanzado originalmente, reflejado a través de su vector normal. En este caso, se suma la contribución de color de la reflexión entre la normal de la intersección y la dirección del rayo original, multiplicado por el color del rayo reflejado. El cálculo del color del rayo reflejado implica una llamada recursiva a la función computeColor. La función computeIllumination no se ha usado, puesto que se han realizado todos los cálculos en la función computeColor. Para saber si un punto está en sombra (no le llega ninguna fuente de luz), con respecto a una determinada fuente de luz, se genera un rayo con origen en dicho punto y dirección el vector de dirección de esa luz entre la posición de la luz y la posición del punto. Si el rayo interseca con algún objeto de la escena, el punto está en sombra. 7 Resultados En este apartado se muestran los resultados obtenidos con nuestro trazador de rayos clásico implementado para esta práctica que, como se puede observar en las siguientes figuras, se corresponden con los resultados proporcionados inicialmente como material de base para la realización de esta práctica. En las figuras que se muestran a continuación aparece a la izquierda el resultado obtenido para cada escena lanzando un solo rayo por cada píxel de la imagen final, y a la derecha el resultado obtenido para cada escena lanzando 9 rayos por cada píxel de la imagen final. Fig. 6 – Test01: Resultados obtenidos con 1 rayo por píxel (izq) y con 9 rayos por píxel (dcha) Fig. 7 – Test02: Resultados obtenidos con 1 rayo por píxel (izq) y con 9 rayos por píxel (dcha) Fig. 8 – Test03: Resultados obtenidos con 1 rayo por píxel (izq) y con 9 rayos por píxel (dcha) Fig. 9 – Test04: Resultados obtenidos con 1 rayo por píxel (izq) y con 9 rayos por píxel (dcha) 8 Bibliografía • Wikipedia: http://en.wikipedia.org • Matt Pharr. Physical Based Rendering: From Theory to Implementation • http://www.dac.escet.urjc.es/rvmaster/asignaturas/TAG3D/08TAG3D%20RayTr acing.pdf • http://www.dac.escet.urjc.es/rvmaster/asignaturas/GPGPU/PG_Practica02.pdf • http://www.realtimerendering.com/intersections.html • http://www.geometrictools.com/LibFoundation/Intersection/Intersection.html