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 é um site que reúne shaders GL contribuídos por usuários. É um ótimo recurso para encontrar código de shader e inspiração. Neste tutorial, vamos pegar um shader do Shadertoy e fazê-lo rodar no Defold. Presume-se algum entendimento básico de shaders. Se você precisar estudar o assunto, o manual de Shader é um bom ponto de partida.
O shader que usaremos é Star Nest, de Pablo Andrioli (usuário “Kali” no Shadertoy). É um fragment shader procedural puramente matemático que renderiza um efeito de campo estelar muito interessante.

O shader tem apenas 65 linhas de código GLSL bastante complicado, mas não se preocupe. Vamos tratá-lo como uma caixa-preta que faz seu trabalho com base em algumas entradas simples. Nosso trabalho aqui é modificar o shader para que ele se conecte ao Defold em vez do Shadertoy.
O shader Star Nest é um fragment shader puro, então precisamos apenas de algo para o shader texturizar. Há várias opções: um sprite, um tilemap, uma GUI ou um modelo. Neste tutorial, usaremos um modelo 3D simples. O motivo é que podemos transformar facilmente a renderização do modelo em um efeito de tela cheia—algo que precisamos fazer se quisermos aplicar pós-processamento visual, por exemplo.
Podemos começar a partir de um projeto vazio.

Você pode usar uma malha quad.gltf embutida em builtins/assets/meshes.
Opcionalmente, você também pode criar uma malha de plano quadrado no Blender, ou em qualquer outro programa de modelagem 3D — por conveniência, as 4 coordenadas dos vértices ficam em -1 e 1 no eixo X, e -1 e 1 no eixo Y. O Blender usa o eixo Z apontando para cima por padrão, então você precisa girar a malha 90° ao redor do eixo X. Você também deve garantir que as coordenadas UV corretas sejam geradas para a malha. No Blender, entre no Edit Mode com a malha selecionada e então selecione Mesh ▸ UV unwrap... ▸ Unwrap.
Blender é um software 3D gratuito e open-source que pode ser baixado em blender.org.

quad.gltf.model.material embutido.O modelo deve aparecer no editor de cena, mas é renderizado todo preto. Isso acontece porque ele ainda não tem textura definida:

star-nest.material clicando com Right Mouse Button na pasta main no painel Assets, selecionando New->Material e nomeando-o star-nest.
star-nest.vp e um programa de fragment shader star-nest.fp:star-nest.vp.star-nest.fp.view_proj”, do tipo Viewproj (de “view projection”).
Abra o arquivo do programa de vertex shader star-nest.vp. Ele deve conter o código a seguir:
#version 140
// positions are in world space
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;
}
Abra o arquivo do programa de fragment shader star-nest.fp e modifique o código para que a cor do fragmento seja definida com base nas coordenadas X e Y das coordenadas UV (var_texcoord0). Fazemos isso para garantir que o modelo esteja configurado corretamente:
#version 140
in vec2 var_texcoord0;
out vec4 out_fragColor;
void main()
{
out_fragColor = vec4(var_texcoord0.xy, 0.0, 1.0);
}
Defina a propriedade Material para o material star-nest recém-criado no componente de modelo do objeto de jogo star-nest na main.collection.
Agora o editor deve renderizar o modelo com o novo shader e podemos ver claramente se as coordenadas UV estão corretas: o canto inferior esquerdo deve ter cor preta (0, 0, 0), o canto superior esquerdo deve ter cor verde (0, 1, 0), o canto superior direito deve ter cor amarela (1, 1, 0) e o canto inferior direito deve ter cor vermelha (1, 0, 0):

Agora podemos executar nosso projeto (Project->Build ou o atalho Ctrl/Cmd + B), mas veremos uma tela preta (bem, quase, exceto talvez por um pixel minúsculo no canto inferior esquerdo). Isso acontece porque não há câmera, e o script de renderização padrão usa um fallback simples, mostrando um espaço 2D enorme, enquanto nosso modelo está na posição (0,0,0) com apenas 1 de largura.
Vamos adicionar um objeto de jogo com um componente de câmera para definir o que veremos no jogo.
camera com posição (0,0,1). (É importante definir a coordenada Z como 1, para que esse objeto de jogo fique à frente do nosso modelo, já que o eixo Z agora aponta para nós na configuração 2D padrão).Camera e você verá uma prévia da câmera com nosso quad dentro. Com as propriedades padrão, temos sorte suficiente nesse cenário para não precisar alterar nada e já devemos ver o resultado correto, exceto por uma coisa - não precisamos de um frustum de câmera tão grande, então podemos reduzir o Far Z para 2.
Opcionalmente, podemos alterar o tipo da câmera definindo Orthographic Projection como true, e então também ajustar o Orthographic Zoom para algo como 600, mas nesse caso não teremos proporção automática, então nosso modelo não preencherá a tela.
Agora que tudo está no lugar, vamos começar a trabalhar no código real do shader. Primeiro, vamos olhar o código original. Ele consiste em algumas seções:

Usaremos um pipeline moderno com GLSL na versão 140 - para isso, declaramos a versão no topo do arquivo com #version 140.
As linhas 5–18 definem várias constantes. Podemos deixá-las como estão. Elas são constantes GLSL simples e não dependem especificamente do Shadertoy ou do Defold.
As linhas 21 e 63 contêm as coordenadas X e Y em espaço de tela do fragmento de entrada (in vec2 fragCoord) e a cor do fragmento de saída (out vec4 fragColor).
O Defold passa coordenadas de textura do vertex shader para o fragment shader por meio de uma variável interpolada como coordenadas UV (no intervalo 0–1). Em nosso vertex shader isso é declarado com um qualificador out:
// in star-nest.vp
out vec2 var_texcoord0;
No fragment shader, o mesmo valor é recebido com um qualificador in:
// in star-nest.fp
in vec2 var_texcoord0;
Então, no GLSL 140, declaramos uma saída explícita do fragmento com o qualificador out:
// in star-nest.fp
out vec4 out_fragColor;
Portanto, onde o código original do Shadertoy escreve em fragColor, nosso shader Defold escreve em out_fragColor.
As linhas 23–27 configuram as dimensões da textura, além da direção de movimento e do tempo escalado. No Shadertoy, o shader recebe a posição do pixel por fragCoord e a resolução do viewport/textura é passada ao shader como uniform vec3 iResolution. O shader calcula coordenadas no estilo UV com a proporção correta a partir das coordenadas do fragmento e da resolução. Também é feito algum deslocamento da resolução para obter um enquadramento melhor.
No Defold, não partimos de coordenadas de pixel. Em vez disso, já recebemos coordenadas UV normalizadas do vertex shader por meio de var_texcoord0. Essas coordenadas estão no intervalo de 0.0 a 1.0 ao longo do quad renderizado.
A versão para Defold precisa alterar esses cálculos para usar as coordenadas UV de var_texcoord0.
Uma conversão típica se parece com isto:
vec2 uv = var_texcoord0.xy;
uv = uv * 2.0 - 1.0;
uv.x *= aspect;
O valor exato de aspect depende de como o exemplo está configurado. Se o efeito for renderizado em um quad de tela cheia com tamanho de display conhecido, a proporção pode ser codificada diretamente para o tutorial. Se o efeito precisar suportar tamanhos de janela arbitrários, passe a resolução como uma constante de fragmento e coloque-a dentro de um bloco uniform GLSL 140.
O tempo também é configurado aqui. Ele é passado ao shader como uniform float iGlobalTime. O Defold (desde 1.12.3) fornece tempo aos shaders por meio de uma constante especial Time, que usaremos.
No Defold moderno, uniforms não opacos são declarados dentro de blocos uniform. No fragment shader, nós o declaramos assim:
uniform fragment_inputs
{
vec4 time;
};
Então, no star-nest.material, adicionaremos uma Fragment Constant chamada time e definiremos seu tipo como Time.
O valor pode então ser usado assim:
float iGlobalTime = time.x;
float dt = time.y;
onde time.x é o tempo desde o início da engine, e time.y é o delta time do frame anterior.
As linhas 29–39 configuram a rotação da renderização volumétrica, com a posição do mouse afetando a rotação. As coordenadas do mouse são passadas ao shader como uniform vec4 iMouse.
Neste tutorial, vamos ignorar a entrada do mouse.
As linhas 41–62 são o núcleo do shader. Podemos deixar esse código como está.
Percorrer as seções acima e fazer as alterações necessárias resulta no código de shader a seguir. Ele foi limpo um pouco para melhorar a legibilidade. As diferenças entre as versões do Defold e do Shadertoy são anotadas:
#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>
{
// get coords and direction
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;
// volumetric rendering
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;
// tiling fold
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++) {
// the magic formula
p = abs(p) / dot(p, p) - formuparam;
// absolute sum of average change
a += abs(length(p) - pa);
pa = length(p);
}
// dark matter
float dm = max(0.0, darkmatter - a * a * 0.001);
a *= a * a;
// dark matter, don't render near
if (r > 6) {
fade *= 1.0 - dm;
}
v += fade;
// coloring based on distance
v += vec3(s, s * s, s * s * s * s) * a * brightness * fade;
fade *= distfading;
s += stepsize;
}
// color adjust
v = mix(vec3(length(v)), v, saturation);
out_fragColor = vec4(v * 0.01, 1.0); // <9>
}
Salve o programa de fragment shader. O modelo agora deve estar bem texturizado com um campo estelar no editor Scene e em runtime:

A peça final do quebra-cabeça é introduzir tempo para fazer as estrelas se moverem. O Defold (desde 1.12.3) fornece isso automaticamente por meio de uma constante de fragmento do tipo Time.
Time.
E é isso! Já lidamos com esse time no fragment shader. Terminamos!
Um exercício divertido de continuação é adicionar ao shader a entrada original de movimento do mouse. Você precisará criar uma nova Fragment Constant, desta vez do tipo User, e atualizá-la em on_input em algum script que detecte movimento do mouse usando a função go.set() e definindo as coordenadas de entrada nessa nova constante.
Boas criações com Defold!