Desde la aparición de DirectX 8 la cantidad de nuevos tipos de shader ha ido aumentando con el tiempo, los siguientes en incluirse a la lista son llamados Traversal Shaders, los cuales tienen que ver con los avances de cara a la adopción con pausa, pero sin freno del trazado de rayos en los juegos. Veamos en que consiste y que mejoras aportará a los juegos de cara al rendimiento.
Los Traversal Shaders son una de las dos filosofías de cara a plantear el problema del recorrido de la estructura de datos espacial en el Ray Tracing, el cual consiste en el uso de programas shader para recorrer la estructura de datos espacial que representa la escena en vez de utilizar hardware especializado o dedicado.
Antes de nada, recordemos lo que es un Shader
En software conocemos coloquialmente como shader a un programa que se ejecuta en los núcleos de la GPU, los cuales tienen diferentes nombres según el fabricante. Así pues AMD llama a estas piezas de hardware Computer Units en sus Radeon, NVIDIA las denomina Stream Multiprocessors o SM en sus GeForce e Intel les da en sus ARC bautiza a dichas unidades como Xe Core.
Así pues el shader en sí mismo es el software que se ejecuta en una de esas unidades, las cuales tienen un funcionamiento muy cercano a una CPU, la diferencia es que un shader es un programa que se ejecuta sobre una primitiva gráfica en las diferentes etapas del pipeline de renderizado: vértices, primitivas, triángulos, fragmentos o píxeles. Aunque estas categorías no son más que abstracciones que hacemos nosotros, para la unidad shader todo son datos y esto significa que pueden ejecutar todo tipo de programas.
Entonces, ¿por qué no utilizar una CPU? Pues por el hecho que hay problemas que las GPU al estar pensadas para operar en paralelo trabajan mucho mejor a la hora de solucionar ciertos contextos que lo que hace una CPU, y de la misma manera ocurre en el caso de las GPU.
Microsoft, el DirectX Ray Tracing y sus shaders
Cuando los de Redmond hablaron por primera vez de implementar el trazado de rayos fue en la Games Developer Conference de 2018, quedaban unos meses para el lanzamiento de las RTX 20 de NVIDIA y por aquel entonces se desconocía por completo, al menos públicamente, de la existencia de unidades para acelerar el Ray Tracing como han sido los RT Cores de NVIDIA y las Ray Acceleration Units de AMD.
¿Cuál fue la propuesta de Microsoft para expandir su API multimedia? Pues añadir una serie de etapas adicionales, las cuales quedan definidas en el siguiente diagrama:
Entender el diagrama es fácil:
- Los bloques en azul son programas shader que se ejecutan a nivel de GPU.
- Los bloques en verde se ejecutan en la CPU, driver, en tándem con la GPU.
- Los rombos en gris son condiciones que se pueden dar cuando un rayo atraviesa la escena.
Ahora bien, en este diagrama hay un elemento que no se incluye y es por ahora uno de los problemas más grandes que existen de cara al Ray Tracing: el recorrido de la estructura de aceleración. ¿Y qué es eso? Hemos hablado de ello en los tutoriales sobre el trazado de rayos, pero nunca esta de más recordarlo.
Estructuras de datos para acelerar el Ray Tracing
Con tal de acelerar, y por tanto que se ejecuten a más velocidad los algoritmos del trazado de rayos lo que se hace es mapear la posición de los objetos en la escena en una estructura de datos, la cual tiene forma de árbol binario que la GPU tendría que recorrer.
Para que entendáis el proceso de recorrer la estructura de datos lo que se hace es empezar desde la raíz que representa toda la escena y se va concretando por niveles hasta llegar al último nivel. En cada nivel lo que se hace es hacer una petición al RT Core o unidad equivalente para que calcule si hay intersección, si la hay entonces baja al siguiente nivel, si no la hay entonces ese camino se para por completo. Esto se hace hasta llegar al final del árbol que es donde ya no se hace la intersección rayo-caja y se realiza la intersección rayo-polígono.
Si habéis sido perspicaces habréis visto que en el diagrama de la sección anterior entre los tipos de shader tenemos los de intersección, pero no los encargados de recorrer el árbol BVH, o sea de atravesarlos, aunque se sobreentiende que esa tarea es realizada por las unidades shaders aunque no hay un tipo de shader específico para ello.
Los Traversal Shaders, ¿qué son y cuál es su origen?
En la documentación de DirectX Ray Tracing podemos encontrar entre los futuribles los llamados Traversal Shaders, los cuales se añadirán a futuro dentro del pipeline para Ray Tracing en una posterior versión de la API de Microsoft, pero lo mejor será ponernos en situación.
El ejercicio de atravesar la estructura de datos hasta el momento pese a que es un programa shader, este es genérico y es controlado por el driver de la gráfica, por lo que los programadores no han de hacer nada pues bien se entiende que el traversal shader le da el control al código de la aplicación a la hora de realizar el proceso de atravesar la estructura de datos nodo por nodo.
¿Y qué ventajas aporta esto al rendimiento? La principal es que podemos definir escenarios en los que un rayo o varios se descarten antes de realizar la intersección, lo cual en estos momentos no es posible hacer. Un ejemplo muy claro sería de cara a objetos muy lejanos a la cámara en el que el detalle de la iluminación no puede ser tan apreciado como en corta distancia. Hay que tener en cuenta que en la actual versión de la API de los de Redmond podemos definir de cara a la iluminación indirecta si un objeto emite rayos o no a través del Ray Generation Shader, pero no podemos crear escenarios donde podamos descartar los rayos al vuelo, en especial con la distancia.
Traversal Shaders para construir la estructura de datos espacial
La división de I+D en gráficos de Intel presento en el 2020 un documento titulado Lazy Build of Acceleration Structures with Traversal Shaders y los que dominéis un poco el idioma de Shakespeare habréis deducido que consiste en la construcción de las mismas estructuras de datos espaciales haciendo uso de los Traversal Shaders. Por lo que estos no solo se pueden utilizar para controlar el recorrido, sino también para construirlo.
Lo primero que llama la atención es lo de Lazy Build, lo cual podríamos traducir siendo educados como construcción con poco esfuerzo. ¿Y en qué consiste? Pues lo que busca esta técnica es que el tiempo de construcción de la estructura de datos se reduzca. Para ello se basa en informaciones previas de fotogramas anteriores sumadas a un algoritmo de visibilidad y si esto os parece confuso, dejad que os definamos lo que significa visibilidad cuando hablamos de renderizado en 3D.
Tenemos que partir del principio de que cuando una GPU está renderizando lo que hace es calcular la visibilidad entre un punto en el espacio y la primera superficie visible en una dirección dada o para simplificar: la visibilidad entre dos elementos. Antes de continuar hemos de tener en cuenta una cosa, seguro que al leer esto te has imaginado a ti mismo mirando dos objetos. Pues no es así la cosa, estamos hablando de como vería un objeto a otro si este pudiese ver, pero desde la definición más simple hace referencia a la cámara que es la visión desde la que renderizamos.
En el renderizado híbrido que combina rasterización con trazado de rayos y que es el que ahora hacen uso todos los juegos no se calcula la visibilidad de la cámara desde dicho algoritmo, sino con el de rasterizado. La idea en el futuro es que la visibilidad respecto a la cámara se realice desde el Ray Tracing, para que con dicha información la GPU acabe construyendo una estructura de datos de toda la escena a través de los Traversal Shaders.