Soporte para shaders en un engine.
Con la llegada de los GPUs programables, no hemos visto envueltos en un frenesí de shaders y herramientas. Todo el mundo baja herramientas como RenderMonkey, FX Composer, etc. para crear shaders, algunos hacen demos pequeñas mostrando las ultimas características de sus placas, y los shaders mas complejos y extraños. Todo esto es perfecto pero mucha gente no se da cuenta que uno de los problemas mas grandes de la flexibilidad disponible ahora no esta en escribir los shaders en si, sino en diseñar y escribir un engine que soporte los shaders y su flexibilidad. Esto es lo que voy a tratar de encarar en este pequeño artículo.
Introducción
Después de las lecciones aprendidas en la creación del ^FishEngine (un engine 3D muy parecido al del Doom 3), a finales de Febrero del 2005 inicie la producción de un nuevo engine 3D. Desde el primer día sabia que una de las diferencias principales entre el Albedo Engine y el antiguo ^FishEngine era el uso de glSlang, dejando de lado los register combiners y extensiones similares. Este cambio viene directamente de la necesidad de dar más flexibilidad a la hora de trabajar tanto a los programadores como a los artistas. Trabajar con extensiones propietarias que además están limitadas en sus características simplemente no va más. Uno no puede estar manteniendo cientos de shaders con versiones para cada extensión, ahora simplemente se escribir el shader en un lenguaje de alto nivel y listo.
Inicios
Hacer un engine no es una tarea que haces en una semana y que podes remplazar todos los días. Los engines son sistemas complejos de hacer, que toman mucho tiempo y en general no todos tienen el lujo de remplazarlo todos los días. Basado en el hecho que el Albedo Engine tiene que ser un engine que va a ser usado en cualquier proyecto 3D que realice, la flexibilidad y facilidad de uso son sumamente importantes. Y viendo la evolución de los gráficos, sabemos que tenemos que darle una importancia especial a los shaders ya que en el futuro van a ser cada vez mas complejos. Después de unos meses de trabajo, a mediados de Abril del 2005 empieza el complejo proceso de agregar soporte para shaders. Esto era una situación bastante complicada porque esto era una característica totalmente nueva que no estaba implementada en el ^FishEngine antiguo (había soporte para glSlang pero había un solo shader que era usado para toda la geometría de la misma manera que el Doom 3). Pero después de mucha investigación, tiempo de implementación y pruebas llegue a tener un soporte muy bueno.
Los shaders en el Albedo Engine
Un shader en el Albedo Engine no se refiere al el código glSlang del shader en si, sino que se refiere también a todos lo necesarios para que ese shader se dibuje de manera correcta. Esto implica cambios de estados como el blending, manejo de parámetros para los vertex shaders y los fragment shaders, manejo de las texturas para los shaders, y más. Por cada shader es posible especificar dos tipos de parámetros, parámetros locales que son parámetros que no cambian por material sino por shader, por ejemplo una constante, y los parámetros externos que pueden cambiar con el material como por ejemplo el valor especular de una superficie. A la vez, estos parámetros pueden ser valores internos del motor como por ejemplo se le puede pasar la posición del usuario, o pueden ser valores escritos por el usuario. En el motor no hay múltiples instancias de un mismo shader, excepto en el caso donde los parámetros internos son distintos, de lo contrario siempre hay una sola instancia de un shader. Por otra parte incluso si hay parámetros diferentes, hay un solo shader object compilado y ejecutado. Por otra parte tenemos un shader manager que se encarga de las cosas más básicas. Se encarga de leer los shaders, crear cada shader que va a manejar el engine, se encarga de liberar todos los recursos usados por los shaders cuando es necesario, pasar parámetros a los shader y demás. Es importante tener un buen shader manager para conectar a el engine con cada shader de una manera simple.
Los shaders y la geometría.
Una parte muy importante es ver la conexión que existe entre la geometría y los shaders. Mirando desde punto de vista de los shaders, podemos ver que un vertex shader aplicado a la geometría puede causar un montón de efectos de los que hay que tener cuidado. Por ejemplo si hacemos el culling de la geometría que no se ve, pero un shader aplicado a esa geometría hace que por ejemplo una esfera se vuelva 2 veces más grande entonces lo que va a pasar es que la esfera original va a salir de nuestro frustum lo que va a causar que de repente la esfera desaparece. Por eso es importante tener ciertos tipos de tags a la geometría como por ejemplo a esa esfera simplemente se dibuja siempre, o se dibuja si estamos en cierto sector. Por otra parte mirando desde el punto de vista de la peformance, es importante evitar lo más posible los cambios de estados o en este caso el cambio de shaders. Por eso es importante que la geometría que se va a dibujar este ordenada por shader o material de esa manera se reduce a uno la cantidad de veces que hay que cambiar el shader activo al momento de renderizar. En el caso del Albedo Engine cada nodo tiene la geometría organizada por material por lo tanto los cambios de material se minimizan lo mas posible. Por otra parte es importante reconocer que hay shaders que se aplican de diferentes maneras con respecto a la geometría para llegar a la solución final. Por ejemplo un efecto de tone mapping no tiene nada que ver con la geometría en si sino que se renderiza la escena final a una textura de punto flotante. Están los shaders que se aplican a toda la geometría por igual como podría ser la textura difusa de una superficie, no importa donde este la geometría y otros aspectos a su alrededor, siempre el componente es igual. Pero hay otros efectos que tienen un volumen de impacto, como podría ser una luz omnidireccional con un radio de decaída definido. En este caso el shader se aplica a toda la geometría que esta dentro de radio de impacto de la luz. Por eso es importante poder diferenciar eso al momento de escribir un engine porque seria una perdida de recursos renderizar toda la geometría cuando en verdad solo un porcentaje de la geometría se ve impactada por un shader volumétrico.
Los shaders y sus parámetros.
Lo más importante para un shader son sus parámetros. Esto se puede ver sobretodo con los sistemas de creación de shaders como el RenderMonkey de ATI, o el FX Componer de Nvidia. Mas allá de que son herramientas útiles, trabajar dentro de tu engine en si tiene muchísimo mas valor porque te permite trabajar con los shaders con unos parámetros y en un ambiente que es complicado simular en los shaders previewers. La diferencia de hacer un shaders para iluminación en un shader previewer y trabajar en tu motor mismo es abismal. Los shaders previewers te permite ver un shader específico en general en un entorno mínimo para poder probarlo, pero al momento de ver el producto final, lo importante son los parámetros que se le pasan desde tu engine y como todos los efectos interactúan para lograr un frame final que sea lo que el artista busca. Entonces es importante primero que nada hacerle fácil el trabajo a los diseñadores dándole herramientas intuitivas con un GUI para la edición de parámetros en tiempo real, o por lo menos una pequeña consola que les permita cambiar el valor de un parámetro. Todos esto se tiene que realizar dentro del engine mientras el artista esta en su escena. Es mas, si podes ofrecer edición del shader en si dentro del engine mejor todavía, pero igual ese son el tipo de características que se pueden cortar por tiempo, pero el GUI de edición de parámetros no ya que los beneficios que ofrece son muchos y el tiempo de implementación no es largo (teniendo en cuenta que ya tenes un GUI en tu engine).
Conclusión.
Espero que esto les ayude a ver como encarar la implementación de shaders en sus engines, que les ayude a ver cuales son los potenciales problemas y a que cosas hay que darle importancia. Si tienen cualquier pregunta lo pueden hacer a través de los comentarios.