Bandeira tremulando
Uma bandeira listrada que ondula a partir do mastro, desenhada num quad plano entortando a UV com um seno que viaja.
- 01ObjetivoDistorcer
- 02TécnicaUV warping
- 03ExecuçãoBandeira tremulando
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 Onda que viaja
+Add+++ Entrada02 Entortar a UV
stepStepstepstepstep Entrada03 Listras na UV entortada
cosCoscoscoscos Entrada04 Sombrear as dobras
mixMixmixmixmix Entrada05 Colorir o tecido
mixMixmixmixmix Entrada06 Sobre o céu
Como funciona
O quad nunca se move. Cada pixel pergunta “qual ponto do desenho da bandeira fica aqui?”, e o shader responde com uma
UVUma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela. Coordenadas UV Uma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela.uv = gl_FragCoord.xy / resolution
1. Uma onda que viaja. sin(fromPole * frequency - time * speed) é um seno ao longo da largura da bandeira.
Subtrair o tempo move as cristas para a direita, para longe do mastro, a speed / frequency unidades de UV por
segundo. A prévia mostra a metade positiva da onda; a negativa é cortada para preto.
2. Entortar a UV. A onda vira um deslocamento vertical, wave * amplitude * fromPole, somado à UV. O fator
fromPole vale 0 no mastro e cresce até a ponta solta, então a borda presa ao mastro fica reta e a ponta oposta balança
mais, como um tecido de verdade. A prévia mostra a UV entortada em vermelho e verde: as linhas de mesmo verde agora são
onduladas.
3. Listras na UV entortada. A bandeira é um retângulo do espaço entortado, remapeado para 0..1 como cloth. As
listras são step(0.5, fract(cloth.y * bands * 0.5)): o fract repete a coordenada e o step divide cada repetição
em duas cores. Como elas saem de cloth, e não da UV original, acompanham a onda, assim como as bordas de cima e de
baixo da bandeira.
4. Sombrear as dobras. Só entortar parece uma fita de papel deslizando para cima e para baixo. O tecido pega luz, e
quanto depende da inclinação. O deslocamento é sin(phase) * amplitude * fromPole, e a inclinação dele é mais ou menos
cos(phase) * amplitude * frequency * fromPole. Limitada a -1..1, essa inclinação faz 1.0 + 0.35 * inclinação clarear
as partes da onda inclinadas para um lado e escurecer as outras. Esse brilho é uma luz falsa, sem normal nem vetor de
luz, e custa um cosseno, algumas multiplicações e um clamp. Ele some perto do mastro, onde o tecido quase não se mexe, e
desaparece quando a amplitude é 0 e a bandeira fica parada e lisa.
5 e 6. Cor. O mix escolhe a cor de fundo ou a da listra, multiplicada pelo sombreamento das dobras, e o resultado
vai sobre o céu onde o ponto entortado cai dentro do retângulo da bandeira. O mastro é desenhado por último, na UV sem
warping, então fica rígido.
Ajustes e limites
Cerca de 60 operações matemáticas por pixel, a maioria nas bordas suaves: cada chamada de band são dois smoothsteps.
Nenhuma leitura de textura, uma passada. Uma bandeira com textura custa mais ou menos o mesmo, com uma chamada de
texture() em warped no lugar das listras.
- Amplitude vezes frequência define a inclinação. O deslocamento só depende de x, então a imagem não consegue
dobrar sobre si mesma, mas onde
amplitude * frequency * fromPolepassa de uns 2 as listras se inclinam tanto que viram lascas finas e cintilam. Os valores padrão chegam a uns 1.3 na ponta solta. - A silhueta é falsa. A borda ondula porque cada pixel testa se o seu ponto entortado está dentro do retângulo; o quad continua sendo um retângulo. Visto de lado, é um cartão plano. Nesse caso, use deslocamento de vértices.
- Some uma segunda onda menor em outra frequência para um visual menos mecânico; um pouco de ruído na fase deixa a bandeira com rajadas de vento.
Monte no GLSL
O shader copiado é um shader de fragmento WebGL2 completo para um quad: ele declara as entradas, um uniform por
parâmetro e u_time, e escreve a cor codificada em sRGB.
- Desenhe um quad (dois triângulos, ou um triângulo grande) cujo shader de vértice repasse a UV, de 0..1 a partir do
canto inferior esquerdo, como
v_uv. - Compile o código copiado como shader de fragmento e ligue-o a esse shader de vértice.
- A cada quadro, ponha em
u_timeos segundos desde o início e desenhe. Defina os uniformsp_uma vez, ou sempre que um controle mudar; as cores vão em linear. - Para usar uma imagem de bandeira no lugar das listras, adicione um uniform
sampler2De troque omixdefabricportexture(image, cloth).rgb. Use o modo de wrap clamp, para a imagem não se repetir na borda.
O shader não tem discard e escreve alfa 1, então a cor do céu preenche o quad. Para uma bandeira sobre o seu próprio
fundo, devolva inside como alfa e desenhe com gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA).
Monte no Godot
O shader é um shader canvas_item que assume o HDR 2D desligado (o padrão do projeto); ele decodifica as cores e codifica o resultado por conta própria.
O shader exportado desenha em qualquer nó 2D que tenha material. Um ColorRect é o mais simples: ele não tem textura própria e a UV dele o cobre de canto a canto.
- Adicione um
ColorRecta uma cena 2D ou a uma interface e ajuste o tamanho. - No Inspector, ponha em Material um novo
ShaderMaterial, e no Shader dele um novoShader. - Cole o código copiado no shader. A bandeira começa a se mexer na hora: o
TIMEdo Godot alimentai.time. - Ajuste os parâmetros em Shader Parameters. Os nomes são os mesmos deste cartão, sem o prefixo
p_.
Para montá-lo como VisualShader (um VisualShader canvas_item no mesmo material), os passos correspondem um a um:
UV, com umVectorDecomposepara pegar x e y; inverta y com umOneMinus.Timevezes a velocidade, subtraído defromPolevezes a frequência, entrando numSin: a onda.Multiplyda onda pela amplitude e porfromPole, eAddem y: a UV entortada.FracteStepno y remapeado: as listras.Cosda mesma fase,Multiplypela amplitude, pela frequência e porfromPole, e depoisClampem -1..1: o sombreamento das dobras.Mixdas duas cores pelas listras, depoisMixsobre o céu pela máscara de dentro, em Color.
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 → Waving flag, 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_amplitude; // Amplitude: 0.12 (0.0 to 0.3)uniform float p_frequency; // Frequency: 14.0 (2.0 to 40.0)uniform float p_speed; // Speed: 4.0 (0.0 to 12.0)uniform float p_bands; // Stripes: 5.0 (1.0 to 13.0)uniform vec3 p_field; // Field color: vec3(0.12, 0.27, 0.55), sRGB; upload it decoded to linearuniform vec3 p_stripe; // Stripe color: vec3(0.95, 0.78, 0.25), sRGB; upload it decoded to linearuniform vec3 p_backdrop; // Sky: vec3(0.62, 0.78, 0.9), sRGB; upload it decoded to linearuniform vec3 p_pole; // Pole: vec3(0.28, 0.24, 0.2), sRGB; upload it decoded to linearin 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));}// A soft-edged 0..1 window, so the flag's outline doesn't stair-step as it moves.float band(float x, float from, float to) { return smoothstep(from - 0.004, from + 0.004, x) * (1.0 - smoothstep(to - 0.004, to + 0.004, x));}vec4 effect(Inputs i) { // The cloth is fixed at the pole and free at the far end, so the wave grows with distance from the pole float fromPole = max(i.uv.x - 0.1, 0.0); float phase = fromPole * p_frequency - i.time * p_speed; float wave = sin(phase); vec2 warped = i.uv + vec2(0.0, wave * p_amplitude * fromPole); vec2 cloth = (warped - vec2(0.1, 0.2)) / vec2(0.8, 0.6); float inside = band(cloth.x, 0.0, 1.0) * band(cloth.y, 0.0, 1.0); float stripes = step(0.5, fract(cloth.y * floor(p_bands) * 0.5)); // The offset's slope: cloth tilted toward the light is brighter, cloth tilted away darker float fold = 1.0 + 0.35 * clamp(cos(phase) * p_amplitude * p_frequency * fromPole, -1.0, 1.0); vec3 fabric = mix(p_field, p_stripe, stripes) * fold; float staff = band(i.uv.x, 0.085, 0.1) * band(i.uv.y, 0.05, 0.84); vec3 color = mix(mix(p_backdrop, fabric, inside), p_pole, staff); return vec4(color, 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 → Waving flag, Godot 4. From Creative Center, MIT license.shader_type canvas_item;// Unshaded shows the effect's color as is, like the site's preview; scene lights don't touch itrender_mode unshaded;uniform float amplitude : hint_range(0.0, 0.3, 0.005) = 0.12;uniform float frequency : hint_range(2.0, 40.0, 0.5) = 14.0;uniform float speed : hint_range(0.0, 12.0, 0.1) = 4.0;uniform float bands : hint_range(1.0, 13.0, 1.0) = 5.0;uniform vec3 field : source_color = vec3(0.12, 0.27, 0.55);uniform vec3 stripe : source_color = vec3(0.95, 0.78, 0.25);uniform vec3 backdrop : source_color = vec3(0.62, 0.78, 0.9);uniform vec3 pole : source_color = vec3(0.28, 0.24, 0.2);struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// Godot's 2D renderer works in sRGB (HDR 2D off, the default), so the frame decodes color params and sRGB// textures to linear itself and encodes the result. With HDR 2D on, make these return c unchanged.vec3 u_toLinear(vec3 c) { return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));}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));}// A soft-edged 0..1 window, so the flag's outline doesn't stair-step as it moves.float band(float x, float from, float to) { return smoothstep(from - 0.004, from + 0.004, x) * (1.0 - smoothstep(to - 0.004, to + 0.004, x));}vec4 effect(Inputs i) { // The cloth is fixed at the pole and free at the far end, so the wave grows with distance from the pole float fromPole = max(i.uv.x - 0.1, 0.0); float phase = fromPole * frequency - i.time * speed; float wave = sin(phase); vec2 warped = i.uv + vec2(0.0, wave * amplitude * fromPole); vec2 cloth = (warped - vec2(0.1, 0.2)) / vec2(0.8, 0.6); float inside = band(cloth.x, 0.0, 1.0) * band(cloth.y, 0.0, 1.0); float stripes = step(0.5, fract(cloth.y * floor(bands) * 0.5)); // The offset's slope: cloth tilted toward the light is brighter, cloth tilted away darker float fold = 1.0 + 0.35 * clamp(cos(phase) * amplitude * frequency * fromPole, -1.0, 1.0); vec3 fabric = mix(u_toLinear(field), u_toLinear(stripe), stripes) * fold; float staff = band(i.uv.x, 0.085, 0.1) * band(i.uv.y, 0.05, 0.84); vec3 color = mix(mix(u_toLinear(backdrop), fabric, inside), u_toLinear(pole), staff); return vec4(color, 1.0);}void fragment() { Inputs i; // UV starts at the top-left; the effect expects GLSL's bottom-left i.uv = vec2(UV.x, 1.0 - 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 = 1.0 / SCREEN_PIXEL_SIZE; i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y); vec4 frag_out = effect(i); COLOR = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}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.
Leitura complementar
- 2D Matrices (abre em uma nova aba)Livro
- sin (abre em uma nova aba)Referência
- cos (abre em uma nova aba)Referência
- Domain warping (abre em uma nova aba)Artigo
- CanvasItem shaders (abre em uma nova aba)Documentação
- Using VisualShaders (abre em uma nova aba)Documentação