Introducción a los shaders

Esta página explica qué son los shaders y te proporcionará una visión general de cómo funcionan en Godot. Para una referencia detallada del lenguaje de sombreado del motor, consulta Lenguaje de shading.

Los shaders son un tipo especial de programa que se ejecuta en las Unidades de Procesamiento Gráfico (GPU). Inicialmente se utilizaban para sombrear escenas 3D, pero en la actualidad pueden hacer mucho más. Puedes utilizarlos para controlar cómo el motor dibuja geometría y píxeles en la pantalla, lo que te permite lograr todo tipo de efectos.

Los motores de renderizado modernos como Godot dibujan todo utilizando shaders: las tarjetas gráficas pueden ejecutar miles de instrucciones en paralelo, lo que lleva a una velocidad de renderizado increíble.

Debido a su naturaleza paralela, los shaders no procesan la información de la misma manera que lo hace un programa típico. El código del shader se ejecuta en cada vértice o píxel de forma aislada. Tampoco puedes almacenar datos entre fotogramas. Como resultado, al trabajar con shaders, necesitas programar y pensar de manera diferente a otros lenguajes de programación.

Supongamos que quieres actualizar todos los píxeles de una textura a un color dado. En GDScript, tu código usaría bucles for de la siguiente manera:

for x in range(width):
  for y in range(height):
    set_color(x, y, some_color)

Tu código ya forma parte de un bucle en un shader, por lo que el código correspondiente se vería así.

void fragment() {
  COLOR = some_color;
}

Nota

La tarjeta gráfica llama a la función fragment() una o más veces por cada píxel que tiene que dibujar. Más sobre eso a continuación.

Shaders en Godot

Godot proporciona un lenguaje de sombreado basado en el popular Lenguaje de Sombreado de OpenGL (GLSL) pero simplificado. El motor se encarga de parte del trabajo de inicialización de bajo nivel, lo que facilita la escritura de shaders complejos.

En Godot, los shaders están compuestos por tres funciones principales: vertex(), fragment()` y light().

  1. La función vertex() se ejecuta en todos los vértices de la malla y establece sus posiciones y algunas otras variables por vértice.

  2. La función fragment() se ejecuta para cada píxel cubierto por la malla. Utiliza los valores generados por la función vertex(), interpolados entre los vértices.

  3. La función light() se ejecuta para cada píxel y para cada luz. Toma variables de la función fragment() y de sus ejecuciones anteriores.

Advertencia

La función light() no se ejecutará si el modo de renderizado vertex_lighting está habilitado, o si la opción Rendering > Quality > Shading > Force Vertex Shading está habilitada en la Configuración del Proyecto. Por defecto, está habilitada en plataformas móviles.

Tipos de Shaders

En lugar de proporcionar una configuración de propósito general para todos los usos (2D, 3D, partículas), debes especificar el tipo de shader que estás escribiendo. Los diferentes tipos de shaders admiten diferentes modos de renderizado, variables integradas y funciones de procesamiento.

En Godot, todos los shaders deben especificar su tipo en la primera línea, de la siguiente manera:

shader_type spatial;

Aqui están los tipos validos:

Modos de renderizado

Los shaders tienen modos de renderizado opcionales que puedes especificar en la segunda línea, después del tipo de shader, de la siguiente manera:

shader_type spatial;
render_mode unshaded, cull_disabled;

Los modos de renderizado alteran la forma en que Godot aplica el shader. Por ejemplo, el modo unshaded hace que el motor omita la función incorporada de procesamiento de luces.

Cada tipo de shader tiene diferentes modos de renderizado. Consulta la referencia de cada tipo de shader para obtener una lista completa de los modos de renderizado disponibles.

Funciones de procesamiento

Dependiendo del tipo de shader, puedes sobrescribir diferentes funciones de procesamiento. Para spatial y canvas_item, tienes acceso a vertex(), fragment() y light(). Para particles, solo tienes acceso a vertex().

Procesador Vertex

La función de procesamiento vertex() se llama una vez por cada vértice en los shaders spatial y canvas_item. Para los shaders de particles, se llama una vez por cada partícula.

Cada vértice en la geometría de tu mundo tiene propiedades como una posición y un color. La función "vertex()" modifica esos valores y los pasa a la función "fragment()". También puedes usarla para enviar datos adicionales a la función "fragment()" utilizando "varyings".

Por defecto, Godot transforma la información de tus vértices por ti, lo cual es necesario para proyectar la geometría en la pantalla. Sin embargo, puedes utilizar modos de renderizado para transformar los datos tú mismo; consulta la documentación de shaders espaciales para ver un ejemplo.

Procesador Fragment

La función de procesamiento fragment() se utiliza para configurar los parámetros del material de Godot por píxel. Este código se ejecuta en cada píxel visible que el objeto o primitiva dibuja. Solo está disponible en shaders spatial y canvas_item.

El uso estándar de la función fragment() es configurar las propiedades del material que se utilizan para calcular la iluminación. Por ejemplo, podrías establecer valores para ROUGHNESS, RIM o TRANSMISSION, que le indicarían a la función de luz cómo responder a esa fracción. Esto permite controlar un complejo proceso de sombreado sin que el usuario tenga que escribir mucho código. Si no necesitas esta funcionalidad incorporada, puedes ignorarla y escribir tu propia función de procesamiento de luz, y Godot la optimizará. Por ejemplo, si no escribes un valor en RIM, Godot no calculará la iluminación del borde. Durante la compilación, Godot comprueba si se utiliza RIM; si no es así, eliminará todo el código correspondiente. Por lo tanto, no desperdiciarás cálculos en efectos que no utilices.

Procesador Light

El procesador light() también se ejecuta por píxel y se ejecuta una vez por cada luz que afecta al objeto. No se ejecuta si ninguna luz afecta al objeto. Es una función que se llama dentro del procesador fragment() y generalmente opera en las propiedades del material configuradas dentro del procesador fragment().

El procesador light() funciona de manera diferente en 2D que en 3D; para una descripción de cómo funciona en cada caso, consulta su documentación en Shaders de CanvasItem y Shaders espaciales, respectivamente.