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.

Shaders de leitura de tela

Introdução

Muitas vezes deseja-se criar um shader que lê da mesma tela na qual está escrevendo. APIs 3D, como OpenGL ou DirectX, tornam isso muito difícil devido a limitações internas de hardware. As GPUs são extremamente paralelas, então a leitura e a escrita causam todo tipo de problemas de cache e coerência. Como resultado, nem mesmo o hardware mais moderno suporta isso adequadamente.

A solução alternativa é fazer uma cópia da tela, ou de uma parte da tela, para um back-buffer e então ler a partir dele enquanto desenha. O Godot fornece algumas ferramentas que facilitam esse processo.

Textura de tela (Screen texture)

A Linguagem de shading do Godot possui uma textura especial para acessar o conteúdo já renderizado da tela. Ela é usada especificando um hint ao declarar um uniform sampler2D: hint_screen_texture. Uma variação integrada especial SCREEN_UV pode ser usada para obter a UV relativa à tela para o fragmento atual. Como resultado, este fragment shader de canvas_item resulta em um objeto invisível, porque ele mostra apenas o que está atrás:

shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

void fragment() {
    COLOR = textureLod(screen_texture, SCREEN_UV, 0.0);
}

textureLod é usado aqui pois queremos apenas ler do mipmap inferior. Se você quiser ler de uma versão borrada da textura, você pode aumentar o terceiro argumento de textureLod e mudar o hint de filter_nearest para filter_nearest_mipmap (ou qualquer outro filtro com mipmaps ativados). Se estiver usando um filtro com mipmaps, o Godot calculará automaticamente a textura borrada para você.

Aviso

Se o modo de filtro não for alterado para um modo de filtro que contenha mipmap em seu nome, textureLod com um parâmetro LOD maior que 0.0 terá a mesma aparência que com o parâmetro LOD 0.0.

Exemplo de textura de tela

A textura de tela pode ser usada para muitas coisas. Há uma demonstração especial para Screen Space Shaders que você pode baixar para ver e aprender. Um exemplo é um shader simples para ajustar brilho, contraste e saturação:

shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

uniform float brightness = 1.0;
uniform float contrast = 1.0;
uniform float saturation = 1.0;

void fragment() {
    vec3 c = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;

    c.rgb = mix(vec3(0.0), c.rgb, brightness);
    c.rgb = mix(vec3(0.5), c.rgb, contrast);
    c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation);

    COLOR.rgb = c;
}

Por trás dos panos

Embora isso pareça mágico, não é. Em 2D, quando hint_screen_texture é encontrado pela primeira vez em um nó que está prestes a ser desenhado, o Godot faz uma cópia de tela cheia para um back-buffer. Nós subsequentes que o utilizarem em shaders não terão a tela copiada para eles, porque isso acaba sendo ineficiente. Em 3D, a tela é copiada após o passe de geometria opaca, mas antes do passe de geometria transparente, de modo que objetos transparentes não serão capturados na textura de tela.

Como resultado, em 2D, se shaders que usam hint_screen_texture se sobrepuserem, o segundo não usará o resultado do primeiro, resultando em visuais inesperados:

../../_images/texscreen_demo1.png

Na imagem acima, a segunda esfera (superior direita) está usando a mesma origem para a textura de tela que a primeira abaixo, então a primeira "desaparece", ou não fica visível.

Em 2D, isso pode ser corrigido através do nó BackBufferCopy, que pode ser instanciado entre ambas as esferas. O BackBufferCopy pode funcionar especificando uma região da tela ou a tela inteira:

../../_images/texscreen_bbc.png

Com a cópia correta do back-buffer, as duas esferas se misturam corretamente:

../../_images/texscreen_demo2.png

Aviso

In 3D, materials that use hint_screen_texture are considered transparent themselves and will not appear in the resulting screen texture of other materials. If you plan to instantiate a scene that uses a material with hint_screen_texture, you will need to use a BackBufferCopy node.

Em 3D, há menos flexibilidade para resolver esse problema específico porque a textura de tela é capturada apenas uma vez. Tenha cuidado ao usar a textura de tela em 3D, pois ela não capturará objetos transparentes e poderá capturar alguns objetos opacos que estão na frente do objeto que está usando a textura de tela.

Você pode reproduzir a lógica de back-buffer em 3D criando um Viewport com uma câmera na mesma posição do seu objeto e, em seguida, usar a textura do Viewport em vez da textura de tela.

Lógica de back-buffer

Então, para ficar mais claro, eis como a lógica de cópia de backbuffer funciona em 2D no Godot:

  • Se um nó usa hint_screen_texture, a tela inteira é copiada para o back buffer antes de desenhar esse nó. Isso só acontece na primeira vez; nós subsequentes não acionam isso.

  • Se um nó BackBufferCopy foi processado antes da situação descrita no ponto acima (mesmo que hint_screen_texture não tenha sido usado), o comportamento descrito no ponto acima não acontece. Em outras palavras, a cópia automática da tela inteira só acontece se hint_screen_texture for usado em um nó pela primeira vez e nenhum nó BackBufferCopy (que não esteja desativado) foi encontrado antes na ordem da árvore.

  • O BackBufferCopy pode copiar a tela inteira ou uma região. Se configurado para apenas uma região (não a tela inteira) e seu shader usar pixels que não estão na região copiada, o resultado dessa leitura é indefinido (provavelmente lixo de frames anteriores). Em outras palavras, é possível usar o BackBufferCopy para copiar de volta uma região da tela e depois ler a textura da tela de uma região diferente. Evite este comportamento!

Textura de profundidade

Para shaders 3D, também é possível acessar o buffer de profundidade da tela. Para isso, o hint hint_depth_texture é usado. Esta textura não é linear; ela deve ser convertida usando a matriz de projeção inversa.

O código a seguir recupera a posição 3D abaixo do pixel que está sendo desenhado:

uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;

void fragment() {
    float depth = textureLod(depth_texture, SCREEN_UV, 0.0).r;
    vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth, 1.0);
    vec3 pixel_position = upos.xyz / upos.w;
}

Textura de normal-rugosidade (normal-roughness)

Nota

Texturas de normal-rugosidade são suportadas apenas no método de renderização Forward+, e não nos modos Mobile ou Compatibility.

De forma semelhante, a textura de normal-rugosidade pode ser usada para ler as normais e a rugosidade de objetos renderizados no pré-passe de profundidade. A normal é armazenada nos canais .xyz (mapeados para o intervalo 0-1) enquanto a rugosidade é armazenada no canal .w.

uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;

void fragment() {
    float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
    vec3 screen_normal = texture(normal_roughness_texture, SCREEN_UV).xyz;
    screen_normal = screen_normal * 2.0 - 1.0;

Redefinindo texturas de tela

Os hints de textura de tela (hint_screen_texture, hint_depth_texture e hint_normal_roughness_texture) podem ser usados com múltiplos uniforms. Por exemplo, você pode querer ler da textura várias vezes com uma flag de repetição ou flag de filtro diferente.

O exemplo a seguir mostra um shader que lê a normal do espaço de tela com filtragem linear, mas lê a rugosidade do espaço de tela usando filtragem de vizinho mais próximo (nearest neighbor).

uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;
uniform sampler2D normal_roughness_texture2 : hint_normal_roughness_texture, repeat_enable, filter_linear;

void fragment() {
    float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
    vec3 screen_normal = texture(normal_roughness_texture2, SCREEN_UV).xyz;
    screen_normal = screen_normal * 2.0 - 1.0;