Shaders no Godot para Iniciantes: Escreva Seu Primeiro Shader Hoje

Tutorial de shader no Godot para iniciante: entenda a Godot Shading Language, escreva seu primeiro shader e use uniforms pra controlar efeitos em tempo real.
Shaders no Godot para Iniciantes: Escreva Seu Primeiro Shader Hoje
Shader tem fama de coisa de outro mundo. Quem está começando olha pra um efeito de água, um flash de dano ou uma distorção de calor e assume que aquilo exige matemática de pós-graduação. Não exige. Escrever um shader no Godot, mesmo sendo iniciante, é mais parecido com aprender uma segunda linguagem de script do que com virar engenheiro gráfico: a Godot Shading Language é pequena, o editor dá feedback na hora e o primeiro efeito útil cabe em cinco linhas.
Esse tutorial é o caminho que eu queria ter encontrado quando comecei: o que um shader realmente é, como a linguagem funciona, seu primeiro shader rodando num sprite, e uniforms pra controlar o efeito a partir do GDScript. Tudo em Godot 4.x, tudo código que roda como está.
O que um shader é, de verdade
Um shader é um programa que roda na GPU, não na CPU. E ele roda de um jeito muito específico: em paralelo, uma execução pra cada vértice ou pra cada pixel que vai aparecer na tela.
Isso muda a forma de pensar. No GDScript você escreve "percorra todos os inimigos e faça X". Num shader você escreve "dado ESTE pixel, qual a cor dele?". A GPU então faz essa pergunta pra milhões de pixels ao mesmo tempo. Você nunca escreve um loop sobre a imagem inteira; você escreve a regra de um pixel só, e a GPU aplica em todos.
No Godot existem três tipos principais de shader, declarados na primeira linha do arquivo:
shader_type canvas_item;pra tudo que é 2D: sprites, UI, TileMapshader_type spatial;pra materiais 3Dshader_type particles;pra controlar sistemas de partículas
Pra aprender, comece pelo canvas_item. O resultado é visual e imediato, e os conceitos transferem direto pro 3D depois.
Dentro do shader, duas funções importam agora:
vertex()roda uma vez por vértice e pode mover a geometriafragment()roda uma vez por pixel e decide a cor final
Noventa por cento dos efeitos 2D de iniciante vivem dentro da fragment().
A Godot Shading Language em cinco minutos
O Godot não usa GLSL puro: usa a Godot Shading Language, uma linguagem própria muito parecida com GLSL mas simplificada. Se você já escreve GDScript, as diferenças que vão te morder são poucas e vale conhecer antes do primeiro erro de compilação:
Tipagem é rígida. float, int, vec2, vec3, vec4 são tipos diferentes e a linguagem não converte sozinha. sin(x * 10) dá erro se x for float, porque 10 é int. Escreva 10.0. Esse é, disparado, o erro mais comum de quem vem do GDScript.
Vetores são a moeda da casa. Cor é um vec4 (vermelho, verde, azul, alfa, cada um de 0.0 a 1.0). Posição de pixel na textura é um vec2 chamado UV, que vai de (0.0, 0.0) no canto superior esquerdo a (1.0, 1.0) no inferior direito. Você acessa componentes com .x, .y ou .r, .g, .b, .a, e pode pegar vários de uma vez: cor.rgb é um vec3.
Existem variáveis embutidas (built-ins). No fragment() de um canvas_item, as que você vai usar o tempo todo:
UV: a coordenada do pixel atual na texturaTEXTURE: a textura do node (a imagem do Sprite2D, por exemplo)COLOR: a cor de saída; o que você escrever aqui é o que apareceTIME: segundos desde que o jogo começou, perfeito pra animar
Com isso você já lê 90% dos shaders 2D que encontrar por aí.
Seu primeiro shader no Godot, passo a passo
Crie uma cena com um Sprite2D e coloque qualquer textura nele (o icon.svg do projeto serve). Agora:
- Selecione o Sprite2D e, no Inspector, abra CanvasItem > Material
- Em Material, escolha New ShaderMaterial
- Clique no material criado e, em Shader, escolha New Shader
- Dê um nome (ex:
meu_primeiro.gdshader) e dê dois cliques pra abrir o editor de shader
O Godot abre um editor embutido com syntax highlight e erros em tempo real. Cole isto:
shader_type canvas_item;
void fragment() {
COLOR = vec4(1.0, 0.0, 0.0, 1.0);
}
O sprite inteiro ficou vermelho. Pode parecer pouco, mas você acabou de mandar a GPU ignorar a textura e pintar todo pixel com a mesma cor. É o "Hello World" de shader, e o ciclo de feedback é esse: salvou, viu na hora.
Segundo passo: em vez de jogar a textura fora, vamos lê-la e modificá-la. Esse shader converte o sprite pra preto e branco:
shader_type canvas_item;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
// Média ponderada: o olho humano é mais sensível ao verde.
float cinza = dot(tex.rgb, vec3(0.299, 0.587, 0.114));
COLOR = vec4(vec3(cinza), tex.a);
}
Três coisas novas aqui. texture(TEXTURE, UV) lê a cor da textura na posição do pixel atual. dot() multiplica os componentes par a par e soma, que é exatamente a conta de média ponderada. E vec3(cinza) cria um vetor com o mesmo valor nos três canais, ou seja, um tom de cinza. O alfa original (tex.a) é preservado, então as bordas transparentes do sprite continuam transparentes.
Isso já é um efeito de jogo de verdade: aplique num inimigo morto, numa foto de "memória", num mundo sem cor antes do power-up.
Animando com TIME
Shader estático é filtro de foto. Shader com TIME é efeito de jogo. A variável TIME cresce continuamente, e passar ela pra dentro de um sin() gera oscilação, que é a base de quase toda animação procedural.
Esse shader faz o sprite ondular como se estivesse debaixo d'água:
shader_type canvas_item;
void fragment() {
vec2 uv = UV;
// Desloca cada linha horizontalmente, com fase baseada na altura.
uv.x += sin(uv.y * 20.0 + TIME * 3.0) * 0.02;
COLOR = texture(TEXTURE, uv);
}
A lógica: em vez de mudar a cor, mudamos de onde lemos a cor. Cada pixel lê a textura um pouco deslocado pro lado, e o deslocamento varia com a altura (uv.y * 20.0 controla quantas ondas cabem) e com o tempo (TIME * 3.0 controla a velocidade). O 0.02 é a amplitude: quão longe a onda empurra.
Mexa nesses três números com o jogo rodando e observe. Esse hábito de fuçar constante por constante ensina mais que qualquer leitura.
Dá pra animar geometria também. Na função vertex(), a built-in VERTEX é a posição do vértice, e empurrar ela cria balanço físico:
shader_type canvas_item;
void vertex() {
// Balanço suave, tipo planta ao vento.
VERTEX.x += sin(TIME * 2.0 + VERTEX.y * 0.05) * 3.0;
}
Num Sprite2D comum isso mexe os quatro cantos do quad. O efeito fica muito melhor em meshes com mais vértices, mas a ideia de "vertex desloca, fragment colore" já fica clara aqui.
Uniforms: o jogo controlando o shader
Até agora todos os valores estão chumbados no código do shader. Uniforms resolvem isso: são variáveis que o shader expõe pra fora, editáveis no Inspector e alteráveis por GDScript em runtime. É a ponte entre a lógica do jogo e o efeito visual.
O exemplo clássico, que praticamente todo jogo 2D usa, é o flash branco de dano:
shader_type canvas_item;
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0;
uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
void fragment() {
vec4 tex = texture(TEXTURE, UV);
// mix() interpola: 0.0 = textura normal, 1.0 = cor cheia.
COLOR = vec4(mix(tex.rgb, flash_color.rgb, flash_amount), tex.a);
}
Os hints depois dos dois-pontos são só conforto de editor, mas que conforto: hint_range(0.0, 1.0) vira um slider no Inspector, e source_color vira um color picker. Salve o shader e olhe o material no Inspector: os dois parâmetros estão lá, mexíveis com o jogo rodando.
Agora o lado GDScript. Quando o inimigo toma dano, sobe o flash pra 1.0 e desce de volta a 0.0 com um tween:
extends Sprite2D
func tomar_dano() -> void:
material.set_shader_parameter("flash_amount", 1.0)
var tween = create_tween()
tween.tween_method(
func(valor): material.set_shader_parameter("flash_amount", valor),
1.0, 0.0, 0.25
)
set_shader_parameter() é o único método que você precisa decorar: ele escreve em qualquer uniform pelo nome. O tween anima o valor de 1.0 até 0.0 em um quarto de segundo, e o sprite pisca branco e volta ao normal. Quatro linhas de shader, meia dúzia de GDScript, e você tem o feedback de dano que todo action game usa.
Erros de shader que todo iniciante no Godot comete
Alguns tropeços são tão universais que vale listar antes que você perca uma tarde em cada um.
Int onde devia ser float. UV * 2 não compila; UV * 2.0 compila. Quando o editor reclamar de tipo, procure um número sem ponto decimal. Vai estar lá.
Material compartilhado mudando todo mundo junto. Material é um resource. Se você arrastou o mesmo material pra dez inimigos, mudar um uniform muda os dez ao mesmo tempo, inclusive via set_shader_parameter(). Pra cada instância ter seu próprio flash de dano, marque Resource > Local to Scene no material, ou use Make Unique no Inspector. Esse bug confunde porque o código está certo e o comportamento parece assombrado.
Esperar loop e estado. Não dá pra "guardar" um valor entre frames dentro do fragment shader, nem percorrer outros pixels livremente. Cada execução enxerga só o próprio pixel. Se o efeito precisa de memória (um rastro, por exemplo), o estado vive no GDScript e entra no shader via uniform.
Copiar GLSL da internet sem adaptar. Shadertoy e afins usam GLSL puro, com nomes diferentes (fragColor, iTime, iResolution). A tradução costuma ser direta (iTime vira TIME, fragColor vira COLOR), mas colar sem traduzir gera uma parede de erros que assusta à toa.
Fechando
Shader no Godot se resume a uma pergunta respondida milhões de vezes por segundo: "qual a cor deste pixel?". A Godot Shading Language te dá UV pra saber onde você está, TEXTURE pra ler a imagem original, TIME pra animar e uniforms pra conversar com o resto do jogo. Com essas quatro peças você reconstrói a maioria dos efeitos 2D que vê por aí.
Meu conselho prático: pegue o shader de flash de dano deste artigo e coloque num projeto seu hoje. Depois mude a cor, depois a duração, depois tente fazer o ondulado de água por conta própria sem olhar o código. Shader se aprende igual game feel: mexendo num número de cada vez e olhando a tela. A teoria pesada de gráficos pode esperar; o seu primeiro efeito, não.


