, , , ,

Shader Graph para principiantes

by

Si prefieres seguir un vídeo, puedes verlo en mi canal:

¿Qué es Shader Graph?

Shader Graph es una de las herramientas más esperadas de Unity. Gracias a su grafo de nodos, podrás crear shaders de manera completamente visual, sin escribir una sola línea de código.

Y si lo que te gusta es escribir código, ¡no te preocupes! Shader Graph también te da la opción de crear tus propios nodos, para que tengas el control en todo momento.

Requisitos previos

  • Unity 2019.1 o superior.
  • Un proyecto creado.
  • Tener URP instalado.

Tu primer shader

Crea una carpeta de Shaders en un tu proyecto. Dentro, iremos al menú…

Click derecho > Create > Shader > Universal Render Pipeline

…donde encontraremos todos los shaders que podemos crear con Shader Graph:

  • Lit Shader Graph, para objetos 3D a los que afecta la luz de la escena. Es el modelo de iluminación estándar de URP, que simula las físicas de iluminación reales. Es ideal cuando sólo queremos modificar detalles concretos del modelo.
  • Unlit Shader Graph, para objetos 3D a los que no afecta la luz de la escena. Aplicado directamente a un objeto, lo veremos pintado de un color plano. Sin embargo, ésta será la base de todos los modelos de iluminación nuevos que queramos crear, como los famosos toon shader.
  • Sprite Lit Shader Graph, para sprites a los que afecta la luz de la escena.
  • Sprite Unlit Shader Graph, para sprites a los que no afecta la luz. Muy útil para efectos de UI.

Seleccionaremos Unlit Shader Graph porque al ser el más simple, nos permitirá entender mejor lo que está pasando.

Se creará un objeto llamado New Unlit Shader Graph. Ponle un nombre si lo deseas. Para abrilo, haz doble click en él o pulsa Enter.

El espacio de trabajo de Shader Graph

Espacio de trabajo de Shader Graph

Esto será lo que veamos cuando abramos por primera vez nuestro nuevo shader; el espacio de trabajo vacío de Shader Graph.

Vamos a ver qué hace cada cosa:

Barra de herramientas

La barra de herramientas nos permite modificar el espacio de trabajo. Veamos todas las opciones:

  • New Shader Graph, es el nombre del archivo que tenemos abierto. Cuando tengamos varios shaders abiertos a la vez, se acumularán en esta zona en forma de pestañas.
  • Save Asset significa Guardar archivo. Al pulsarlo, los últimos cambios que hayamos realizado quedarán grabados en disco.
  • Save as… significa Guardar como…, y nos permitirá guardar el shader como un archivo distinto.
  • Show in Project significa Mostrar en el proyecto. Si tenemos una pestaña de Project View abierta en el editor de Unity, hacer click en este botón nos localizará el shader en la jerarquía de carpetas.
  • Color Mode, Modo de color, es un desplegable con cuatro opciones que nos permite decidir cómo se colorearán los nodos:
    • <None> (Ninguno), no muestra colores en los nodos.
    • Category (Categoría), colorea una línea en los nodos en base a su categoría.
    • Precision (Precisión), colorea una línea en los nodos en base a su tipo de precisión.
    • User Defined (Definido por usuario), permite elegir el color para cada nodo.
  • Blackboard, significa Pizarra. Nos permite elegir si se muestra o no en el espacio de trabajo.
  • Graph Inspector, significa Inspector del Grafo. Nos permite elegir si se muestra o no en el espacio de trabajo.
  • Main Preview, significa Vista Previa Principal. Nos permite elegir si se muestra o no en el espacio de trabajo.

Blackboard (Pizarra)

La Pizarra nos permite definir las variables que utilizará este shader.

  • Nos muestra el nombre de nuestro grafo.
  • El botón + nos permite crear variables nuevas
  • También nos permite crear Keywords, palabras clave, que actuarán como las variables de compilación en los shaders programados. Es una cualidad avanzada que veremos en otros tutoriales.
  • Además, podemos modificar el nombre de nuestras variables haciendo doble click en ellas.
  • Si la variable tiene un puntito verde, significará que la variable será visible desde el inspector cuando asignemos el shader a un material:

Además, podremos arrastrar de los bordes de la ventana para cambiar el tamaño de la pizarra en cualquier momento, o desactivarla desde la barra de herramientas.

Graph Inspector (Inspector del Grafo)

El Inspector del Grafo nos permite configurar el shader, su variables y sus nodos.

Node Settings

La configuración del nodo. Nos permite modificar nodos (1) y variables (2).

Para Nodos

  • Add Node, el nombre del nodo que tenemos seleccionado. En este caso, utilicé un nodo de suma (add).
  • Precision, la precisión de la operación que realiza el nodo. Ésta puede ser heredada del nodo (Inherit), o forzada a ser de tipo flotante (Single) o su mitad (Half).
    • El tipo flotante es el float de toda la vida, pero Unity utiliza los nombres aún más genéricos y correctos.
  • Preview, la vista previa del nodo, nos permite decidir si queremos que se muestre como determina el nodo (Inherit), en 2D o en 3D.

Para Variables

  • Name, nos permite cambiar el nombre de la variable. Este nombre es el que se mostrará en el inspector y el grafo de nodos.
  • Reference, nos permite cambiar el identificador de referencia de esta variable. Las referencias se utilizan para cambiar los valores de un shader desde código.
  • Default, el valor por defecto de esta variable.
  • Mode, cómo se comportará esta variable. Dependiendo del tipo de variable nos ofrecerá distintas opciones. Puedes consultar en este link todas las propiedades por tipo de variable. Para variables nuevas su valor será siempre Por defecto (Default).
  • Precision, en qué precisión guardaremos esta variable. Como con los nodos, podrá ser la heredada, Single o Half.
  • Exposed, un booleano que nos permitirá decidir si queremos exponer esta variable en el inspector. Desmarcarlo hará que no aparezca en el menú de configuración de los materiales que utilicen este shader.
  • Override Property Declaration nos permitirá decidir si el shader tiene el control explícito de declarar esta variable.

Graph Settings

Nos permite ajustar la configuración del grafo.

  • Precision, en qué precisión saldrá el output de este grafo. Como con los nodos, podrá ser la recibida o heredada, Single o Half.
  • Target Settings y Active Targets nos permitirán definir la compatibilidad final de este shader. Por defecto, empezará con el objetivo Universal. Puedes aprender más sobre los Targets en este link.
  • La sección Universal nos permite configurar cómo queremos que este shader se relacione con su Target.
    • Material. Aquí podremos cambiar el tipo de material entre Unlit, Lit, Sprite Unlit y Sprite Lit, por si nos hubiéramos equivocado al crearlo o quisiéramos cambiar un shader ya existente.
    • Surface le indicará a la gráfica qué tipo de superficie tiene este objeto; Opaca (Opaque) o Transparente (Transparent). Esto afectará a cómo se procesa el objeto en el Pipeline, y si puede ser transparente.
    • Alpha Clip nos permite activar la posibilidad de desechar fragmentos (píxeles) cuya transparencia esté por debajo de un umbral.
    • Two Sided indicará a la gráfica si también debe renderizar las caras internas de los objetos.
    • Custom Editor GUI nos permitirá indicar a Unity si tenemos un editor personalizado para este shader.

Además, podremos arrastrar de los bordes de la ventana para cambiar el tamaño de la configuración en cualquier momento, o desactivarla desde la barra de herramientas.

Main Preview (Vista Previa Principal)

La Vista Previa nos permite ver en todo momento cuál es el resultado final de nuestro shader. Este resultado dependerá de los valores por defecto de las variables en la Pizarra, ¡así que no tengas miedo de jugar con ellas!

Si hacemos Click Izquierdo y arrastramos, podremos girar la vista previa, y con la rueda del ratón podremos acercarla y alejarla.

Si hacemos Click Derecho sobre la Vista Previa, nos aparecerán el siguiente menú:

Ahí podremos elegir qué forma queremos que tenga la vista previa para mostrarnos el resultado.

Como una foto vale más que mil palabras, aquí te dejo un mosaico de todas las opciones:

* Aunque el cubo y el Quad parezcan iguales, veremos que son distintos al girarlo. Un Quad es un tipo de plano.

** Custom Mesh nos permitirá elegir la malla que queramos. Yo he elegido a Suzanne, el modelo de pruebas de Blender.

Los Nodos en Shader Graph

Los nodos son cajitas que iremos añadiendo a nuestro grafo para crear el shader.

Cómo añadir nodos

Hay cuatro formas de añadir un nodo nuevo a nuestro espacio de trabajo:

  • Arrastrar una variable de la pizarra de variables y soltándola donde queramos añadirla. Esto creará un nodo de variable que representará al valor definido en la pizarra.
  • Hacer Click derecho > Create Node. Abrirá un submenú donde podremos buscar el nodo que queramos.
    • También podrás elegir nodos de variable «en crudo».
    • Todos los nodos de lógica nuevos que crees tendrán sus inputs como variables crudas.
  • Pulsar Espacio en cualquier parte del espacio de trabajo. Abrirá un submenú donde podremos buscar el nodo que queramos.
  • Intentar conectar un nodo con el vacío también abrirá el submenú de creación de nodos, y nos permitirá elegir con cuál de sus inputs queremos conectar nuestro nodo.

Anatomía de un nodo

Cada nodo tiene una función dentro del grafo, y todos los nodos tienen entradas de datos (input) y salidas de datos (output).

En base a cuántos inputs y outputs tienen, podemos clasificar los nodos en cuatro tipos:

  • Lógicos, con uno o más inputs y outputs.
  • Variables, sólo tienen un output; su propio valor.
  • El Output del grafo, que sólo tiene inputs.
  • Sub-grafos, nodos especiales que contienen otro grafo de nodos en su interior, y que pueden tener múltiples inputs (o ninguno), pero que siempre nos darán una salida.

Este es un nodo lógico de suma:

Como ves, tiene dos inputs y un output, y cada uno marca un número entre paréntesis. El número nos indica cuántos parámetros tiene la variable que entra o sale del nodo. En el caso de los colores, estos tienen cuatro variables; RGBA, definidos dentro de un Vector4.

Muchos nodos lógicos de Unity no definen el tipo de sus inputs y outputs. Este mismo nodo te permitirá sumar dos enteros, dos floats, dos colores, dos imágenes… y adaptará su output en base a ellos. También te permitirá sumar dos tipos distintos, siempre que la operación sea posible. Por ejemplo, un float y un entero, un Vector3 y un Vector4, etcétera.

Además, todos los nodos tienen una zona de vista previa, que cambiará dependiendo de los parámetros que esté manejando. La imagen que ves arriba es un nodo de suma que está sumando dos colores, y el resultado es naranja.

Si pasas el ratón por encima de la vista previa, verás una flecha que te permitirá minimizarla.

También encontrarás una opción para abrir y cerrar las vistas previas en el menú contextual del espacio de trabajo; haciendo Click derecho sobre el espacio vacío.

Cómo unir nodos

Imaginemos que queremos sumar dos colores para que sean el color de la superficie de nuestro objeto. Sabemos que necesitamos un nodo lógico de suma, así que lo creamos en nuestro espacio de trabajo.

Para que el output de Add llegue a Base Color, todo lo que hay que hacer es arrastrar del círculo azul junto a Out(1) hasta el círculo amarillo de Base Color(3).

Base Color sólo acepta inputs de tres parámetros, pero el output que le estamos pasando es de uno. No importa, Unity sabe cómo convertirlo y nos deja unirlo.

Ahora necesitamos pasarle dos colores a Add. Arrastramos nuestra variable MyColor de la pizarra al espacio de trabajo, y la unimos con el primer operando de Add:

Vemos que el nodo se adapta a la situación actualizando su segundo input para recibir otro color. También podemos modificar la variable en crudo que nos presenta.

Nodos personalizados

Habrá veces en que necesitemos crear un nodo con lógica especial o específica para nuestro proyecto. Unity nos lo pone súper fácil. Todo lo que tendremos que hacer es crear un nodo Custom Function, pasarle el archivo HLSL que contiene la función y configurarlo.

Pero vayamos paso a paso.

El código de un nodo personalizado

Como ejemplo, vamos a crear un nodo que siempre divida por la mitad un color.

Creamos un archivo con la extensión .hlsl en el proyecto y lo abrimos. El nombre del archivo no importa y que podrás tener varias funciones en cada uno. Yo lo voy a llamar MyFunctions.hlsl para este tutorial.

Ahora vamos a escribir nuestra función. Ésta estará escrita en el lenguaje HLSL, un lenguaje bastante parecido a C#.

Unity espera que los nombres de las funciones incluyan la precisión con la que serán calculadas después, por lo que deberemos escribir _half o _float detrás del nombre de la función, así:

void DivideColor_half()
{
}

Añadiremos nuestros inputs igual que solemos hacer en C#. Yo prefiero ponerles los nombres en mayúscula para saber que son las variables que vienen de fuera de la función, pero no dudes en seguir el estándar que más te guste.

void DivideColor_half(half4 Color)
{
}

Hay dos formas de darle output a una función:

  • Añadir otro argumento con la palabra clave out.
void DivideColor_half(half4 Color, out half4 Out)
{
}
  • Indicar el valor de salida de la función, y retornar un valor.
half4 DivideColor_half(half4 Color)
{ 
   return Color;
}

Yo prefiero la primera forma, pero elige la que más te guste.

Con esto decidido, sólo nos quedaría terminar la función:

void DivideColor_half(half4 Color, out half4 Out) 
{
    Out = Color / 2;
}

Configurar un nuevo nodo personalizado

Para crear un nodo personalizado, añadiremos un nodo Custom Function a nuestro espacio de trabajo. Al seleccionarlo, el Graph Inspector cambiará para enseñarnos su configuración:

Tenemos que hacer cuatro cosas para configurar un nodo Custom Function:

  1. Seleccionar la precisión del nodo. Ésta debe coincidir con el sufijo que le pusimos al nombre de la función. En este caso; half.
  2. Añadir nuestro archivo a Source, el origen de la función.
  3. Escribir el nombre de la función que queremos ejecutar en este nodo sin el sufijo. No añadas _half detrás.
  4. Añadir los inputs y outputs de esta función en sus respectivas listas, seleccionando los tipos que hemos marcado en el orden correcto.
    • El nombre de las variables no importa, en realidad.

Si todo ha ido bien, deberías ver algo como esto:

Si la vista previa te enseña un patrón de ajedrez negro y magenta,

…es posible que la lógica de la función esté mal escrita, o que se te haya escapado algo al configurar el nodo. El símbolo de error que aparecerá en la esquina superior derecha del nodo y la consola del proyecto te darán pistas sobre lo que está pasando.

Sub-grafos

Los sub-grafos son un tipo de nodo especial. Nos permiten guardar una parte de un grafo de entero para reutilizarlo en otros grafos. Esto es muy útil, ya que nos ahorrará mucho trabajo y nos permitirá reutilizar y arreglar funcionalidades que queramos compartir entre los grafos.

Para crear un sub-grafo, selecciona los nodos que quieras que estén dentro del sub-grafo. No te preocupes si te falta alguno, podremos modificarlo después.

Haz click derecho y selecciona Convert To > Sub-graph.

Dale un nombre y guárdalo donde creas conveniente. Se creará un archivo con la extensión .shadersubgraph, y verás que en el grafo original los nodos que seleccionaste se han sustituido por un sólo nodo, con el nombre del sub-grafo.

Haz doble click en el nodo del sub-grafo para abrirlo en una ventana nueva.

En la pizarra de variables verás que aparecen las variables que toma este sub-grafo como input. Puedes cambiarles el nombre, lo que actualizará cómo se llaman en el grafo principal.

También podrás cambiar el nombre del nodo de Output desde el Graph Inspector. Unity le da por defecto un nombre genérico indicándonos el tipo del parámetro de salida.

¡Y recuerda que puedes añadir sub-grafos a este sub-grafo!

Output final

Te estarías preguntando qué son estos dos nodos que aparecen en el espacio de trabajo y que no hemos puesto nosotres. Pues bien, se trata del Output del shader, el resultado final.

Vertex

A través de este nodo devolveremos la información «física» del objeto; los datos sobre cada uno de sus vértices:

  • Position, su posición.
  • Normal, la dirección de sus normales.
  • Tangent, su tangente.

Esto nos permitirá modificar cómo y dónde se dibujan los vértices de una malla sin afectar a la información original del modelo.

Fragment

.

A través de este nodo devolveremos la iluminación de la superficie del objeto.

Cuando un objeto se renderiza en pantalla, su superficie se procesa por «fragmentos»; las secciones de sus caras que se renderizan.

A efectos prácticos, puedes equiparar fragmentos a píxeles.

  • Base Color, el color de la superficie del objeto.
  • Sprite Mask, el color cuando este objeto actua como máscara.

Las opciones añadidas que incluyen los grafos Lit vienen de que estos ya representan un modelo de iluminación completo. En realidad, esas opciones son más restrictivas, ya que no tendremos un control total sobre el resultado final. Esto no es malo, ya que muchas veces querremos utilizar ese modelo de iluminación.

Qué shader utilizar dependerá de nuestros casos de uso, como siempre.

Aplicar lo que hemos aprendido

¡Todo lo que te queda por hacer es empezar a crear shaders! Si no sabes por dónde empezar, te dejo aquí abajo una lista de tutoriales que he ido creando en el blog y en el canal.

  • Toon Shader Básico [Vídeo]

Déjame saber qué te han parecido y los shaders que te vayas inventandoen los comentarios.

Nos vemos en la próxima excusa 😉

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *