Teniendo en cuenta la complejidad lumínica de una escena…

photorealistic-direct-light-gi-well-lit-room

Y teniendo en cuenta la formula simplificada para el Raytracing…

slide_39.jpg

Ahora diréis… ¿Que tiene que ver la formula simplificada del Raytracing con la rasterización? Bueno, porque la forma de iluminar de los juegos renderizados via rasterización toman como referencia una versión recursiva de la versión más simplificada del Raytracing, lo que conocemos como Raycasting que es otro método de renderizado que no es muy popular pero se ha utilizado en algunos videojuegos en la historia de los mismos.

 

Doom

minecraft-windows-10-edition-03-700x373

El funcionamiento del Raycasting es muy simple, es Raytracing pero sin iluminación indirecta y sin ser recursivo.

RAY+CASTING+In+order+to+implement+this+algorithm+we+had+to+address+these+six+issues.

Se le llama iluminación de un rebote porque teniendo una fuente de luz (o varias) estas «rebotan» sobre lo que es la superficie de los objetos y devuelven a la cámara un valor de color que es el del color iluminado correspondiente al pixel. Esto no se debe confundir con el pipeline clásico tipo OpenGL que es la rasterización pero fue la base para la propia id software para añadir iluminación más compleja a los juegos en 3D a tiempo real empezando por Quake, eso si, iluminación directa.

El primer paso no obstante no fue en el PC sino en la PlayStation de Sony donde aprovecharon las capacidades de la consola en la versión de Doom de la misma para aplicar fuentes de luz multiplicativas.

492002-doom-playstation-screenshot-the-psx-release-adds-colored-lights

El concepto es bien simple, se multiplica el color de la luz por el color del pixel/texel para conseguir el resultado final. En la PlayStation de Sony el sistema de iluminación del Doom era mucho mejor que en el primer Quake de PC por el hecho que pese a basarse en los mismos principios en general este soportaba iluminación de colores.  Eso si, tenemos que ver antes de nada para evitar confusiones como lo hace el primer Quake para la iluminación.

En 1996 que fue la fecha de lanzamiento de Quake lo que era el pipeline OpenGL era algo completamente ajeno para muchos desarrolladores y no habían tarjetas 3D basadas en ese concepto. En realidad ninguna tarjeta 3D doméstica realizaba lo que es la etapa de la goemetría y mucho menos la etapa del rasterizado, dejando a las tarjetas gráficas con el trabajo de un simple mapeado de texturas. En realidad fue con la Voodoo Graphics que vimos ya el rasterizado a nivel de gráfica…

1280px-KL_Diamond_Monster3D_Voodoo_1

Pero Quake se hizo y se termino mucho antes y por tanto todo el proceso via CPU incluido el rasterizado. ¿Como funcionaba? Para empezar el proceso de rasterizado no generaba un Z-Buffer porque esta función era muy poco soportada por limitaciones de ancho de banda sino que al igual que con los juegos de la primera PlayStation lo que se hacía era generar un arbol binario de posición para ordenar los objetos en la escena. ¿Que se hacía? Simplemente se rasterizaba la escena y según el valor de cada objeto en el arbol binario se colocaba un valor de más o menos contraste de la textura.

quake

El primer Quake no soportaba luces de colores sino que manipulaba el brillo y contraste de color de cada textura para simular una iluminación muy simple. Fue con Quake 2 que se añadieron la combinación con luces de colores, pero con dicho juego se crearon no uno sino dos motores gráficos distintos, uno por software evolucionado del primer Quake y el segundo via OpenGL.

ingame_16x10

OpenGL utiliza Z-Buffering, este es un buffer de imagen que almacena como muy bien sabréis no la información de color de cada pixel sino la distancia de cada objeto respecto a la cámara.

z_buffer

¿Y que relación tiene con el Raycasting? El Z-Buffer y el mapa de luces están muy relacionados y a partir del Z-Buffering se puede conseguir un mapa de luces basado en la distancia respecto a la cámara a través de ir combinando los valores de color del mapa de luces con las texturas pero eso en PC no se hizo estandar hasta años después. Tal fue así que este sistema de iluminación no fue adoptado por Quake 3 por el hecho que a nivel de API dado que Direct3D no soportaba Register/Color Combiners y estos no formaban parte del estandar OpenGL esencial para renderizar pues… Es algo que se dejo de lado de manera explicita en el id tech 3.

Los Register/Color Combiner paradojicamente se vieron por primera vez en la N64 de Nintendo, pero a nivel de PC no se vio hasta las Riva TNT. El concepto se basa en el del Shade Tree y basicamente es la combinación a través de suma y multiplicación de varios mapas de texturas para conseguir una textura de aspecto más complejo.

ShaderTree

En OpenGL 1.X se pueden aplicar hasta unas 8 fuentes de luz… ¿Como se calculan? Es sencillo, se renderiza la escena sin texturizar y tomando como cámara la perspectiva de la fuente de lyz generando un mapa de luces a través del buffer Z obtenido. ¿El problema de ello? Aplicar esto de manera recursiva divide la potencia de la gráfica a medida que la iluminación es más compleja, por lo que si no tenemos ninguna fuente de luz y renderizamos la potencia necesaria en lo que a tasa de relleno se refiere sería de:

LE

Pero si añadimos fuentes de luz directas al estilo OpenGL pasamos a iluminación de dos rebotes.

LEKE

Representativamente la fuente de luz va desde el foco, ilumina la parte de la escena que le toca (primer rebote y por tanto KE) y de ahí envía la energía a la cámara (E) que sería el segundo rebote. ¿Y que es K? Es la cantidad de fuentes de luz aplicada sobre la escena. Todas ellas generadas de manera dinámica y todas ellas consumiendo recursos de mala manera. Es por eso que muchos desarrolladores durante la construcción artística de los mundos empezarón a pre-cocinar mapas de luces como si fuesen texturas para evitar el tener que re-renderizar y que fuesen una simple aplicació via Color/Register Combiner… Los cuales posteriormente evolucionaron a los Pixel/Fragment Shaders pero no lo habían hecho por aquel entonces.

El siguiente avance importante fue la llamada Per-Pixel Lighting, basicamente la iluminacion de los mapas de luces se aplicaba en los propios objetos haciendo una multiplicación del color del vertice por el de la fuente de luz. En el caso del Per-Pixel Ligthint el número de cálculos se incrementa haciendo que se multiplique el color de cada pixel por el del mapa de luces. El primer motor en popularizar esto fue el id Tech 4.

1450647362_doom32011100818161771

El salto en iluminación fue impresionante, pero lo que fue más fue la aplicación del Stencil Buffer como extensión del Z-Buffer primero y luego el añadido de los Vertex Shaders.

El estarcido, también llamado esténcil (del inglés stencil) es una técnica artística de decoración en que una plantilla con un dibujo recortado es usada para aplicar pintura, lanzándola a través de dicho recorte, obteniéndose un dibujo con esa forma.

La utilidad más basica del ”stencil buffer” es marcar marcar que pixeles de la escena son afectados por las fuentes de luz, en que grado y en cuales no. Esto ayuda a crear una iluminación más compleja en los juegos. Dado que generamos un mapa de luces que realmente es el Z-Buffer desde la perpectiva de la fuente y este se combina con su Stencil correspondiente conseguimos un mapa de luces mucho más complejo y preciso.

doom3-2

En cuanto a los Vertex Shaders sirven para obtener la normal haciendo uso de la GPU para ello en vez de la CPU.

En geometría, un vector normal a una cantidad geométrica (línea, curva, superficie, etc) es un vector de un espacio de producto escalar que contiene tanto a la entidad geométrica como al vector normal, que tiene la propiedad de ser ortogonal a todos los vectores tangentes a la entidad geométrica.

¿Por qué es importante esto? Pues porque es necesario para la simulación de la iluminación indirecta en OpenGL y derivados. La iluminación indirecta es aquella generada como tercer rebote y ocurre cuando tras el segundo rebote en vez de ir a cámara la fuente de luz lo que hace es buscar otro objeto contra el que rebotar. En la vida real la luz rebota hasta quedarse sin energía, en el caso de los gráficos en 3D rebota hasta que la potencia de cálculo disponible se lo permite.

Tecnicamente la cosa evolucionaría a esto:

LEKEK2E

OpenGL esta limitado a 8 fuentes de luz y no puede representar más pero si pudiese representar más entonces el coste en tasa de relleno y ancho de banda sería realmente enorme. ¿Entonces? Se utilizan los Fragment/Pixel Shaders que tienen la capacidad de ir manipulando un texel varias veces sin tener que afectar la tasa de relleno y el ancho de banda externo. Pero sobretodo para no caer en el K^2E que enviaría el rendimiento a la porra por completo.

En OpenGL y derivados (Direct3D) se utilizo de inició un tipo de luz difusa muy simplificado donde solo se genera un haz de luz indirecto…

diffuse_angle

Esto es más que nada por limitaciones de las APIs del rasterizado, en la vida real la luz difusa…

diffuse_light

El segundo tipo de luz indirecta es la especular, este se genera generando un vector en angulo inverso y dirección tomando la Normal como referencia.

Graphics3D_LightingSpecular

La iluminación especular viene del nombre speculum (espejo en latin) y es utilizada para representar superficies con una alta capacidad de reflectancia de la luz como pueden ser la superficies metálicas. En las APIs basadas en OpenGL la luz indirecta es simulada via Shader y los reflejos no son nunca exactos o no existen simplemente.

ray-tracing-rasterization,L-Z-214631-13

El motivo es bien simple, con tal de simular la luz indirecta a través de los Shaders lo que hacemos es una aproximación artística del viaje de la luz pero no matemática provocando que el renderizado no sea preciso del todo. Pero… ¿nos hemos quedado en la primera Xbox no? Lo mejor es dar el salto a la generación HD.

Iluminación en la Generación HD

Pese a que los Pixel/Register Combiners ya existían no fue hasta su evolución en los Pixel/Fragment Shaders aparecida en el hardware DX9 en adelante que vimos una evolución en la iluminación siendo Half-Life 2 el primer juego en aplicar un modelo de iluminación más compleja que se conoce como Radiosity Normal Mapping.

La idea es que la iluminación especular puede ser construida a partir de tres mapas de luces que en vez de contener lo que es la información del brillo/color contienen los vectores correspondientes. Esto nos permite un modelo de iluminación más eficiente visualmente que elimina los artefacto estilo vaselina en la representación de la luz especular del modelo en la primera Xbox.

Tremulous_demonstrating_ioquake3's_automatic_texture_mapping_feature.jpg

Varios juegos de la generación HD utilizan este tipo de iluminación…

halo-3-lighting-effects_dxkk.gif

99109fa52e28ad26d2c5b34daa93d714.jpg

60.jpg

¿El problema? No podemos calcularlo de manera directa porque por limitaciones solo podemos obtener un haz de luz especular a través de la normal. Por lo que lo único que podemos obtener y de manera imprecisa es una sola dirección y no las tres que necesitamos: la normal desde el punto de vista del texel. ¿Entonces como se hace? Los mapas de luces son generados durante el periodo de producción artística por lo que la iluminación en estos juegos es pre-cocinada por completo en este caso.

El otro avance fue la iluminacion por diferido… ¿Su objetivo? Bueno, digamos que la siguiente imagen…

scene1

Daria problemas enormes por el hecho que aquí tenemos más de 8 fuentes de luz en escena… ¿Que se hizo para reventar dicha limitación? Pues el ya mil veces comentado Renderizado por Diferido o Deferred Rendering. Se le llama así porque consiste en renderizar la escena no en uno sino en dos pasos, en el primero calculamos solo los atributos necesarios para la iluminación por pixel y los escribimos como búfers de imagen del fotograma final en un búfer llamado G-Buffer. Para ello son necesarios múltiples búfers de imagen que almacenan la información geométrica en forma de búfer de imagen desde la perspectiva de la cámara.

deferred_g_buffer

Esto es posible gracias a una mejora del hardware introducida en el hardware tipo DX9 en adelante, el llamado Vertex Texture Fetch que permite que el Vertex Shader pueda recibir y enviar datos de un mapa de texturas solo que no texturiza ya que esa no es su función. En el caso que nos ocupa sirve para generar los diferentes MRTs, los cuales son cada uno procesados como un búfer completo pero con la diferencia que aquí no aplicamos el texturizado.

deferred_overview

Es en el segundo paso dode obtenemos a partir de los 4 MRT generados previamente la iluminación y la aplicamos a la escena con tal de tener un modelo de iluminación relativo a la geometría. ¿Cual es la utilidad de este tipo de iluminación? No vernos limitados solo a 8 fuentes de luz directas en la escena. No un método que sirva para generar iluminación indirecta precisa pero elimina el cuello de botella sobre la iluminación y en combinacion con las tecnicas via Shaders para simular iluminación especular y difusa se consiguía dar el pego en la anterior generación y también en la actual donde han seguido utilizando versiones más avanzadas de estas técnicas.

¿Cual es el problema general? La iluminacion indirecta no esta representada de manera precisa porque no representamos de manera correcta el viaje de la luz con tal de evitar el impacto sobre la tasa de relleno de la misma. Hemos llegado a los limites de la rasterización desde hace tiempo y necesitamos romper sus limitaciones y esa es actualmente la obsesión de los desarrolladores y no el hecho de tener más pixeles o la necesidad de la VR, saben que el aspecto visual de los juegos se estancará si no rompen lo que son las limitaciones.

Estructuras de Datos y GPUs: Matrimonio mal avenido.

En la actual generación nos hemos encontrado con un estancamiento en lo que a la calidad de la iluminación se refiere, el motivo de ello es la falta de potencia necesaria para un tipo de iluminación más compleja. Cuando se presento el Unreal Engine 4 por primera vez, apareció la siguiente información:

sweeneytflops

La iluminación global dinámica no la hemos visto en la actual generación porque las dos consolas base no llegan a la potencia de 2.5 TFLOPS en adelante necesaria para ello. Hace referencia a las fuentes de luz indirectas generadas matematicamente de manera dinámica y no pre-calculadas de la forma que hemos visto antes. Pero para poder almacenar la trayectoria de la luz necesitamos una estructura de datos que nos permite almacenar su trazado en la escena. ¿Como lo hacemos? Dado que es una escena 3D necesitamos una representación espacial en tres dimensiones… ¿La más simple?

lRWXD

La idea es que en vez de rasterizar la escena al 2D lo que hacemos es rasterizarla el 3D generando pixeles volumetricos, a este proceso se le llama voxelización.

La Voxelización es una variacion del rasterizado clásico solo que al final del pipeline en vez de enviar un búfer de imagen bidimensional obtenemos uno tridimensional donde el búfer es copiado en una textura en 3D.

Voxelization3

Ahora bien… ¿Como almacenamos esta textura en 3D? Hay tres formas distintas de hacerlo, dos de ellas se basan en estructuras de datos espaciales, la tercera es una simple matriz de tres dimensiones.

#1 KD-Tree

El KD-Tree es una variación del arbol binario de partición.

collision-detection-in-3d-environments-30-638

… lo que hace un KD Tree es representar el espacio tridimensional en forma de un cubo que se va subdiviendo por 2 o no dependiendo de la necesidad en cada momento. Es el método más preciso de todos pero al mismo tiempo es el que requiere más potencia de todos al ser un arbol binario con una cantidad bastante importante de ramas. La complejidad de todo arbol binario a la hora de buscar un dato es de:

O(log(n))

Donde en es el número de elementos en el arbol. ¿Es un problema esto para una GPU? Pues si, es un problema enorme porque se ha de atravesar todo el arbol entero y a medida que vamos bajando de nivel mayor es la complejidad y las GPUs son procesadores que basan todas sus instrucciones en un tiempo por intrucción regular. Es decir, todas las instrucciones simples tardan n ciclos y todas las instrucciones complejas tardan m*n ciclos donde tanto m como n son constantes y no variables.

#2 Octree

Los Octree también son arboles pero de 8 ramas cada uno.

octree

400px-Octree2.svg

Este es menos preciso y se basa en ir subdividiendo la escena de manera autocontenida en subdivisiones progresivas de 8 cubos cada vez. De la misma manera que el KD-Tree se trata de un arbol pero al tener menos nodos es menos dificil de atrevesar y es preferible a nivel de procesamiento al KD-Tree pero es una enorme carga para la GPU aunque menor y es la estructura de datos que se esta utilizando en estos momentos para las tecnicas de iluminación global más avanzadas pero tiene de los mismos problemas de procesamiento que los KD Tree en las GPUs, esto ha hecho que las tecnicas basadas en Octres no se hayan estandarizado en consolas. ¿La mejor solución? Generarlo a través de la CPU el Octree pero esto necesita una potencia de cálculo considerable.

¿La tercera opción?

#3 Matriz 3D 

El concepto es el mismo que el del Tile Rendering…

tilebuffer

Pero en vez de hacerlo sobre un búfer en 2D se hace sobre el búfer en 3D correspondiente, a esto se le llama Volume Tile Rendering.

volume-tiled-resources

La idea es que acabamos generando una matriz en 3D de la escena y a través de referenciar el bloque de la textura en 3D que necesitamos lo colocamos directamente en forma de textura 3D en la memoria interna de la GPU para acceder más rapidamente al los voxeles que queremos renderizar en este momento. Al no haber Octree por el medio el tiempo de renderizado es mucho más rápido. ¿Ejemplos donde se ha utilizado esta versión simplificada? En el juego Tomorrow Children realizado por Q Games para PS4.

Pero en terminos generales la iluminacion poco ha evolucionado respecto a la generación anterior siendo más bien todo producto de un excelente trabajo artístico… ¿Vamos a poder ver escenas 3D de iluminación muy compleja en la siguiente generación o nos hemos estancado? Tecnicamente estamos estancados y lo que viene a continuación tampoco es la panacea pero es una solución mejor.

Simplificando que es Gerundio

Tener un sistema de iluminación complejo al nivel del Raytracing a tiempo real es imposible a corto y a medio plazo por lo que se ha de buscar la simplificacion de las cosas. En el Raytracing cada fuente de luz actua a nivel de cada pixel de manera reiterativa, a la hora de aplicar esto al rasterizado se va a hacer con un modelo similar pero simplificado con tal de que la potencia de calculo necesario sea tan baja. ¿El primer recorte? El uso de «conos de luz» para representar el viaje de las fuentes de luz.

TC 12TC 11

El punto de origen del cono de luz será el nivel más bajo de un Octree o KD-Tree donde cada nivel a la inversa del Octree representará una sección de la trayectoria del cono de luz.

global-illumination-techniques-06_9511.jpg

Con ello podemos representar una trayectoria tridimensional de cada una de las fuentes de luz tanto directas como indirectas en un espacio tridimensional simplificado donde la trayectoria de cada fuente de luz se almacena en forma de Mip Maps.

mipmap

¿Lo mejor? Es que podemos realizar el almacenaje reiterativo de la luz a través de la escena en un búfer de imagen volumétrico así como una representación tridimensional de las mismas.

El otro tema es la complejidad de la estructura de datos espacial o de la matriz 3D, cuando la realizamos no volvemos el bufer de imagen final en 3D sino que utilizamos un modelo más simple en lo que a resolución se refiere. Tenemos dos maneras de renderizar la escena antes de la Voxelización:

  1. Voxeles con información sobre un G-Buffer que contienen la información necesaria para un segundo paso posterior.
  2. Voxeles derivados de un renderizado inmediato (no contienen un G-Buffer y por tanto ocupan menos espacio).

En el primer caso lo que hacemos es hacer el primer paso del renderizado en diferido antes de generar a través de los Compute Shaders la escena iluminada pero lo hacemos de manera más precisa utilizando el espacio tridimensional. Es la técnica más simple pero la menos precisa de todas. ¿El motivo? La iluminacion indirecta se sigue consiguiendo via Shaders pero es ideal para espacios cerrados o con muchas fuentes de luz para conseguir el tercer rebote ansiado.

shad2-globalillum1

 

El segundo método es no generar la iluminación indirecta via shaders sino via cálculo matemático. Se necesita una mayor cantidad de cálculo y es contraproducente en rendimiento cuando hay muchas fuentes de luz por lo que es ideal en escenarios abiertos a plena luz del día donde tenemos solo la representación de la luz del sol. Vais a ver en un futuro como los juegos dependiendo del tipo de escena van a utilizar un tipo de iluminación global u otra con tal de conseguir el máximo resultado creible en los juegos siempre dentro de las limitaciones pertinentes.

¿La otra opción? Lo que llamamos motores híbridos aunque de momento solo Imagination con una familia concreta de los PowerVR lo ha aplicado… ¿Hibrido de que? Raytracing con Rasterización.

08_Ray-tracing-in-games_Hybrid-rendering-in-a-game-engine

La idea no es otra que renderizar en diferido pero aplicando solo la primera etapa y luego utilizar el Raytracing para determinar la iluminación indirecta de manera más precisa.

PowerVR-Ray-Tracing-rendering-pipeline-2f.png

Dicho de otra manera, nos permite conseguir iluminación indirecta de manera correcta y más precisa que con los shaders, pero el Raytracing necesita mucha potencia de calculo y de ahí a necesitar hardware especializado para ello, cosa que solo tienen los PowerVR Wizard de Imagination pero no sabemos si vamos a verlo en alguna GPU futura de otra marca.

Pensad que esto es renderizado en un chip de móvil, es el resultado de tener iluminación indirecta de manera correcta y no simulada.
comparisonV11_slide2.png

¿El problema? No hay planes de integrarlo en ninguna GPU de otras marcas desde el momento en que es una tecnología propiedad de Imagination que no pretenden estandarizar en ningún momento a terceros. ¿Es un error? No tiene porque, dado que el Raytracing no se utiliza en juegos sino en otros mercados tiene su salida en esos otros mercados. A mi personalmente me gusta más y lo veo más elegante que el método de fuerza bruta antes descrito que no deja de ser un parche sobre la rasterización clasica, aunque esto no deja de serlo.

¿Y toda esta entrada para que? Pues por un motivo muy simple, la idea de cada generación es la de hacer los pixeles más bonitos que la anterior y no solo que se vean más y el rasterizado empieza a ser un problema grave en ese aspecto que puede bloquear la consecución de escenas más complejas. ¿Y que podemos esperar de una siguiente generación? Ejem… Creo que de media algo como esto:

Pero claro, no olvidemos que la tecnología no lo es todo sino que el 90% del trabajo es talento humano. Si fuese solo la tecnología cualquiera podría crear un juego y hacer que se viese bien.