Ar quente
O ar sobre o chão quente tremula, feito lendo o quadro pronto em coordenadas empurradas por ondulações que sobem.
- 01ObjetivoDistorcer
- 02TécnicaUV warping
- 03ExecuçãoAr quente
Parâmetros
Copie para a sua plataforma
- GLSLCopiar
- GodotCopiar
Cadeia de nós
Montada passo a passo, no estilo do GLSL, com os nomes de nós dele.
Montada passo a passo, no estilo do Godot, com os nomes de nós dele.
Este card ainda não tem versão para Unreal Engine 5, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Unity, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Blender, então os nós mostram os nomes do GLSL.
sinSinsinsinsin UVTempoTexturaTela01 Ondulações subindo
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep Entrada02 Perto do chão
+MultiplyAdd+++ Entrada03 Deslocar a leitura
textureTexture2Dtexturetexturetexture Entrada04 Ler a tela
Como funciona
O ar quente entorta um pouco a luz, porque a densidade dele, e com ela o índice de refração, muda de um bolsão para o outro. Esses bolsões sobem e se misturam, então o que você vê através deles treme. Uma passada de pós imita exatamente isso: ela lê o quadro pronto, mas não exatamente onde cada pixel está.
A cena é desenhada primeiro num render targetUma textura em que um desenho escreve no lugar da tela, para uma passada seguinte ler o resultado. Blur, bloom e todo efeito com várias passadas usam isso. Render target Uma textura em que um desenho escreve no lugar da tela, para uma passada seguinte ler o resultado. Blur, bloom e todo efeito com várias passadas usam isso. Cadeia de pós-processamento Passadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui.t_screen, numa coordenada que ela empurrou. Ela é um elo da
cadeia de pós-processamentoPassadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui.
1. Ondulações subindo. Dois senos formam o deslocamento, um para x e outro para y. Cada um mistura a posição do pixel com o tempo, e subtrair o tempo faz as cristas da ondulação de x subirem pela tela, como o ar quente sobe, e desliza as cristas da ondulação de y para o lado. A fase da ondulação de x é entortada por um terceiro seno, mais lento, para as faixas não serem linhas retas. As frequências, 7, 5.3 e 3.1, não têm uma razão simples entre si, então o padrão não se alinha numa repetição visível. A coordenada é escalada pela proporção da tela antes, para uma ondulação ser tão larga quanto alta em qualquer tela. A prévia mostra o deslocamento em vermelho e verde, preto onde ele é negativo.
2. Perto do chão. 1.0 - smoothstep(0.0, height, uv.y) vale 1 na base da tela e cai até 0 em height. O ar
quente fica sobre o chão, e a câmera vê o chão na parte de baixo do quadro, então é ali que o tremor vai. Um jogo usaria
uma máscara melhor: a distância até uma fogueira, ou uma área quente pintada numa textura.
3. Deslocar a leitura. O deslocamento, vezes a máscara, vezes a intensidade, é somado à UV. A intensidade é minúscula, 0.006 por padrão: uma fração de um por cento da tela, um punhado de pixels. Mais que isso e o tremor parece uma lente quebrada, não ar.
4. Ler a tela. texture(t_screen, warped) lê o quadro na coordenada movida. Pixels vizinhos leem pontos vizinhos,
levemente embaralhados, e a cena parece ondular. O target é amostrado com filtro linear, então um deslocamento de meio
pixel mistura dois texels em vez de pular de um para o outro.
Quanto custa e onde quebra
Cerca de 30 operações matemáticas e uma leitura de textura por pixel, numa passada de tela cheia. A leitura é o custo
de verdade: a passada toca cada pixel da tela, tenha calor ali ou não. Mascarar com um if não economiza muito numa
GPU, onde pixels vizinhos rodam juntos; recortar a passada (scissor) para a área sob o calor economiza.
- As bordas da tela. Perto da borda, a coordenada entortada sai de 0..1. O target usa clamp, então esses pixels repetem a fileira da borda; com uma intensidade tão pequena isso dá uma ou duas linhas de pixels e não aparece.
- O que está na frente do calor também se mexe. Uma passada de pós não sabe o que está perto ou longe: um
personagem de pé na frente do asfalto quente treme junto. Escale o deslocamento por
sceneDepth()para manter parados os objetos próximos, ou desenhe o tremor como material numa superfície posicionada na área quente, que o teste de profundidade esconde atrás dos objetos mais próximos. - A ordem na cadeia. Ponha o ar quente antes do bloom e de outros desfoques, para eles suavizarem as bordas dele, e depois de tudo o que precisa tremer junto com a cena.
Monte no GLSL
O shader copiado é um shader de fragmento WebGL2 completo para uma passada de tela cheia: ele declara as entradas, um
uniform por parâmetro, u_time, u_resolution e t_screen, a cena pronta. O seu código desenha a cena numa textura
antes e depois desenha um triângulo de tela cheia com este shader.
- Crie uma textura do tamanho do canvas e um framebuffer com ela como
COLOR_ATTACHMENT0, mais um renderbuffer de profundidade para a cena. Dê à textura filtroLINEARe wrapCLAMP_TO_EDGE. - A cada quadro, ative o framebuffer e desenhe a cena nele.
- Ative o framebuffer padrão (
null), ligue a textura da cena a uma unidade de textura e apontet_screenpara essa unidade. - Defina
u_timeeu_resolutione desenhe um triângulo que cubra a tela, com um shader de vértice que repasse a UV, de 0..1 a partir do canto inferior esquerdo, comov_uv. As outras entradas não são usadas.
O shader lê t_screen como cor linear e codifica a saída em sRGB. Se a textura da sua cena já guarda valores sRGB (um
target RGBA8 escrito por shaders que codificaram a própria saída), decodifique depois da leitura, ou a passada do
tremor codifica duas vezes e a imagem sai desbotada. Uma textura SRGB8_ALPHA8 é decodificada pela GPU para você.
Monte no Godot
O shader é um shader spatial para um quad de tela cheia. Ele lê a tela por hint_screen_texture, que funciona nos renderizadores Forward+, Mobile e Compatibility.
O shader exportado desenha o efeito sobre a tela inteira a partir de um quad que você coloca na cena 3D. A função
vertex() dele escreve os cantos do quad direto nos cantos da tela, então ele cobre a visão onde quer que o quad e a
câmera estejam.
- Adicione um
MeshInstance3Dcomo filho daCamera3De dê a ele umQuadMeshde tamanho 2 por 2. - Ponha em Material Override um novo
ShaderMaterial, e no Shader dele um novoShader. - Cole o código copiado no shader.
- Em Geometry, ponha Extra Cull Margin no máximo (16384), para o Godot nunca descartar o quad.
- Ajuste os parâmetros em Shader Parameters: os mesmos nomes deste cartão, sem o prefixo
p_. Aumenteheightpara cobrir mais da tela, ou troque a máscara pela sua.
Para montá-lo como VisualShader (um VisualShader spatial, unshaded), a cadeia é ScreenUV, as duas ondulações Sin
feitas a partir dele e de Time, uma máscara SmoothStep no y de ScreenUV, um MultiplyAdd na UV e um nó
Texture2D com Source em Screen, lido no resultado e ligado em Albedo. A parte de vértice, que leva o quad para a
tela, precisa de um nó Expression ou do shader em texto.
Para um tremor em volta de uma fonte só, em vez da tela inteira, use o mesmo shader num quad menor posicionado sobre a fogueira, sem a função de vértice: aí a leitura da tela só acontece onde o quad é desenhado.
Ainda não há tutorial de Unreal Engine 5 para este card.
Ainda não há tutorial de Unity para este card.
Ainda não há tutorial de Blender para este card.
Código
#version 300 es// UV warping → Heat haze, web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_strength; // Strength: 0.006 (0.0 to 0.03)uniform float p_scale; // Ripple scale: 6.0 (1.0 to 20.0)uniform float p_speed; // Rise speed: 3.0 (0.0 to 10.0)uniform float p_height; // Height: 0.6 (0.05 to 1.0)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};vec3 u_toSrgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}vec4 effect(Inputs i) { // Scaled by the aspect ratio so the ripples stay round on a wide screen vec2 p = i.uv * vec2(i.resolution.x / i.resolution.y, 1.0) * p_scale; float rise = i.time * p_speed; // Two sines at unrelated frequencies, the first bent by a third, so the pattern never visibly repeats vec2 wobble = vec2(sin(p.y * 7.0 - rise * 1.3 + sin(p.x * 3.1)), sin(p.x * 5.3 + p.y * 2.0 - rise)); // Hot air hugs the ground: full strength at the bottom of the screen, none above p_height float mask = 1.0 - smoothstep(0.0, p_height, i.uv.y); vec2 warped = i.uv + wobble * mask * p_strength; vec3 scene = texture(t_screen, warped).rgb; return vec4(scene, 1.0);}void main() { Inputs i; i.uv = v_uv; i.position = v_position; // Interpolation shortens the vectors between vertices i.normal = normalize(v_normal); i.view = normalize(v_view); i.time = u_time; i.fragCoord = gl_FragCoord.xy; i.resolution = u_resolution; vec4 frag_out = effect(i); // The output is sRGB: clamped and encoded once, here, with no tone mapping; opaque, so alpha is 1 fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}// UV warping → Heat haze, Godot 4. From Creative Center, MIT license.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float strength : hint_range(0.0, 0.03, 0.0005) = 0.006;uniform float scale : hint_range(1.0, 20.0, 0.5) = 6.0;uniform float speed : hint_range(0.0, 10.0, 0.1) = 3.0;uniform float height : hint_range(0.05, 1.0, 0.01) = 0.6;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) { return vec2(uv.x, 1.0 - uv.y);}vec4 effect(Inputs i) { // Scaled by the aspect ratio so the ripples stay round on a wide screen vec2 p = i.uv * vec2(i.resolution.x / i.resolution.y, 1.0) * scale; float rise = i.time * speed; // Two sines at unrelated frequencies, the first bent by a third, so the pattern never visibly repeats vec2 wobble = vec2(sin(p.y * 7.0 - rise * 1.3 + sin(p.x * 3.1)), sin(p.x * 5.3 + p.y * 2.0 - rise)); // Hot air hugs the ground: full strength at the bottom of the screen, none above height float mask = 1.0 - smoothstep(0.0, height, i.uv.y); vec2 warped = i.uv + wobble * mask * strength; vec3 scene = texture(t_screen, u_flipUv(warped)).rgb; return vec4(scene, 1.0);}void vertex() { // Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is POSITION = vec4(VERTEX.xy, 1.0, 1.0);}void fragment() { Inputs i; // SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y); // A flat quad facing the viewer, the same as the site's 2D preview i.position = vec3(i.uv * 2.0 - 1.0, 0.0); i.normal = vec3(0.0, 0.0, 1.0); i.view = vec3(0.0, 0.0, 1.0); i.time = TIME; // FRAGCOORD's origin is the top-left too i.resolution = VIEWPORT_SIZE; i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y); vec4 frag_out = effect(i); ALBEDO = frag_out.rgb;}Ainda não há código de Unreal Engine 5 para este card.
Ainda não há código de Unity para este card.
Ainda não há código de Blender para este card.