This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
Los programas shader están en el centro del renderizado de gráficos. Son programas escritos en un lenguaje similar a C llamado GLSL (GL Shading Language) que el hardware gráfico ejecuta para realizar operaciones sobre los datos 3D subyacentes (los vértices) o sobre los píxeles que terminan en pantalla (los “fragmentos”). Los shaders se usan para dibujar sprites, iluminar modelos 3D, crear efectos posteriores de pantalla completa y mucho, mucho más.
Este manual describe cómo el pipeline de renderizado de Defold se comunica con los shaders de la GPU. Para crear shaders para tu contenido, también necesitas entender el concepto de materiales, así como cómo funciona el pipeline de renderizado.
Las especificaciones de OpenGL ES 2.0 (OpenGL for Embedded Systems) y OpenGL ES Shading Language se pueden encontrar en Khronos OpenGL Registry.
Ten en cuenta que en computadoras de escritorio es posible escribir shaders usando funcionalidades que no están disponibles en OpenGL ES 2.0. El driver de tu tarjeta gráfica puede compilar y ejecutar sin problemas código shader que no funcionará en dispositivos móviles.
La entrada de un vertex shader son datos de vértices (en forma de attributes) y constantes (uniforms). Las constantes comunes son las matrices necesarias para transformar y proyectar la posición de un vértice al espacio de pantalla.
La salida del vertex shader es la posición de pantalla calculada del vértice (gl_Position). También es posible pasar datos del vertex shader al fragment shader mediante variables varying.
La entrada de un fragment shader son constantes (uniforms), así como cualquier variable varying definida por el vertex shader.
La salida del fragment shader es el valor de color del fragmento específico (gl_FragColor).
La entrada de un compute shader son buffers de constantes (uniforms), imágenes de textura (image2D), samplers (sampler2D) y buffers de almacenamiento (buffer).
La salida del compute shader no está definida explícitamente; no hay una salida específica que deba producirse, a diferencia de los vertex shaders y fragment shaders. Como los compute shaders son genéricos, depende del programador definir qué tipo de resultado debe producir el compute shader.
Cuando un modelo se coloca en el mundo del juego, las coordenadas locales de sus vértices deben traducirse a coordenadas de mundo. Esta traducción se realiza mediante una matriz de transformación de mundo, que indica qué traducción (movimiento), rotación y escala deben aplicarse a los vértices de un modelo para colocarlo correctamente en el sistema de coordenadas del mundo del juego.


position y texcoord0.position y texcoord0.position, textcoord0 y color.position, texcoord0 y color.position, texcoord0 y normal.position, texcoord0, face_color, outline_color y shadow_color.uniform en el programa shader. Los sampler uniforms se agregan a la sección Samplers del material y luego se declaran como uniform en el programa shader. Las matrices necesarias para realizar transformaciones de vértices en un vertex shader están disponibles como constantes:
CONSTANT_TYPE_WORLD es la matriz de mundo que transforma desde el espacio de coordenadas local de un objeto al espacio del mundo.CONSTANT_TYPE_VIEW es la matriz de vista que transforma desde el espacio del mundo al espacio de cámara.CONSTANT_TYPE_PROJECTION es la matriz de proyección que transforma desde la cámara al espacio de pantalla.CONSTANT_TYPE_USER es una constante de tipo vec4 que puedes usar como quieras.El manual de Material explica cómo especificar constantes.
sampler2D muestrea desde una textura de imagen 2D.sampler2DArray muestrea desde una textura de arreglo de imágenes 2D. Esto se usa principalmente para atlas paginados.samplerCube muestrea desde una textura cubemap de 6 imágenes.image2D carga (y potencialmente almacena) datos de textura en un objeto de imagen. Esto se usa principalmente para almacenamiento en compute shaders.Puedes usar un sampler solo en las funciones de búsqueda de texturas de la biblioteca estándar de GLSL. El manual de Material explica cómo especificar la configuración de samplers.

Un mapa UV se genera normalmente en el programa de modelado 3D y se almacena en la malla. Las coordenadas de textura de cada vértice se proporcionan al vertex shader como un atributo. Luego se usa una variable varying para encontrar la coordenada UV de cada fragmento, interpolada a partir de los valores de los vértices.
varying se usan para pasar información entre la etapa de vértices y la etapa de fragmentos.
varying se define en el vertex shader para cada vértice.
Por ejemplo, definir un varying con un valor de color RGB vec3 en cada esquina de un triángulo interpolará los colores a través de toda la forma. Del mismo modo, definir coordenadas de búsqueda de mapa de textura (o coordenadas UV) en cada vértice de un rectángulo permite que el fragment shader busque valores de color de textura para toda el área de la forma.

Como el motor Defold soporta varias plataformas y API gráficas, debe ser sencillo para los desarrolladores escribir shaders que funcionen en todas partes. El pipeline de assets logra esto principalmente de dos maneras (denominadas shader pipelines de ahora en adelante):
A partir de Defold 1.9.2, se recomienda escribir shaders que utilicen el nuevo pipeline, y para lograrlo la mayoría de los shaders deben migrarse a shaders escritos al menos en la versión 140 (OpenGL 3.1). Para migrar un shader, asegúrate de cumplir estos requisitos:
Coloca al menos #version 140 en la parte superior del shader:
#version 140
Así es como se elige el pipeline de shaders en el proceso de build, por eso todavía puedes usar los shaders antiguos. Si no se encuentra ninguna declaración de versión del preprocesador, Defold volverá al pipeline heredado.
En vertex shaders, reemplaza la palabra clave attribute por in:
// en lugar de:
// attribute vec4 position;
// haz:
in vec4 position;
Nota: Los fragment shaders (y compute shaders) no reciben entradas de vértices.
En vertex shaders, los varyings deben tener el prefijo out. En fragment shaders, los varyings pasan a ser in:
// En un vertex shader, en lugar de:
// varying vec4 var_color;
// haz:
out vec4 var_color;
// En un fragment shader, en lugar de:
// varying vec4 var_color;
// haz:
in vec4 var_color;
Los tipos uniform opacos (samplers, imágenes, atomics, SSBOs) no necesitan ninguna migración; puedes usarlos como lo haces hoy:
uniform sampler2D my_texture;
uniform image2D my_image;
Para tipos uniform no opacos, debes colocarlos en un uniform block. Un uniform block es simplemente una colección de variables uniform y se declara con la palabra clave uniform:
uniform vertex_inputs
{
mat4 mtx_world;
mat4 mtx_proj;
mat4 mtx_view;
mat4 mtx_normal;
...
};
void main()
{
// Los miembros individuales del uniform block se pueden usar tal cual
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position, 1.0);
}
Todos los miembros del uniform block se exponen a materiales y componentes como constantes individuales. No se necesita migración para usar buffers de constantes de renderizado, ni go.set y go.get.
En fragment shaders, gl_FragColor está obsoleto a partir de la versión 140. Usa out en su lugar:
// en lugar de:
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// haz:
out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
}
Las funciones específicas de muestreo de texturas, como texture2D y texture2DArray, ya no existen. En su lugar, usa simplemente la función texture:
uniform sampler2D my_texture;
uniform sampler2DArray my_texture_array;
// en lugar de:
// vec4 sampler_2d = texture2D(my_texture, uv);
// vec4 sampler_2d_array = texture2DArray(my_texture_array, vec3(uv, slice));
// haz:
vec4 sampler_2d = texture(my_texture, uv);
vec4 sampler_2d_array = texture(my_texture_array, vec3(uv, slice));
Definir precisión explícita para variables, entradas, salidas y demás era necesario antes para cumplir con contextos OpenGL ES. Esto ya no es necesario; ahora la precisión se define automáticamente para las plataformas que la soportan.
Como ejemplo final donde se aplican todas estas reglas, aquí están los shaders de sprite integrados convertidos al formato nuevo:
#version 140
uniform vx_uniforms
{
mat4 view_proj;
};
// las posiciones están en espacio del mundo
in vec4 position;
in vec2 texcoord0;
out vec2 var_texcoord0;
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
#version 140
in vec2 var_texcoord0;
out vec4 color_out;
uniform sampler2D texture_sampler;
uniform fs_uniforms
{
vec4 tint;
};
void main()
{
// Premultiplica alfa, ya que todas las texturas de runtime ya tienen alfa premultiplicado
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
color_out = texture(texture_sampler, var_texcoord0.xy) * tint_pm;
}
Los shaders en Defold soportan incluir código fuente desde archivos dentro del proyecto que tengan la extensión .glsl. Para incluir un archivo glsl desde un shader, usa el pragma #include con comillas dobles o corchetes. Los includes deben tener rutas relativas al proyecto o una ruta relativa al archivo que los incluye:
// En el archivo /main/my-shader.fp
// Ruta absoluta
#include "/main/my-snippet.glsl"
// El archivo está en la misma carpeta
#include "my-snippet.glsl"
// El archivo está en una subcarpeta al mismo nivel que 'my-shader'
#include "sub-folder/my-snippet.glsl"
// El archivo está en una subcarpeta del directorio padre, es decir /some-other-folder/my-snippet.glsl
#include "../some-other-folder/my-snippet.glsl"
// El archivo está en el directorio padre, es decir /root-level-snippet.glsl
#include "../root-level-snippet.glsl"
Hay algunas salvedades sobre cómo se detectan los includes:
/ inicial.const float #include "my-float-name.glsl" = 1.0 no funcionará.Los fragmentos de código pueden incluir otros archivos .glsl, lo que significa que el shader final producido puede llegar a incluir el mismo código varias veces y, dependiendo del contenido de los archivos, puedes terminar con problemas de compilación por tener los mismos símbolos declarados más de una vez. Para evitar esto, puedes usar header guards, un concepto común en varios lenguajes de programación. Ejemplo:
// En my-shader.vs
#include "math-functions.glsl"
#include "pi.glsl"
// En math-functions.glsl
#include "pi.glsl"
// En pi.glsl
const float PI = 3.14159265359;
En este ejemplo, la constante PI se definirá dos veces, lo que causará errores de compilación al ejecutar el proyecto. En su lugar, debes proteger el contenido con header guards:
// En pi.glsl
#ifndef PI_GLSL_H
#define PI_GLSL_H
const float PI = 3.14159265359;
#endif // PI_GLSL_H
El código de pi.glsl se expandirá dos veces en my-shader.vs, pero como lo has envuelto en header guards, el símbolo PI solo se definirá una vez y el shader compilará correctamente.
Sin embargo, esto no siempre es estrictamente necesario, según el caso de uso. Si en cambio quieres reutilizar código localmente en una función o en otro lugar donde no necesitas que los valores estén disponibles globalmente en el código del shader, probablemente no deberías usar header guards. Ejemplo:
// En red-color.glsl
vec3 my_red_color = vec3(1.0, 0.0, 0.0);
// En my-shader.fp
vec3 get_red_color()
{
#include "red-color.glsl"
return my_red_color;
}
vec3 get_red_color_inverted()
{
#include "red-color.glsl"
return 1.0 - my_red_color;
}
Cuando los shaders se renderizan en el viewport del editor Defold, está disponible una definición de preprocesador EDITOR. Esto te permite escribir código shader que se comporte de forma diferente cuando se ejecuta en el editor y cuando se ejecuta en el motor real del juego.
Esto es especialmente útil para:
Usa la directiva de preprocesador #ifdef EDITOR para compilar condicionalmente código que solo debe ejecutarse en el editor:
#ifdef EDITOR
// Este código solo se ejecutará cuando el shader se renderice en el Defold Editor
color_out = vec4(1.0, 0.0, 1.0, 1.0); // Color magenta para la vista previa del editor
#else
// Este código se ejecutará cuando se ejecute en el juego
color_out = texture(texture_sampler, var_texcoord0) * tint_pm;
#endif
Antes de terminar en la pantalla, los datos que creas para tu juego pasan por una serie de pasos:

Todos los componentes visuales (sprites, nodos GUI, efectos de partículas o modelos) están compuestos por vértices, puntos en el mundo 3D que describen la forma del componente. Lo bueno de esto es que es posible ver la forma desde cualquier ángulo y distancia. El trabajo del programa vertex shader es tomar un único vértice y traducirlo a una posición en el viewport para que la forma pueda terminar en pantalla. Para una forma con 4 vértices, el programa vertex shader se ejecuta 4 veces, cada una en paralelo.

La entrada del programa es la posición del vértice (y otros datos de atributos asociados con el vértice) y la salida es una nueva posición de vértice (gl_Position), así como cualquier variable varying que deba interpolarse para cada fragmento.
El programa vertex shader más simple solo establece la posición de la salida en un vértice cero (lo cual no es muy útil):
void main()
{
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
Un ejemplo más completo es el vertex shader de sprite integrado:
-- sprite.vp
uniform mediump mat4 view_proj; // [1]
attribute mediump vec4 position; // [2]
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0; // [3]
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0); // [4]
var_texcoord0 = texcoord0; // [5]
}
position ya está transformado a espacio del mundo. texcoord0 contiene la coordenada UV del vértice.varying. Esta variable se interpolará para cada fragmento entre los valores definidos para cada vértice y se enviará al fragment shader.gl_Position se define como la posición de salida del vértice actual en el espacio de proyección. Este valor tiene 4 componentes: x, y, z y w. El componente w se usa para calcular interpolación correcta en perspectiva. Este valor normalmente es 1.0 para cada vértice antes de aplicar cualquier matriz de transformación.varying para esta posición de vértice. Después de la rasterización se interpolará para cada fragmento y se enviará al fragment shader.Después del vertex shading, se decide la forma en pantalla del componente: se generan y rasterizan formas primitivas, lo que significa que el hardware gráfico divide cada forma en fragmentos, o píxeles. Luego ejecuta el programa fragment shader, una vez por cada fragmento. Para una imagen en pantalla de 16x24 píxeles, el programa se ejecuta 384 veces, cada una en paralelo.

La entrada del programa es lo que envíen el pipeline de renderizado y el vertex shader, normalmente las coordenadas UV del fragmento, colores de tinte, etc. La salida es el color final del píxel (gl_FragColor).
El programa fragment shader más simple solo establece el color de cada píxel en negro (de nuevo, no es un programa muy útil):
void main()
{
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
De nuevo, un ejemplo más completo es el fragment shader de sprite integrado:
// sprite.fp
varying mediump vec2 var_texcoord0; // [1]
uniform lowp sampler2D DIFFUSE_TEXTURE; // [2]
uniform lowp vec4 tint; // [3]
void main()
{
lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w); // [4]
lowp vec4 diff = texture2D(DIFFUSE_TEXTURE, var_texcoord0.xy);// [5]
gl_FragColor = diff * tint_pm; // [6]
}
varying de coordenadas de textura. El valor de esta variable se interpolará para cada fragmento entre los valores definidos para cada vértice de la forma.sampler2D. El sampler, junto con las coordenadas de textura interpoladas, se usa para realizar la búsqueda de textura y que el sprite pueda texturizarse correctamente. Como esto es un sprite, el motor asignará este sampler a la imagen definida en la propiedad Image del sprite.CONSTANT_TYPE_USER se define en el material y se declara como uniform. Su valor se usa para permitir el tinte de color del sprite. El valor predeterminado es blanco puro.gl_FragColor se define como el color de salida del fragmento: el color difuso de la textura multiplicado por el valor de tinte.El valor de fragmento resultante luego pasa por pruebas. Una prueba común es la prueba de profundidad, en la que el valor de profundidad del fragmento se compara con el valor del depth buffer del píxel que se está probando. Según la prueba, el fragmento puede descartarse o se escribe un valor nuevo en el depth buffer. Un uso común de esta prueba es permitir que los gráficos que están más cerca de la cámara bloqueen los gráficos más alejados.
Si la prueba concluye que el fragmento debe escribirse en el framebuffer, se mezclará con los datos de píxel ya presentes en el buffer. Los parámetros de blending definidos en el render script permiten combinar de varias maneras el color de origen (el valor escrito por el fragment shader) y el color de destino (el color de la imagen en el framebuffer). Un uso común del blending es habilitar el renderizado de objetos transparentes.
Shadertoy contiene una enorme cantidad de shaders aportados por usuarios. Es una gran fuente de inspiración donde puedes aprender sobre varias técnicas de shading. Muchos de los shaders mostrados en el sitio se pueden portar a Defold con muy poco trabajo. El tutorial de Shadertoy repasa los pasos para convertir un shader existente a Defold.
El tutorial de Grading muestra cómo crear un efecto de gradación de color de pantalla completa usando texturas de tabla de búsqueda de color para la gradación.
The Book of Shaders te enseñará cómo usar e integrar shaders en tus proyectos, mejorando su rendimiento y calidad gráfica.