Hacía ya algún tiempo que quería hablar sobre shading. El motivo principal para ello es el hecho de que, en contra de lo que pueda parecer, no hay mucha información en español sobre ello. Y es que, a pesar, de que no es mi fuerte como artista 3D considero que saber qué es un shader y cómo se comportan es importante para tener al menos una base sobre este asunto en cuestión. Y de paso aportar mi granito de arena por pequeño que este sea.
¿Qué es un shader?
Un shader es un conjunto de instrucciones que dan lugar a un pequeño programa (o script) que es ejecutado a través de la GPU de nuestro ordenador con el objetivo de alterar la representación de una imagen durante el renderizado de la misma. El lenguaje utilizado normalmente para la creación de shaders es CG (C for Graphics) el cual también podemos encontrarlo como HLSL (High Level Shader Language), creado por nVidia y Microsoft para que funcionen a través de DirectX u OpenGL.
Los motivos de que estas instrucciones se envíen a nuestra gráfica y no directamente a nuestra CPU es debido a la forma en la cual maneja los datos nuestra GPU. Trataré de explicar las diferencias entre ambos a grandes rasgos.
Cuando nuestro ordenador envía una serie de instrucciones directamente a la CPU lo que ocurre es que estas instrucciones se van a ir resolviendo de forma secuencial por medio del procesador.
Pero en el caso de la GPU, cuyas siglas significan graphic processing unit, los procesos son realizados de forma paralela. Es decir, puede realizar varias operaciones al mismo tiempo de manera simultánea en lugar de una a una como ocurre en la CPU.
Con esto se consigue una velocidad de procesamiento mucho más rápida en las tarjetas gráficas ya que están diseñadas a este efecto para dar respuesta en menor tiempo a varios procesos a la vez.
Antes de pasar a explicar los distintos tipos de shaders, debe quedar claro que hay infinidad de estos y que muchos programas de modelado tienen sus propios shaders creados de base para facilitar la creación de materiales, algunos de ellos como los Phong o Blinn son estándares en la industria. Pero todos ellos están creados usando como base los que veremos a continuación.
Tipos de shaders
Podemos encontrarlos de varios tipos, aunque podemos destacar principalmente dos: los Vertex shader y los Pixel shader.
Vertex Shader:
Este tipo de shader localiza los vértices de las geometrías en la escena y nos permite representarlos. Gracias a los Vertex shaders podemos ver la posición de los vértices de dichas geometrías.
Debemos saber también que los Vertex shaders interpretan las mallas de las geometrías subdividiéndolas en unidades mínimas denominadas primitivas. Estas primitivas nada tienen que ver con las primitivas 3D que ya tratamos en otro artículo sobre box modeling. En este caso el término primitivas se usa para hacer alusión a los triángulos que forman los polígonos, sus aristas y sus vértices (puedes encontrar más información sobre esto en este otro artículo)
Si además hacemos uso de Geometry shader se pueden hacer cambios en vértices que ya existen en los objetos asignados previamente pero no se puede “modelar” con ellos asignando vértices nuevos al modelo aunque se podría hacer a través de la teselación de la malla (para hacer esto se usa otro tipo de shader conocido como Tessellation Shader) y después alterar esos vértices generados a través de dicha teselación.
Un ejemplo típico es para la creación de agua, los cuales manipulan los vértices alterando su altura a través de texturas bump o normal (de los cuales extraen la información de altura del píxel) y utilizando el paneo o el offset de estas para falsear el oleaje del agua.
Pixel Shader:
Conocido también como Fragment shader. Se encarga de “sombrear” el espacio vacío que se crea al representar las primitivas de la geometría por medio del Vertex shader, bien para añadir color y textura o bien para aplicar luminancia y sombreado.
También se encarga de alterar el resultado final de los píxeles que se mostrarán en el renderizado. Estas instrucciones son ejecutadas para cada píxel de la pantalla en un entorno 3D, teniendo en cuenta la iluminación de la escena y la configuración del material.
El resultado de esas configuraciones devuelve un valor para cada uno de los píxeles de la escena llamados fragmentos. Esos fragmentos pueden alterar de forma parcial, total o no aplicar ningún cambio en el resultado final de cada uno de los píxeles de la escena.
Habitualmente se hace alterando los valores de tono, saturación, iluminación y transparencia. Además permite hacer uso de mapas de normales o bump lo que permite falsear el sombreado de los píxeles los cuales reaccionan a la luz para dar la falsa sensación de mayor detalle sobre una superficie que a priori carece de él.
Por ejemplo, imagina una imagen renderizada con un aspecto X, el shader localiza esos píxeles de la imagen y hace que se le añada un valor de 50 al canal azul de su RGB dando como resultado una imagen tintada de azul (algo así como un filtro).
Otro ejemplo sería en el caso de querer hacer reflejos elaborados sobre la superficie de un objeto. De modo que el shader deberá calcular cuál será el color que devolverá cada píxel para aplicar reflejos a aquellos que lo necesiten. Para dichos comportamientos, hará falta hacer uso de información adicional como el vector normal o el vector de iluminación. Aquí sería necesario también recurrir a modelos de reflectancia lambertiana o reflectancia Phong para la iluminación del objeto y recreación de especulares.
El uso de estas ecuaciones puede dar lugar a diferentes tipos de resultados interesantes como el famoso cell shading o toon shading, en el que se eliminan los pasos intermedios del degradado dando lugar a un aspecto cartoon al objeto que lo tenga asignado.
Otros tipos de shaders
A continuación haré mención a otros tipos de shaders que también podemos encontrar y que trabajan normalmente de forma interna.
Tessellation Shader:
Conocido también como Hull Shader. Aunque ya he hecho alusión más arriba a él, vamos a ver cual es su funcionamiento. Se trata de un shader que subdivide las mallas de nuestras geometrías en la escena. Este se encuentra integrado de forma interna en la GPU y no se puede alterar mediante programación, tratándose de una función a la que llamamos cuando queremos hacer uso de ella para subdividir nuestras mallas.
Geometry Shader:
También ha sido mencionado más arriba y su función es la de alterar la posición de los vértices, normalmente mediante mapas de desplazamiento.
Un ejemplo de este suele verse en el uso de mapas de desplazamiento en planos con bastante subdivisión para generar terrenos interpretando los píxeles de la textura de desplazamiento, siendo los blancos los encargados de desplazar la malla hacia arriba y los negros desplazando la malla hacia abajo.
Compute Shader:
Cumple una función específica a nivel de procesamiento, permitiéndonos derivar tareas a otros núcleos de la GPU con el fin de acelerar la velocidad de procesado de ciertas instrucciones, las cuales se trabajarán de forma paralela.