Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Linguagem de shading
Introdução
Godot usa uma linguagem de shading semelhante à GLSL ES 3.0. A maioria dos tipos de dados e funções são suportados e os poucos restantes provavelmente serão adicionados com o tempo.
Se você já está familiarizado com GLSL, o Godot Shader Migration Guide é um recurso que o ajudará na transição de GLSL regular para a linguagem de shaders do Godot.
Tipos de dados
A maioria dos tipos de dados de GLSL ES 3.0 são suportados:
Tipo |
Descrição |
|---|---|
void |
Tipo de dados Void, útil apenas para funções que não retornam nada. |
bool |
Tipo de dados booleano, só podem conter |
bvec2 |
Vetor de dois componentes de booleanos. |
bvec3 |
Vetor de três componentes de booleanos. |
bvec4 |
Vetor de quatro componentes de booleanos. |
int |
Inteiro escalar com sinal de 32 bits. |
ivec2 |
Vetor de dois componentes de inteiros com sinal. |
ivec3 |
Vetor de três componentes de inteiros com sinal. |
ivec4 |
Vetor de quatro componentes de inteiros com sinal. |
uint |
Inteiro escalar sem sinal; não pode conter números negativos. |
uvec2 |
Vetor de dois componentes de inteiros sem sinal. |
uvec3 |
Vetor de três componentes de inteiros sem sinal. |
uvec4 |
Vetor de quatro componentes de inteiros sem sinal. |
float |
Escalar de ponto flutuante de 32 bits. |
vec2 |
Vetor de dois componentes de valores de ponto flutuante. |
vec3 |
Vetor de três componentes de valores de ponto flutuante. |
vec4 |
Vetor de quatro componentes de valores de ponto flutuante. |
mat2 |
Matriz 2x2, em ordem majoritária de coluna (column major order). |
mat3 |
Matriz 3x3, em ordem majoritária de coluna (column major order). |
mat4 |
Matriz 4x4, em ordem majoritária de coluna (column major order). |
sampler2D |
Tipo sampler para vincular texturas 2D, que são lidas como float. |
isampler2D |
Tipo sampler para vincular texturas 2D, que são lidas como inteiro com sinal. |
usampler2D |
Tipo sampler para vincular texturas 2D, que são lidas como inteiro sem sinal. |
sampler2DArray |
Tipo sampler para vincular arrays de texturas 2D, que são lidas como float. |
isampler2DArray |
Tipo sampler para vincular arrays de texturas 2D, que são lidas como inteiro com sinal. |
usampler2DArray |
Tipo sampler para vincular arrays de texturas 2D, que são lidas como inteiro sem sinal. |
sampler3D |
Tipo sampler para vincular texturas 3D, que são lidas como float. |
isampler3D |
Tipo sampler para vincular texturas 3D, que são lidas como inteiro com sinal. |
usampler3D |
Tipo sampler para vincular texturas 3D, que são lidas como inteiro sem sinal. |
samplerCube |
Tipo sampler para vincular Cubemaps, que são lidas como float. |
samplerCubeArray |
Tipo sampler para vincular arrays de Cubemaps, que são lidas como float. Suportado apenas em Forward+ e Mobile, não em Compatibility. |
samplerExternalOES |
Tipo sampler externo. Suportado apenas na plataforma Compatibility/Android. |
Esses tipos também podem ser colocados dentro de arrays ou structs, que também são utilizáveis como parâmetros de função ou valores de retorno. Arrays podem ser usados como uniforms, mas structs não.
Aviso
Variáveis locais não são inicializadas com um valor padrão como 0.0. Se você usar uma variável sem atribuí-la primeiro, ela conterá o valor que já estivesse presente naquele local da memória, e falhas visuais imprevisíveis aparecerão. No entanto, uniforms e varyings são inicializados com um valor padrão.
Conversão
Assim como no GLSL ES 3.0, a conversão implícita (implicit casting) entre escalares e vetores do mesmo tamanho, mas de tipos diferentes, não é permitida. A conversão de tipos de tamanhos diferentes também não é permitida. A conversão deve ser feita explicitamente através de construtores.
Exemplo:
float a = 2; // invalid
float a = 2.0; // valid
float a = float(2); // valid
As constantes inteiras padrão possuem sinal, portanto, a conversão é sempre necessária para converter para sem sinal:
int a = 2; // valid
uint a = 2; // invalid
uint a = uint(2); // valid
Membros
Os membros escalares individuais dos tipos vetoriais são acessados através dos membros "x", "y", "z" e "w". Alternativamente, usar "r", "g", "b" e "a" também funciona e é equivalente. Use o que melhor se adaptar às suas necessidades.
Para matrizes, use a sintaxe de indexação m[coluna][linha] para acessar cada escalar, ou m[coluna] para acessar um vetor pelo índice da coluna. Por exemplo, para acessar o componente y da translação de uma matriz de transformação mat4 (4ª coluna, 2ª linha), você usa m[3][1] ou m[3].y.
Construindo
A construção de tipos vetoriais deve sempre passar:
// The required amount of scalars
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
// Complementary vectors and/or scalars
vec4 a = vec4(vec2(0.0, 1.0), vec2(2.0, 3.0));
vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0);
// A single scalar for the whole vector
vec4 a = vec4(0.0);
A construção de tipos de matriz requer vetores da mesma dimensão que a matriz, interpretados como colunas. Você também pode construir uma matriz diagonal usando a sintaxe matx(float). Consequentemente, mat4(1.0) é uma matriz de identidade.
mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0));
mat4 identity = mat4(1.0);
Matrizes também podem ser construídas a partir de uma matriz de outra dimensão. Existem duas regras:
1. If a larger matrix is constructed from a smaller matrix, the additional rows and columns are set to the values they would have in an identity matrix. 1. If a smaller matrix is constructed from a larger matrix, the top, left submatrix of the larger matrix is used.
mat3 basis = mat3(MODEL_MATRIX);
mat4 m4 = mat4(basis);
mat2 m2 = mat2(m4);
Swizzling
É possível obter qualquer combinação de componentes em qualquer ordem, desde que o resultado seja outro tipo de vetor (ou escalar). Isso é mais fácil de mostrar do que de explicar:
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
vec3 b = a.rgb; // Creates a vec3 with vec4 components.
vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component.
vec3 b = a.bgr; // "b" will be vec3(2.0, 1.0, 0.0).
vec3 b = a.xyz; // Also rgba, xyzw are equivalent.
vec3 b = a.stp; // And stpq (for texture coordinates).
float c = b.w; // Invalid, because "w" is not present in vec3 b.
vec3 c = b.xrt; // Invalid, mixing different styles is forbidden.
b.rrr = a.rgb; // Invalid, assignment with duplication.
b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa.
Precisão
É possível adicionar modificadores de precisão aos tipos de dados; use-os para uniforms, variáveis, argumentos e varyings:
lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1
mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float
highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (32 bit default)
Usar uma precisão menor para algumas operações pode acelerar a matemática envolvida (ao custo de menos precisão). Isso raramente é necessário na função processadora de vértice (onde a precisão total é necessária na maioria das vezes), mas costuma ser útil no processador de fragmento.
Algumas arquiteturas (principalmente mobile) podem se beneficiar significativamente disso, mas há desvantagens, como o custo adicional de processamento da conversão entre precisões. Consulte a documentação da arquitetura alvo para obter mais informações. Em muitos caso, os drivers mobile causam comportamentos inconsistentes ou inesperados, e é melhor evitar especificar a precisão, a menos que seja necessário.
Arrays
Arrays são recipientes para múltiplas variáveis de um tipo semelhante.
Arrays locais
Arrays locais são declarados em funções. Eles podem usar todos os tipos de dados permitidos, exceto samplers. A declaração do array segue uma sintaxe no estilo C: [const] + [precision] + typename + identifier + [tamanho do array].
void fragment() {
float arr[3];
}
Eles podem ser inicializados no início desta forma:
float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor
int int_arr[3] = int[] (2, 1, 0); // second constructor
vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor
bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count
Você pode declarar múltiplos arrays (mesmo com tamanhos diferentes) em uma única expressão:
float a[3] = float[3] (1.0, 0.5, 0.0),
b[2] = { 1.0, 0.5 },
c[] = { 0.7 },
d = 0.0,
e[5];
Para acessar um elemento do array, use a sintaxe de indexação:
float arr[3];
arr[0] = 1.0; // setter
COLOR.r = arr[0]; // getter
Os arrays também possuem uma função nativa .length() (não deve ser confundida com a função nativa length()). Ela não aceita parâmetros e retornará o tamanho do array.
float arr[] = { 0.0, 1.0, 0.5, -1.0 };
for (int i = 0; i < arr.length(); i++) {
// ...
}
Nota
Se você usar um índice abaixo de 0 ou maior que o tamanho do array - o shader irá travar e interromper a renderização. Para evitar isso, use as funções length(), if ou clamp() para garantir que o índice esteja entre 0 e o tamanho do array. Sempre teste e verifique seu código com cuidado. Se você passar uma expressão constante ou um número, o editor verificará seus limites para evitar esse travamento.
Arrays globais
You can declare arrays in global scope as either const or uniform:
shader_type spatial;
const lowp vec3 v[1] = lowp vec3[1] ( vec3(0, 0, 1) );
uniform lowp vec3 w[1];
void fragment() {
ALBEDO = v[0] + w[0];
}
Nota
Os arrays globais usam a mesma sintaxe que os arrays locais, exceto por um const ou uniform adicionado à sua declaração. Observe que arrays uniform não podem ter um valor padrão.
Constantes
Use a palavra-chave const antes da declaração da variável para torná-la imutável, o que significa que ela não poderá ser modificada. Todos os tipos básicos, exceto samplers, podem ser declarados como constantes. Acessar e usar um valor constante é ligeiramente mais rápido do que usar um uniform. As constantes devem ser inicializadas em sua declaração.
const vec2 a = vec2(0.0, 1.0);
vec2 b;
a = b; // invalid
b = a; // valid
Constants cannot be modified and additionally cannot have hints, but multiple of them (if they have the same type) can be declared in a single expression.
const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2);
Semelhante às variáveis, os arrays também podem ser declarados com const.
const float arr[] = { 1.0, 0.5, 0.0 };
arr[0] = 1.0; // invalid
COLOR.r = arr[0]; // valid
As constantes podem ser declaradas tanto globalmente (fora de qualquer função) quanto localmente (dentro de uma função). Constantes globais são úteis quando você deseja ter acesso a um valor em todo o seu shader que não precisa ser modificado. Assim como os uniforms, as constantes globais são compartilhadas entre todos os estágios do shader, mas não são acessíveis fora dele.
shader_type spatial;
const float GOLDEN_RATIO = 1.618033988749894;
As constantes do tipo float devem ser inicializadas usando a notação de ponto . após a parte decimal ou usando a notação científica. O sufixo opcional f também é suportado.
float a = 1.0;
float b = 1.0f; // same, using suffix for clarity
float c = 1e-1; // gives 0.1 by using the scientific notation
As constantes do tipo uint (inteiro sem sinal) devem ter um sufixo u para diferenciá-las dos inteiros com sinal. Alternativamente, isso pode ser feito usando a função de conversão nativa uint(x).
uint a = 1u;
uint b = uint(1);
Structs (Estruturas)
Structs são tipos compostos que podem ser usados para uma melhor abstração do código do shader. Você pode declará-los no escopo global desta forma:
struct PointLight {
vec3 position;
vec3 color;
float intensity;
};
Após a declaração, você pode instanciá-los e inicializá-los assim:
void fragment()
{
PointLight light;
light.position = vec3(0.0);
light.color = vec3(1.0, 0.0, 0.0);
light.intensity = 0.5;
}
Ou usar o construtor da struct para o mesmo propósito:
PointLight light = PointLight(vec3(0.0), vec3(1.0, 0.0, 0.0), 0.5);
Structs may contain other struct or array, you can also instantiate them as global constant:
shader_type spatial;
...
struct Scene {
PointLight lights[2];
};
const Scene scene = Scene(PointLight[2](PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0)));
void fragment()
{
ALBEDO = scene.lights[0].color;
}
Você também pode passá-los para funções:
shader_type canvas_item;
...
Scene construct_scene(PointLight light1, PointLight light2) {
return Scene({light1, light2});
}
void fragment()
{
COLOR.rgb = construct_scene(PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 1.0), 1.0)).lights[0].color;
}
Operadores
A linguagem de sombreamento do Godot suporta o mesmo conjunto de operadores que o GLSL ES 3.0. Abaixo está a lista deles em ordem de precedência:
Precedência |
Classe |
Operador |
1 (mais alto) |
agrupamento entre parênteses |
() |
2 |
unário |
+, -, !, ~ |
3 |
multiplicativo |
/, *, % |
4 |
aditivo |
+, - |
5 |
deslocamento de bits (bit-wise shift) |
<<, >> |
6 |
relacional |
<, >, <=, >= |
7 |
igualdade |
==, != |
8 |
Operador bit a bit AND |
& |
9 |
OU exclusivo bit a bit (bit-wise exclusive OR) |
^ |
10 |
OU inclusivo bit a bit (bit-wise inclusive OR) |
| |
11 |
AND lógico |
&& |
12 (mais baixo) |
OU inclusivo lógico |
|| |
Nota
A maioria dos operadores que aceitam vetores ou matrizes (multiplicação, divisão, etc.) operam componente por componente (component-wise), o que significa que a função é aplicada ao primeiro valor de cada vetor e depois ao segundo valor de cada vetor, etc. Alguns exemplos:
Operação |
Operação Escalar Equivalente |
|---|---|
|
|
|
|
|
|
A Especificação da Linguagem GLSL diz na seção 5.10 Operações de Vetores e Matrizes:
Com poucas exceções, as operações são componente por componente. Normalmente, quando um operador atua em um vetor ou matriz, ele está operando independentemente em cada componente do vetor ou matriz, de maneira componente por componente. [...] As exceções são matriz multiplicada por vetor, vetor multiplicado por matriz e matriz multiplicada por matriz. Estas não operam componente por componente, mas realizam a multiplicação algébrica linear correta.
Controle de fluxo
A linguagem de sombreamento do Godot suporta os tipos mais comuns de controle de fluxo:
// `if`, `else if` and `else`.
if (cond) {
} else if (other_cond) {
} else {
}
// Ternary operator.
// This is an expression that behaves like `if`/`else` and returns the value.
// If `cond` evaluates to `true`, `result` will be `9`.
// Otherwise, `result` will be `5`.
int result = cond ? 9 : 5;
// `switch`.
switch (i) { // `i` should be a signed integer expression.
case -1:
break;
case 0:
return; // `break` or `return` to avoid running the next `case`.
case 1: // Fallthrough (no `break` or `return`): will run the next `case`.
case 2:
break;
//...
default: // Only run if no `case` above matches. Optional.
break;
}
// `for` loop. Best used when the number of elements to iterate on
// is known in advance.
for (int i = 0; i < 10; i++) {
}
// `while` loop. Best used when the number of elements to iterate on
// is not known in advance.
while (cond) {
}
// `do while`. Like `while`, but always runs at least once even if `cond`
// never evaluates to `true`.
do {
} while (cond);
Tenha em mente que nas GPUs modernas, um loop infinito pode existir e pode congelar sua aplicação (incluindo o editor). O Godot não pode proteger você disso, então tome cuidado para não cometer esse erro!
Além disso, ao comparar valores de ponto flutuante com um número, certifique-se de compará-los com um intervalo (range) em vez de um número exato.
Uma comparação como if (value == 0.3) pode não ser avaliada como true. A matemática de ponto flutuante é frequentemente aproximada e pode desafiar as expectativas. Ela também pode se comportar de maneira diferente dependendo do hardware.
Não faça isso.
float value = 0.1 + 0.2;
// May not evaluate to `true`!
if (value == 0.3) {
// ...
}
Em vez disso, sempre realize uma comparação de intervalo com um valor epsilon. Quanto maior o número de ponto flutuante (e menos preciso o número de ponto flutuante), maior deve ser o valor epsilon.
const float EPSILON = 0.0001;
if (value >= 0.3 - EPSILON && value <= 0.3 + EPSILON) {
// ...
}
Veja floating-point-gui.de para mais informações.
Descartando
As funções fragment, light e personalizadas (chamadas a partir de fragment ou light) podem usar a palavra-chave discard. Se usada, o fragmento é descartado e nada é gravado.
Cuidado, pois o discard possui um custo de desempenho quando utilizado, já que impedirá que o pré-passo de profundidade (depth prepass) seja eficaz em quaisquer superfícies que utilizem o shader. Além disso, um pixel descartado ainda precisa ser renderizado no shader de vértice, o que significa que um shader que usa discard em todos os seus pixels ainda é mais custoso de renderizar em comparação com não renderizar nenhum objeto em primeiro lugar.
Funções
É possível definir funções em um shader do Godot. Elas usam a seguinte sintaxe:
ret_type func_name(args) {
return ret_type; // if returning a value
}
// a more specific example:
int sum2(int a, int b) {
return a + b;
}
Você só pode usar funções que tenham sido definidas acima (mais acima no editor) da função a partir da qual você as está chamando. Redefinir uma função que já foi definida acima (ou usar o nome de uma função embutida) causará um erro.
Os argumentos da função podem ter modificadores (qualifiers) especiais:
in: Significa que o argumento é apenas para leitura (padrão).
out: Significa que o argumento é apenas para escrita.
inout: Significa que o argumento é totalmente passado por referência.
const: Significa que o argumento é uma constante e não pode ser alterado, podendo ser combinado com o modificador in.
Exemplo abaixo:
void sum2(int a, int b, inout int result) {
result = a + b;
}
A sobrecarga de funções é suportada. Você pode definir várias funções com o mesmo nome, mas com argumentos diferentes. Observe que a conversão implícita de tipos em chamadas de funções sobrecarregadas não é permitida, como de int para float (1 para 1.0).
vec3 get_color(int t) {
return vec3(1, 0, 0); // Red color.
}
vec3 get_color(float t) {
return vec3(0, 1, 0); // Green color.
}
void fragment() {
vec3 red = get_color(1);
vec3 green = get_color(1.0);
}
Variações
Para enviar dados do processador de vértice (vertex) para o de fragmento (fragment) ou de luz (light), são usadas varyings. Elas são definidas para cada vértice primitivo no processador de vértice, e o valor é interpolado para cada pixel no processador de fragmento.
shader_type spatial;
varying vec3 some_color;
void vertex() {
some_color = NORMAL; // Make the normal the color.
}
void fragment() {
ALBEDO = some_color;
}
void light() {
DIFFUSE_LIGHT = some_color * 100; // optionally
}
Uma varying também pode ser um array:
shader_type spatial;
varying float var_arr[3];
void vertex() {
var_arr[0] = 1.0;
var_arr[1] = 0.0;
}
void fragment() {
ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color
}
Também é possível enviar dados do processador de fragmento para o de luz usando a palavra-chave varying. Para fazer isso, você pode atribuir o valor na função fragment e usá-lo posteriormente na função light.
shader_type spatial;
varying vec3 some_light;
void fragment() {
some_light = ALBEDO * 100.0; // Make a shining light.
}
void light() {
DIFFUSE_LIGHT = some_light;
}
Observe que uma varying não pode ser atribuída dentro de funções personalizadas ou em uma função do processador de luz, como:
shader_type spatial;
varying float test;
void foo() {
test = 0.0; // Error.
}
void vertex() {
test = 0.0;
}
void light() {
test = 0.0; // Error too.
}
Essa limitação foi introduzida para evitar o uso incorreto antes da inicialização.
Modificadores de interpolação
Certos valores são interpolados durante o pipeline de sombreamento. Você pode modificar como essas interpolações são feitas usando modificadores de interpolação.
shader_type spatial;
varying flat vec3 our_color;
void vertex() {
our_color = COLOR.rgb;
}
void fragment() {
ALBEDO = our_color;
}
Existem dois modificadores de interpolação possíveis:
Qualificador |
Descrição |
|---|---|
flat |
O valor não é interpolado. |
suave |
O valor é interpolado de maneira corrigida por perspectiva (perspective-correct). Este é o padrão. |
Uniforms
Passar valores para os shaders é possível com uniforms, que são definidos no escopo global do shader, fora das funções. Quando um shader é posteriormente atribuído a um material, os uniforms aparecerão como parâmetros editáveis no inspetor do material. Os uniforms não podem ser escritos de dentro do shader. Qualquer tipo de dados, exceto void, pode ser um uniform.
shader_type spatial;
uniform float some_value;
uniform vec3 colors[3];
Você pode definir uniforms no editor, no inspetor do material. Alternativamente, você pode defini-los via código.
Dicas de uniforme (Uniform hints)
O Godot fornece dicas (hints) de uniform opcionais para fazer o compilador entender para que o uniform é usado e como o editor deve permitir que os usuários o modifiquem.
shader_type spatial;
uniform vec4 color : source_color;
uniform float amount : hint_range(0, 1);
uniform vec4 other_color : source_color = vec4(1.0); // Default values go after the hint.
uniform sampler2D image : source_color;
Os uniforms também podem receber valores padrão:
shader_type spatial;
uniform vec4 some_vector = vec4(0.0);
uniform vec4 some_color : source_color = vec4(1.0);
Observe que ao adicionar um valor padrão e uma dica, o valor padrão fica depois da dica.
Lista completa de dicas de uniforms abaixo:
Tipo |
Dica |
Descrição |
|---|---|---|
vec3, vec4 |
source_color |
Usado como cor. |
int |
hint_enum("String1", "String2") |
Exibe a entrada int como um widget de menu suspenso (dropdown) no editor. |
int, float |
hint_range(min, max[, step]) |
Restrito a valores dentro de um intervalo (com min/max/passo). |
sampler2D |
source_color |
Usado como cor albedo. |
sampler2D |
hint_normal |
Usado como normalmap. |
sampler2D |
hint_default_white |
Como valor ou cor albedo, o padrão é branco opaco. |
sampler2D |
hint_default_black |
Como valor ou cor albedo, o padrão é preto opaco. |
sampler2D |
hint_default_transparent |
Como valor ou cor albedo, o padrão é preto transparente. |
sampler2D |
hint_anisotropy |
Como flowmap, o padrão é para a direita. |
sampler2D |
hint_roughness[_r, _g, _b, _a, _normal, _gray] |
Usado para limitador de rugosidade (roughness limiter) na importação (tenta reduzir o aliasing especular). |
sampler2D |
filter[_nearest, _linear][_mipmap][_anisotropic] |
Ativa a filtragem de textura especificada. |
sampler2D |
repeat[_enable, _disable] |
Ativa a repetição de textura. |
sampler2D |
hint_screen_texture |
A textura é a textura da tela (screen texture). |
sampler2D |
hint_depth_texture |
A textura é a textura de profundidade (depth texture). |
sampler2D |
hint_normal_roughness_texture |
A textura é a textura de rugosidade normal (suportada apenas no Forward+). |
Usando hint_enum
Você pode acessar valores int como um widget de menu suspenso legível usando o uniform hint_enum:
uniform int noise_type : hint_enum("OpenSimplex2", "Cellular", "Perlin", "Value") = 0;
Você pode atribuir valores explícitos ao uniform hint_enum usando a sintaxe de dois pontos semelhante ao GDScript:
uniform int character_speed: hint_enum("Slow:30", "Average:60", "Very Fast:200") = 60;
O valor será armazenado como um inteiro, correspondente ao índice da opção selecionada (ou seja, 0, 1 ou 2) ou ao valor atribuído pela sintaxe de dois pontos (ou seja, 30, 60 ou 200). Ao definir o valor com set_shader_parameter(), você deve usar o valor inteiro, não o nome em String.
Usando source_color
Qualquer textura que contenha dados de cor sRGB requer uma dica source_color para ser amostrada corretamente. Isso ocorre porque o Godot renderiza em espaço de cor linear, mas algumas texturas contêm dados de cor sRGB. Se essa dica não for usada, a textura parecerá desbotada.
Texturas de albedo e de cor normalmente devem ter a dica source_color. Texturas de normais, rugosidade (roughness), metálico (metallic) e altura (height) normalmente não precisam da dica source_color.
O uso da dica source_color é obrigatório nos renderizadores Forward+ e Mobile, e em shaders canvas_item quando o HDR 2D está ativado. A dica source_color é opcional para o renderizador Compatibility e para shaders canvas_item se o HDR 2D estiver desativado. No entanto, é recomendado sempre usar a dica source_color, porque ela funciona mesmo se você mudar de renderizador ou desativar o HDR 2D.
Grupos de uniformes
Para agrupar múltiplos uniforms em uma seção no inspetor, você pode usar a palavra-chave group_uniform da seguinte forma:
group_uniforms MyGroup;
uniform sampler2D test;
Você pode fechar o grupo usando:
group_uniforms;
A sintaxe também suporta subgrupos (não é obrigatório declarar o grupo base antes deste):
group_uniforms MyGroup.MySubgroup;
Uniformes globais
Às vezes, você quer modificar um parâmetro em muitos shaders diferentes ao mesmo tempo. Com um uniform regular, isso exige muito trabalho, pois todos esses shaders precisam ser rastreados e o uniform precisa ser definido para cada um deles. Os uniforms globais permitem criar e atualizar uniforms que estarão disponíveis em todos os shaders, em todos os tipos de shader (canvas_item, spatial, particles, sky e fog).
Os uniforms globais são especialmente úteis para efeitos ambientais que afetam muitos objetos em uma cena, como fazer a folhagem se curvar quando o jogador está por perto, ou fazer os objetos se moverem com o vento.
Nota
Os uniforms globais não são a mesma coisa que o escopo global de um shader individual. Enquanto os uniforms regulares são definidos fora das funções do shader e, portanto, estão no escopo global daquele shader, os uniforms globais são globais para todos os shaders de todo o projeto (mas, dentro de cada shader, também ficam no escopo global).
Para criar um uniform global, abra as Configurações do Projeto e vá para a aba Shader Globals. Especifique um nome para o uniform (case-sensitive) e um tipo, depois clique em Add no canto superior direito da janela de diálogo. Você poderá então editar o valor atribuído ao uniform clicando no valor na lista de uniforms:
Adicionando um uniform global na aba Shader Globals das Configurações do Projeto
Depois de criar um uniform global, você pode usá-lo em um shader da seguinte forma:
shader_type canvas_item;
global uniform vec4 my_color;
void fragment() {
COLOR = my_color.rgb;
}
Observe que o uniform global deve existir nas Configurações do Projeto no momento em que o shader é salvo, caso contrário a compilação falhará. Embora você possa atribuir um valor padrão usando global uniform vec4 my_color = ... no código do shader, ele será ignorado, pois o uniform global sempre deve ser definido nas Configurações do Projeto de qualquer maneira.
Para alterar o valor de um uniform global em tempo de execução, use o método RenderingServer.global_shader_parameter_set em um script:
RenderingServer.global_shader_parameter_set("my_color", Color(0.3, 0.6, 1.0))
A atribuição de valores de uniforms globais pode ser feita quantas vezes for desejado sem impactar o desempenho, pois a definição dos dados não requer sincronização entre a CPU e a GPU.
Você também pode adicionar ou remover uniformes globais em tempo de execução:
RenderingServer.global_shader_parameter_add("my_color", RenderingServer.GLOBAL_VAR_TYPE_COLOR, Color(0.3, 0.6, 1.0))
RenderingServer.global_shader_parameter_remove("my_color")
Adicionar ou remover uniforms globais em tempo de execução possui um custo de desempenho, embora não seja tão acentuado quanto obter os valores de uniforms globais a partir de um script (veja o aviso abaixo).
Aviso
Embora você possa consultar o valor de um uniform global em tempo de execução em um script usando RenderingServer.global_shader_parameter_get("nome_do_uniform"), isso gera uma grande penalidade de desempenho, pois a thread de renderização precisa se sincronizar com a thread que fez a chamada.
Portanto, não é recomendado ler valores de uniforms de shader globais continuamente em um script. Se precisar ler os valores em um script após defini-los, considere a criação de um autoload onde você armazena os valores que precisa consultar ao mesmo tempo em que os define como uniforms globais.
Uniforms por instância (Per-instance uniforms)
Nota
Os uniforms por instância estão disponíveis tanto em shaders canvas_item (2D) quanto spatial (3D).
Às vezes, você quer modificar um parâmetro em cada nó que utiliza o material. Como exemplo, em uma floresta cheia de árvores, você pode querer que cada árvore tenha uma cor ligeiramente diferente que seja editável manualmente. Sem os uniforms por instância, isso exigiria a criação de um material exclusivo para cada árvore (cada um com uma tonalidade ligeiramente diferente). Isso torna o gerenciamento de materiais mais complexo e também gera um custo de desempenho devido à cena exigir mais instâncias de materiais exclusivos. Cores de vértices (vertex colors) também poderiam ser usadas aqui, mas exigiriam a criação de cópias exclusivas da malha (mesh) para cada cor diferente, o que também gera um custo extra de desempenho.
Os uniforms por instância são definidos em cada GeometryInstance3D, e não em cada instância de Material. Leve isso em consideração ao trabalhar com malhas que possuem múltiplos materiais atribuídos a elas ou com configurações de MultiMesh.
shader_type spatial;
// Provide a hint to edit as a color. Optionally, a default value can be provided.
// If no default value is provided, the type's default is used (e.g. opaque black for colors).
instance uniform vec4 my_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
void fragment() {
ALBEDO = my_color.rgb;
}
Após salvar o shader, você pode alterar o valor do uniform por instância usando o inspetor:
Definindo o valor de um uniform por instância na seção GeometryInstance3D do inspetor
Os valores de uniforms por instância também podem ser definidos em tempo de execução usando o método set_instance_shader_parameter em um nó que herda de GeometryInstance3D:
$MeshInstance3D.set_instance_shader_parameter("my_color", Color(0.3, 0.6, 1.0))
Ao usar uniforms por instância, existem algumas restrições das quais você deve estar ciente:
Os uniforms por instância não suportam texturas ou arrays, apenas tipos escalares e vetoriais comuns. Como alternativa, você pode passar um array de texturas como um uniform regular e, em seguida, passar o índice da textura a ser desenhada usando um uniform por instância.
Nota
Em versões do GLSL anteriores à 4.0 (ou seja, GLSL 3.3 e inferiores), você não pode indexar diretamente um array de texturas usando um uniform por instância, pois os arrays de samplers só podem ser indexados por expressões constantes em tempo de compilação. Isso afeta shaders compilados com o renderizador Compatibility.
Se você for afetado por isso, use a instrução
switchpara selecionar la textura:
uniform sampler2D texture_array[2];
instance uniform int texture_index;
void fragment() {
vec4 color;
switch (texture_index) {
case 0:
color = texture(texture_array[0], UV);
break;
case 1:
color = texture(texture_array[1], UV);
break;
}
COLOR = color;
}
Existe um limite máximo prático de 16 uniforms de instância por shader.
Se sua malha usa múltiplos materiais, os parâmetros do primeiro material de malha encontrado "vencerão" sobre os subsequentes, a menos que tenham o mesmo nome, índice e tipo. Neste caso, todos os parâmetros são afetados corretamente.
Se você se deparar com a situação acima, poderá evitar conflitos especificando manualmente o índice (0-15) do uniform de instância usando a dica
instance_index:
instance uniform vec4 my_color : source_color, instance_index(5);
Definindo uniformes a partir de código
Você pode definir uniforms a partir do GDScript usando o método set_shader_parameter():
material.set_shader_parameter("some_value", some_value)
material.set_shader_parameter("colors", [Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)])
Nota
O primeiro argumento de set_shader_parameter() é o nome do uniform no shader. Ele deve corresponder exatamente ao nome do uniform no shader, caso contrário não será reconhecido.
O GDScript usa tipos de variáveis diferentes do GLSL, portanto, ao passar variáveis do GDScript para os shaders, o Godot converte o tipo automaticamente. Abaixo está uma tabela dos tipos correspondentes:
Tipo GLSL |
Tipo GDScript |
Notas |
|---|---|---|
bool |
bool |
|
bvec2 |
int |
Inteiro empacotado bit a bit (bitwise packed int) onde o bit 0 (LSB) corresponde a x. Por exemplo, um bvec2 de (bx, by) poderia ser criado da seguinte maneira: bvec2_input: int = (int(bx)) | (int(by) << 1)
|
bvec3 |
int |
Inteiro empacotado bit a bit (bitwise packed int) onde o bit 0 (LSB) corresponde a x. |
bvec4 |
int |
Inteiro empacotado bit a bit (bitwise packed int) onde o bit 0 (LSB) corresponde a x. |
int |
int |
|
ivec2 |
Vector2i |
|
ivec3 |
Vector3i |
|
ivec4 |
Vector4i |
|
uint |
int |
|
uvec2 |
Vector2i |
|
uvec3 |
Vector3i |
|
uvec4 |
Vector4i |
|
float |
float |
|
vec2 |
Vector2 |
|
vec3 |
Vector3, Color |
Quando Color for usado, será interpretado como (r, g, b). |
vec4 |
Vector4, Color, Rect2, Plane, Quaternion |
Quando Color for usado, será interpretado como (r, g, b, a). Quando Rect2 for usado, será interpretado como (position.x, position.y, size.x, size.y). Quando Plane for usado, será interpretado como (normal.x, normal.y, normal.z, d). |
mat2 |
Transform2D |
|
mat3 |
Fundamento |
|
mat4 |
Projection, Transform3D |
Quando um Transform3D for usado, o vetor w é definido como a identidade. |
sampler2D |
Texture2D |
|
isampler2D |
Texture2D |
|
usampler2D |
Texture2D |
|
sampler2DArray |
Texture2DArray |
|
isampler2DArray |
Texture2DArray |
|
usampler2DArray |
Texture2DArray |
|
sampler3D |
Texture3D |
|
isampler3D |
Texture3D |
|
usampler3D |
Texture3D |
|
samplerCube |
Cubemap |
Veja Alterando o tipo de importação para instruções sobre como importar cubemaps para uso no Godot. |
samplerCubeArray |
CubemapArray |
Compatível apenas com Forward+ e Mobile, não com Compatibility. |
samplerExternalOES |
ExternalTexture |
Compatível apenas com a plataforma Compatibility/Android. |
Nota
Tenha cuidado ao definir uniforms de shader a partir do GDScript, pois nenhum erro será gerado se o tipo não coincidir. Seu shader simplesmente exibirá um comportamento indefinido. Especificamente, isso inclui definir um int/float do GDScript (64 bits) em um int/float da linguagem de shader do Godot (32 bits). Isso pode levar a consequências indesejadas em casos onde alta precisão é necessária.
Limites de uniformes
Existe um limite para o tamanho total dos uniforms de shader que você pode usar em um único shader. Na maioria das plataformas desktop, esse limite é de 65536 bytes, ou 4096 uniforms vec4. Em plataformas móveis, o limite é tipicamente de 16384 bytes, ou 1024 uniforms vec4. Uniforms vetoriais menores que um vec4, como vec2 ou vec3, são preenchidos (padded) até o tamanho de um vec4. Uniforms escalares, como int ou float, não são preenchidos, e bool é preenchido até o tamanho de um int.
Os arrays contam como o tamanho total de seus conteúdos. Se você precisar de um array uniform que seja maior do que esse limite, considere empacotar os dados em uma textura, já que os conteúdos de uma textura não contam para esse limite, apenas o tamanho do uniform sampler.
Variáveis embutidas
Um grande número de variáveis embutidas está disponível, como UV, COLOR e VERTEX. Quais variáveis estão disponíveis depende do tipo de shader (spatial, canvas_item, particle, etc.) e da função usada (vertex, fragment, light, start, process, sky ou fog). Para uma lista das variáveis embutidas disponíveis, consulte as páginas correspondentes:
Funções embutidas
Um grande número de funções embutidas é suportado, em conformidade com o GLSL ES 3.0. Consulte a página Built-in functions para mais detalhes.
Comentários
A linguagem de sombreamento suporta a mesma sintaxe de comentários usada em C# e C++, utilizando
//para comentários de uma única linha e/* */para comentários de múltiplas linhas:Adicionalmente, você pode usar comentários de documentação que são exibidos no inspetor ao passar o mouse sobre um parâmetro do shader. Os comentários de documentação atualmente só são suportados quando colocados imediatamente acima de uma declaração de
uniform. Esses comentários de documentação suportam apenas a sintaxe de comentário de múltiplas linhas (mesmo que usados em uma única linha) e devem usar dois asteriscos iniciais (/**) em vez de apenas um (/*):Os asteriscos nas linhas seguintes não são obrigatórios, mas são recomendados de acordo com o Guia de estilo de shaders. Esses asteriscos são removidos automaticamente pelo inspetor, de modo que não aparecerão na dica de ferramenta (tooltip).