Pular para o conteúdo
Distorcer · UV warpingIniciante

Bandeira tremulando

Uma bandeira listrada que ondula a partir do mastro, desenhada num quad plano entortando a UV com um seno que viaja.

UVTempoTexturaTelaSuperfície
Abrir no Playground
  1. 01ObjetivoDistorcer
  2. 02TécnicaUV warping
  3. 03ExecuçãoBandeira tremulando
Quad de tela cheia

Parâmetros

0.120
14.0
4.0
5
#1f458c
#f2c740
#9ec7e6
#473d33

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.

  1. sinSinsinsinsin
    UV
    Tempo
    Textura
    Tela

    01 Onda que viaja

  2. +Add+++

    02 Entortar a UV

  3. stepStepstepstepstep

    03 Listras na UV entortada

  4. cosCoscoscoscos

    04 Sombrear as dobras

  5. mixMixmixmixmix

    05 Colorir o tecido

  6. mixMixmixmixmix

    06 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 UV empurrada para cima ou para baixo por uma onda. As listras, desenhadas nessa coordenada entortada, ondulam.

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 * fromPole passa 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.

  1. 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.
  2. Compile o código copiado como shader de fragmento e ligue-o a esse shader de vértice.
  3. A cada quadro, ponha em u_time os segundos desde o início e desenhe. Defina os uniforms p_ uma vez, ou sempre que um controle mudar; as cores vão em linear.
  4. Para usar uma imagem de bandeira no lugar das listras, adicione um uniform sampler2D e troque o mix de fabric por texture(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.

  1. Adicione um ColorRect a uma cena 2D ou a uma interface e ajuste o tamanho.
  2. No Inspector, ponha em Material um novo ShaderMaterial, e no Shader dele um novo Shader.
  3. Cole o código copiado no shader. A bandeira começa a se mexer na hora: o TIME do Godot alimenta i.time.
  4. 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:

  1. UV, com um VectorDecompose para pegar x e y; inverta y com um OneMinus.
  2. Time vezes a velocidade, subtraído de fromPole vezes a frequência, entrando num Sin: a onda.
  3. Multiply da onda pela amplitude e por fromPole, e Add em y: a UV entortada.
  4. Fract e Step no y remapeado: as listras.
  5. Cos da mesma fase, Multiply pela amplitude, pela frequência e por fromPole, e depois Clamp em -1..1: o sombreamento das dobras.
  6. Mix das duas cores pelas listras, depois Mix sobre 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

waving-flag.fragGLSL
#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);}
waving-flag.gdshaderShader do Godot
// 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