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
Programas de shader estão no centro da renderização gráfica. Eles são programas escritos em uma linguagem parecida com C chamada GLSL (GL Shading Language), que o hardware gráfico executa para realizar operações nos dados 3D subjacentes (os vértices) ou nos pixels que acabam na tela (os “fragmentos”). Shaders são usados para desenhar sprites, iluminar modelos 3D, criar efeitos de pós-processamento em tela cheia e muito, muito mais.
Este manual descreve como o pipeline de renderização do Defold se comunica com shaders na GPU. Para criar shaders para seu conteúdo, você também precisa entender o conceito de materiais, assim como o funcionamento do pipeline de renderização.
As especificações de OpenGL ES 2.0 (OpenGL for Embedded Systems) e OpenGL ES Shading Language podem ser encontradas em Khronos OpenGL Registry.
Observe que, em computadores desktop, é possível escrever shaders usando recursos não disponíveis no OpenGL ES 2.0. O driver da sua placa de vídeo pode compilar e executar sem problemas código de shader que não funcionará em dispositivos móveis.
A entrada de um vertex shader são dados de vértice (na forma de attributes) e constantes (uniforms). Constantes comuns são as matrizes necessárias para transformar e projetar a posição de um vértice para o espaço da tela.
A saída do vertex shader é a posição calculada do vértice na tela (gl_Position). Também é possível passar dados do vertex shader para o fragment shader por meio de variáveis varying.
A entrada de um fragment shader são constantes (uniforms), além de quaisquer variáveis varying definidas pelo vertex shader.
A saída do fragment shader é o valor de cor do fragmento específico (gl_FragColor).
A entrada de um compute shader são buffers de constantes (uniforms), imagens de textura (image2D), samplers (sampler2D) e buffers de armazenamento (buffer).
A saída do compute shader não é definida explicitamente; não há uma saída específica que precise ser produzida, ao contrário dos vertex shaders e fragment shaders. Como compute shaders são genéricos, cabe ao programador definir que tipo de resultado o compute shader deve produzir.
Quando um modelo é colocado no mundo do jogo, as coordenadas locais dos vértices do modelo precisam ser traduzidas para coordenadas de mundo. Essa tradução é feita por uma matriz de transformação world, que informa qual translação (movimento), rotação e escala devem ser aplicadas aos vértices de um modelo para que ele seja posicionado corretamente no sistema de coordenadas do mundo do jogo.


position e texcoord0.position e texcoord0.position, textcoord0 e color.position, texcoord0 e color.position, texcoord0 e normal.position, texcoord0, face_color, outline_color e shadow_color.uniform no programa de shader. Uniforms de sampler são adicionados à seção Samplers do material e depois declarados como uniform no programa de shader. As matrizes necessárias para realizar transformações de vértices em um vertex shader ficam disponíveis como constantes:
CONSTANT_TYPE_WORLD é a matriz world que mapeia do espaço de coordenadas local de um objeto para o espaço de mundo.CONSTANT_TYPE_VIEW é a matriz de visualização que mapeia do espaço de mundo para o espaço da câmera.CONSTANT_TYPE_PROJECTION é a matriz de projeção que mapeia da câmera para o espaço da tela.CONSTANT_TYPE_USER é uma constante do tipo vec4 que você pode usar como quiser.O manual de Material explica como especificar constantes.
sampler2D amostra de uma textura de imagem 2D.sampler2DArray amostra de uma textura de array de imagens 2D. Isso é usado principalmente para atlas paginados.samplerCube amostra de uma textura cubemap com 6 imagens.image2D carrega (e potencialmente armazena) dados de textura em um objeto de imagem. Isso é usado principalmente por compute shaders para armazenamento.Você só pode usar um sampler nas funções de consulta de textura da biblioteca padrão GLSL. O manual de Material explica como especificar configurações de sampler.

Um UV-map normalmente é gerado no programa de modelagem 3D e armazenado na malha. As coordenadas de textura de cada vértice são fornecidas ao vertex shader como um atributo. Uma variável varying é então usada para encontrar a coordenada UV de cada fragmento, interpolada a partir dos valores dos vértices.

Por exemplo, definir um varying para um valor de cor RGB vec3 em cada canto de um triângulo interpolará as cores por toda a forma. De modo semelhante, definir coordenadas de consulta de mapa de textura (ou coordenadas UV) em cada vértice de um retângulo permite que o fragment shader consulte valores de cor da textura para toda a área da forma.

Como a engine Defold suporta várias plataformas e APIs gráficas, precisa ser simples para desenvolvedores escrever shaders que funcionem em todos os lugares. O pipeline de assets consegue isso principalmente de duas formas (chamadas de shader pipelines daqui em diante):
A partir do Defold 1.9.2, recomenda-se escrever shaders que usem o novo pipeline. Para isso, a maioria dos shaders precisa ser migrada para shaders escritos em pelo menos a versão 140 (OpenGL 3.1). Para migrar um shader, certifique-se de que estes requisitos sejam atendidos:
Coloque pelo menos #version 140 no topo do shader:
#version 140
É assim que o pipeline de shader é escolhido no processo de build, por isso você ainda pode usar shaders antigos. Se nenhum pré-processador de versão for encontrado, o Defold usará o pipeline legado.
Em vertex shaders, substitua a palavra-chave attribute por in:
// em vez de:
// attribute vec4 position;
// faça:
in vec4 position;
Observação: fragment shaders (e compute shaders) não recebem entradas de vértice.
Em vertex shaders, varyings devem receber o prefixo out. Em fragment shaders, varyings se tornam in:
// Em um vertex shader, em vez de:
// varying vec4 var_color;
// faça:
out vec4 var_color;
// Em um fragment shader, em vez de:
// varying vec4 var_color;
// faça:
in vec4 var_color;
Tipos uniform opacos (samplers, images, atomics, SSBOs) não precisam de migração; você pode usá-los como usa hoje:
uniform sampler2D my_texture;
uniform image2D my_image;
Para tipos uniform não opacos, você precisa colocá-los em um uniform block. Um uniform block é simplesmente uma coleção de variáveis uniform, declarada com a palavra-chave uniform:
uniform vertex_inputs
{
mat4 mtx_world;
mat4 mtx_proj;
mat4 mtx_view;
mat4 mtx_normal;
...
};
void main()
{
// Membros individuais do uniform block podem ser usados diretamente
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position, 1.0);
}
Todos os membros no uniform block são expostos a materiais e componentes como constantes individuais. Nenhuma migração é necessária para usar buffers de constantes de renderização, ou go.set e go.get.
Em fragment shaders, gl_FragColor está obsoleto a partir da versão 140. Use out em vez disso:
// em vez de:
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// faça:
out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
}
Funções específicas de amostragem de textura, como texture2D e texture2DArray, não existem mais. Em vez disso, use apenas a função texture:
uniform sampler2D my_texture;
uniform sampler2DArray my_texture_array;
// em vez de:
// vec4 sampler_2d = texture2D(my_texture, uv);
// vec4 sampler_2d_array = texture2DArray(my_texture_array, vec3(uv, slice));
// faça:
vec4 sampler_2d = texture(my_texture, uv);
vec4 sampler_2d_array = texture(my_texture_array, vec3(uv, slice));
Definir precisão explícita para variáveis, entradas, saídas e assim por diante era necessário anteriormente para compatibilidade com contextos OpenGL ES. Isso não é mais necessário; a precisão agora é definida automaticamente para plataformas que a suportam.
Como exemplo final onde todas essas regras são aplicadas, aqui estão os shaders de sprite integrados convertidos para o novo formato:
#version 140
uniform vx_uniforms
{
mat4 view_proj;
};
// posições estão em espaço de 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()
{
// Pré-multiplica alpha, pois todas as texturas de runtime já são assim
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
color_out = texture(texture_sampler, var_texcoord0.xy) * tint_pm;
}
Shaders no Defold suportam incluir código-fonte de arquivos dentro do projeto que tenham a extensão .glsl. Para incluir um arquivo glsl a partir de um shader, use a pragma #include com aspas duplas ou colchetes. Includes precisam ter caminhos relativos ao projeto ou um caminho relativo ao arquivo que os inclui:
// No arquivo /main/my-shader.fp
// Caminho absoluto
#include "/main/my-snippet.glsl"
// O arquivo está na mesma pasta
#include "my-snippet.glsl"
// O arquivo está em uma subpasta no mesmo nível de 'my-shader'
#include "sub-folder/my-snippet.glsl"
// O arquivo está em uma subpasta no diretório pai, isto é /some-other-folder/my-snippet.glsl
#include "../some-other-folder/my-snippet.glsl"
// O arquivo está no diretório pai, isto é /root-level-snippet.glsl
#include "../root-level-snippet.glsl"
Há algumas ressalvas sobre como includes são encontrados:
/ inicialconst float #include "my-float-name.glsl" = 1.0 não funcionaráTrechos podem eles mesmos incluir outros arquivos .glsl, o que significa que o shader final produzido pode potencialmente incluir o mesmo código várias vezes. Dependendo do conteúdo dos arquivos, você pode acabar com problemas de compilação por ter os mesmos símbolos declarados mais de uma vez. Para evitar isso, você pode usar header guards, um conceito comum em várias linguagens de programação. Exemplo:
// Em my-shader.vs
#include "math-functions.glsl"
#include "pi.glsl"
// Em math-functions.glsl
#include "pi.glsl"
// Em pi.glsl
const float PI = 3.14159265359;
Neste exemplo, a constante PI será definida duas vezes, o que causará erros de compilador ao executar o projeto. Em vez disso, você deve proteger o conteúdo com header guards:
// Em pi.glsl
#ifndef PI_GLSL_H
#define PI_GLSL_H
const float PI = 3.14159265359;
#endif // PI_GLSL_H
O código de pi.glsl será expandido duas vezes em my-shader.vs, mas como você o envolveu em header guards, o símbolo PI será definido apenas uma vez e o shader compilará com sucesso.
No entanto, isso nem sempre é estritamente necessário, dependendo do caso de uso. Se, em vez disso, você quiser reutilizar código localmente em uma função ou em outro lugar onde não precisa que os valores estejam disponíveis globalmente no código do shader, provavelmente não deve usar header guards. Exemplo:
// Em red-color.glsl
vec3 my_red_color = vec3(1.0, 0.0, 0.0);
// Em 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;
}
Quando shaders são renderizados no viewport do Defold Editor, uma definição de pré-processador EDITOR fica disponível. Isso permite escrever código de shader que se comporta de forma diferente ao rodar no editor e ao rodar na engine real do jogo.
Isso é particularmente útil para:
Use a diretiva de pré-processador #ifdef EDITOR para compilar condicionalmente código que deve rodar apenas no editor:
#ifdef EDITOR
// Este código só será executado quando o shader for renderizado no Defold Editor
color_out = vec4(1.0, 0.0, 1.0, 1.0); // Cor magenta para pré-visualização no editor
#else
// Este código será executado ao rodar no jogo
color_out = texture(texture_sampler, var_texcoord0) * tint_pm;
#endif
Antes de acabar na tela, os dados que você cria para seu jogo passam por uma série de etapas:

Todos os componentes visuais (sprites, nós de GUI, efeitos de partículas ou modelos) consistem em vértices, pontos no mundo 3D que descrevem a forma do componente. O bom disso é que é possível ver a forma de qualquer ângulo e distância. O trabalho do programa de vertex shader é pegar um único vértice e traduzi-lo para uma posição no viewport para que a forma possa aparecer na tela. Para uma forma com 4 vértices, o programa de vertex shader roda 4 vezes, cada execução em paralelo.

A entrada do programa é a posição do vértice (e outros dados de atributo associados ao vértice), e a saída é uma nova posição de vértice (gl_Position), além de quaisquer variáveis varying que devem ser interpoladas para cada fragmento.
O programa de vertex shader mais simples apenas define a posição da saída para um vértice zero (o que não é muito útil):
void main()
{
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
Um exemplo mais completo é o 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 já está transformado em espaço de mundo. texcoord0 contém a coordenada UV do vértice.gl_Position é definido como a posição de saída do vértice atual no espaço de projeção. Esse valor tem 4 componentes: x, y, z e w. O componente w é usado para calcular interpolação correta em perspectiva. Esse valor normalmente é 1.0 para cada vértice antes de qualquer matriz de transformação ser aplicada.Após o vertex shading, a forma na tela do componente é decidida: formas primitivas são geradas e rasterizadas, o que significa que o hardware gráfico divide cada forma em fragmentos, ou pixels. Ele então executa o programa de fragment shader, uma vez para cada fragmento. Para uma imagem na tela com tamanho de 16x24 pixels, o programa roda 384 vezes, cada execução em paralelo.

A entrada do programa é tudo o que o pipeline de renderização e o vertex shader enviam, normalmente as coordenadas uv do fragmento, cores de tint etc. A saída é a cor final do pixel (gl_FragColor).
O programa de fragment shader mais simples apenas define a cor de cada pixel como preto (novamente, não é um programa muito útil):
void main()
{
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
Novamente, um exemplo mais completo é o 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]
}
sampler2D é declarada. O sampler, junto com as coordenadas de textura interpoladas, é usado para realizar uma consulta de textura para que o sprite receba textura corretamente. Como este é um sprite, a engine atribuirá este sampler à imagem definida na propriedade Image do sprite.CONSTANT_TYPE_USER é definida no material e declarada como uniform. Seu valor é usado para permitir tingimento de cor no sprite. O padrão é branco puro.gl_FragColor é definido para a cor de saída do fragmento: a cor difusa da textura multiplicada pelo valor de tint.O valor de fragmento resultante então passa por testes. Um teste comum é o depth test, em que o valor de profundidade do fragmento é comparado ao valor do depth buffer para o pixel que está sendo testado. Dependendo do teste, o fragmento pode ser descartado ou um novo valor é escrito no depth buffer. Um uso comum desse teste é permitir que gráficos mais próximos da câmera bloqueiem gráficos mais distantes.
Se o teste concluir que o fragmento deve ser escrito no frame buffer, ele será misturado com os dados de pixel já presentes no buffer. Parâmetros de blending definidos no script de renderização permitem que a cor de origem (o valor escrito pelo fragment shader) e a cor de destino (a cor da imagem no framebuffer) sejam combinadas de várias formas. Um uso comum de blending é permitir a renderização de objetos transparentes.
Shadertoy contém um número enorme de shaders contribuídos por usuários. É uma ótima fonte de inspiração onde você pode aprender sobre várias técnicas de shading. Muitos shaders apresentados no site podem ser portados para o Defold com muito pouco trabalho. O tutorial Shadertoy percorre as etapas de conversão de um shader existente para o Defold.
O tutorial Grading mostra como criar um efeito de gradação de cor em tela cheia usando texturas de tabela de consulta de cores para a gradação.
The Book of Shaders ensinará como usar e integrar shaders aos seus projetos, melhorando o desempenho e a qualidade gráfica deles.