This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Shadertoy.com es un sitio que reúne shaders GL aportados por usuarios. Es un gran recurso para encontrar código de shader e inspiración. En este tutorial tomaremos un shader de Shadertoy y lo haremos funcionar en Defold. Se asume una comprensión básica de shaders. Si necesitas leer más, el manual de Shader es un buen lugar para empezar.
El shader que usaremos es Star Nest de Pablo Andrioli (usuario “Kali” en Shadertoy). Es un fragment shader puramente matemático y procedural de magia negra que renderiza un efecto de campo estelar realmente genial.

El shader tiene solo 65 líneas de código GLSL bastante complicado, pero no te preocupes. Vamos a tratarlo como una caja negra que hace lo suyo a partir de unas pocas entradas simples. Nuestro trabajo aquí es modificar el shader para que interactúe con Defold en lugar de Shadertoy.
El shader Star Nest es un fragment shader puro, así que solo necesitamos algo que el shader pueda texturizar. Hay varias opciones: un sprite, un tilemap, una GUI o un modelo. Para este tutorial vamos a usar un modelo 3D simple. La razón es que podemos convertir fácilmente el renderizado del modelo en un efecto de pantalla completa—algo que necesitamos hacer si queremos realizar post processing visual, por ejemplo.
Podemos empezar desde un proyecto vacío.

Puedes usar una mesh quad.gltf integrada desde builtins/assets/meshes.
Opcionalmente, también puedes crear una mesh de plano cuadrático en Blender o cualquier otro programa de modelado 3D — por comodidad, las 4 coordenadas de vértice están en -1 y 1 en el eje X y -1 y 1 en el eje Y. Blender tiene el eje Z hacia arriba por defecto, así que necesitas rotar la mesh 90° alrededor del eje X. También debes asegurarte de generar coordenadas UV correctas para la mesh. En Blender, entra en Edit Mode con la mesh seleccionada, luego selecciona Mesh ▸ UV unwrap... ▸ Unwrap.
Blender es software 3D gratuito y open-source que se puede descargar desde blender.org.

quad.gltf.model.material integrado.El modelo debería aparecer en el editor de escena, pero se renderiza completamente negro. Esto se debe a que todavía no tiene textura definida:

star-nest.material haciendo click con Right Mouse Button en la carpeta main del panel Assets y seleccionando New->Material, y nómbralo star-nest.
star-nest.vp y un fragment shader program star-nest.fp:star-nest.vp.star-nest.fp.view_proj” de tipo Viewproj (por “view projection”).
Abre el archivo de vertex shader program star-nest.vp. Debería contener el siguiente código:
#version 140
// las posiciones están en espacio del mundo
in vec4 position;
in vec2 texcoord0;
out vec2 var_texcoord0;
uniform vertex_inputs
{
mat4 view_proj;
};
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
Abre el archivo de fragment shader program star-nest.fp y modifica el código para que el color del fragmento se defina según las coordenadas X e Y de las coordenadas UV (var_texcoord0). Hacemos esto para asegurarnos de que tenemos el modelo configurado correctamente:
#version 140
in vec2 var_texcoord0;
out vec4 out_fragColor;
void main()
{
out_fragColor = vec4(var_texcoord0.xy, 0.0, 1.0);
}
Define la propiedad Material con nuestro material star-nest recién creado en el componente model del objeto de juego star-nest en main.collection.
Ahora el editor debería renderizar el modelo con el nuevo shader y podemos ver claramente si las coordenadas UV son correctas; la esquina inferior izquierda debería tener color negro (0, 0, 0), la esquina superior izquierda color verde (0, 1, 0), la esquina superior derecha color amarillo (1, 1, 0) y la esquina inferior derecha debería tener color rojo (1, 0, 0):

Ahora podemos ejecutar nuestro proyecto (Project->Build o el atajo Ctrl/Cmd + B), pero veremos una pantalla negra (bueno, casi, salvo quizá un pixel diminuto en la esquina inferior izquierda). Esto ocurre porque no hay cámara, y el render script predeterminado usa un fallback simple que muestra un espacio 2D enorme, mientras que nuestro modelo está en la posición (0,0,0) con solo 1 de ancho.
Agreguemos un objeto de juego con un componente de cámara para definir lo que veremos en el juego.
camera con posición (0,0,1). (Es importante definir la coordenada Z en 1, para que este objeto de juego esté delante de nuestro modelo, ya que el eje Z apunta ahora, en la configuración 2D predeterminada, hacia nosotros).Camera y verás una vista previa de cámara con nuestro quad dentro. Con las propiedades predeterminadas tenemos suerte en esta configuración y no necesitamos cambiar nada; ya deberíamos ver el resultado correcto, excepto por una cosa: no necesitamos un frustum de vista de cámara tan enorme, así que podemos reducir Far Z a 2.
Opcionalmente, podemos cambiar el tipo de cámara definiendo Orthographic Projection como true, y luego ajustar también Orthographic Zoom a algo como 600, pero en este caso no tendremos una relación de aspecto automática, así que nuestro modelo no llenará la pantalla.
Ahora que todo está en su lugar, empecemos a trabajar en el código real del shader. Primero veamos el código original. Consta de algunas secciones:

Usaremos un pipeline moderno con GLSL en versión 140; para hacerlo, declararemos la versión al inicio del archivo con #version 140.
Las líneas 5–18 definen un montón de constantes. Podemos dejarlas tal cual. Son constantes GLSL simples y no dependen específicamente de Shadertoy ni de Defold.
Las líneas 21 y 63 contienen las coordenadas de textura de espacio de pantalla X e Y del fragmento de entrada (in vec2 fragCoord) y el color de fragmento de salida (out vec4 fragColor).
Defold pasa coordenadas de textura desde el vertex shader al fragment shader a través de una variable interpolada como coordenadas UV (en el rango 0–1). En nuestro vertex shader esto se declara con un calificador out:
// in star-nest.vp
out vec2 var_texcoord0;
En el fragment shader, el mismo valor se recibe con un calificador in:
// in star-nest.fp
in vec2 var_texcoord0;
Luego, en GLSL 140, declaramos una salida de fragment explícita con el calificador out:
// in star-nest.fp
out vec4 out_fragColor;
Así que donde el código Shadertoy original escribe en fragColor, nuestro shader Defold escribe en out_fragColor.
Las líneas 23–27 configuran las dimensiones de la textura, así como la dirección de movimiento y el tiempo escalado. En Shadertoy, el shader recibe la posición de pixel mediante fragCoord y la resolución del viewport de la textura/viewport se pasa al shader como uniform vec3 iResolution. El shader calcula coordenadas estilo UV con la relación de aspecto correcta a partir de las coordenadas de fragmento y la resolución. También se aplica cierto desplazamiento de resolución para obtener un encuadre más agradable.
En Defold, no partimos de coordenadas de pixel. En su lugar, ya recibimos coordenadas UV normalizadas desde el vertex shader a través de var_texcoord0. Estas coordenadas están en el rango de 0.0 a 1.0 a través del quad renderizado.
La versión de Defold necesita alterar estos cálculos para usar las coordenadas UV de var_texcoord0.
Una conversión típica se ve así:
vec2 uv = var_texcoord0.xy;
uv = uv * 2.0 - 1.0;
uv.x *= aspect;
El valor exacto de aspecto depende de cómo esté configurado el ejemplo. Si el efecto se renderiza en un quad de pantalla completa con un tamaño de display conocido, la relación de aspecto puede hardcodearse para el tutorial. Si el efecto necesita soportar tamaños de ventana arbitrarios, pasa la resolución como una constante de fragmento y colócala dentro de un bloque uniform GLSL 140.
El tiempo también se configura aquí. Se pasa al shader como uniform float iGlobalTime. Defold (desde 1.12.3) proporciona tiempo a los shaders mediante una constante especial Time que usaremos.
En Defold moderno, los uniforms no opacos se declaran dentro de bloques uniform. En el fragment shader lo declaramos así:
uniform fragment_inputs
{
vec4 time;
};
Luego, en star-nest.material, agregaremos una Fragment Constant llamada time y definiremos su tipo como Time.
El valor puede usarse así:
float iGlobalTime = time.x;
float dt = time.y;
donde time.x es el tiempo desde el inicio del motor, y time.y es el delta time desde el frame anterior.
Las líneas 29–39 configuran la rotación del renderizado volumétrico, con la posición del mouse afectando la rotación. Las coordenadas del mouse se pasan al shader como uniform vec4 iMouse.
Para este tutorial vamos a omitir el input del mouse.
Las líneas 41–62 son el núcleo del shader. Podemos dejar este código tal cual.
Recorrer las secciones anteriores y hacer los cambios necesarios da como resultado el siguiente código de shader. Se limpió un poco para mejorar la legibilidad. Se indican las diferencias entre las versiones de Defold y Shadertoy:
#version 140 // <1>
// Star Nest by Pablo Román Andrioli
// This content is under the MIT License.
#define iterations 17
#define formuparam 0.53
#define volsteps 20
#define stepsize 0.1
#define zoom 0.800
#define tile 0.850
#define speed 0.010
#define brightness 0.0015
#define darkmatter 0.300
#define distfading 0.730
#define saturation 0.850
in vec2 var_texcoord0; // <2>
out vec4 out_fragColor; // <3>
uniform fragment_inputs // <4>
{
vec4 time;
};
void main() // <5>
{
// obtiene coordenadas y dirección
vec2 res = vec2(1.0, 1.0); // <6>
vec2 uv = var_texcoord0.xy * res.xy - 0.5;
vec3 dir = vec3(uv * zoom, 1.0);
float iGlobalTime = time.x; // <7>
float shader_time = iGlobalTime * speed;
float a1 = 0.5; // <8>
float a2 = 0.8;
mat2 rot1 = mat2(cos(a1), sin(a1), -sin(a1), cos(a1));
mat2 rot2 = mat2(cos(a2), sin(a2), -sin(a2), cos(a2));
dir.xz *= rot1;
dir.xy *= rot2;
vec3 from = vec3(1.0, 0.5, 0.5);
from += vec3(shader_time * 2.0, shader_time, -2.0);
from.xz *= rot1;
from.xy *= rot2;
// renderizado volumétrico
float s = 0.1;
float fade = 1.0;
vec3 v = vec3(0.0);
for (int r = 0; r < volsteps; r++) {
vec3 p = from + s * dir * 0.5;
// repetición plegada
p = abs(vec3(tile) - mod(p, vec3(tile * 2.0)));
float pa = 0.0;
float a = 0.0;
for (int i = 0; i < iterations; i++) {
// la fórmula mágica
p = abs(p) / dot(p, p) - formuparam;
// suma absoluta del cambio promedio
a += abs(length(p) - pa);
pa = length(p);
}
// materia oscura
float dm = max(0.0, darkmatter - a * a * 0.001);
a *= a * a;
// materia oscura, no renderizar cerca
if (r > 6) {
fade *= 1.0 - dm;
}
v += fade;
// coloreado basado en distancia
v += vec3(s, s * s, s * s * s * s) * a * brightness * fade;
fade *= distfading;
s += stepsize;
}
// ajuste de color
v = mix(vec3(length(v)), v, saturation);
out_fragColor = vec4(v * 0.01, 1.0); // <9>
}
Guarda el fragment shader program. El modelo ahora debería estar bien texturizado con un campo estelar en el editor de escena y en runtime:

La última pieza del puzzle es introducir tiempo para hacer que las estrellas se muevan. Defold (desde 1.12.3) proporciona esto automáticamente mediante una constante de fragmento de tipo Time.
Time.
¡Y eso es todo! Ya manejamos este time en el fragment shader. ¡Terminamos!
Un ejercicio de continuación divertido es agregar al shader el input original de movimiento del mouse. Necesitarás crear una nueva Fragment Constant, esta vez de tipo User, y actualizarla en on_input en algún script que detecte movimiento del mouse usando la función go.set() y definiendo las coordenadas de input en la nueva constante.
¡Feliz Defolding!