Pular para o conteúdo
Revelar · Wipes e transiçõesIniciante

Wipe linear

Uma borda reta e suave que atravessa o quad em qualquer ângulo, acelerando e desacelerando, e depois volta.

UVTelaTempoSuperfície
Abrir no Playground
  1. 01ObjetivoRevelar
  2. 02TécnicaWipes e transições
  3. 03ExecuçãoWipe linear
Quad de tela cheia

Parâmetros

2.4
20
0.060

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. clampClampclampclampclamp
    UV
    Tela
    Tempo

    01 Progresso

  2. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    02 Suavizar início e fim

  3. dotDotdotdotdot

    03 Posição ao longo da transição

  4. mixMixmixmixmix

    04 Frente

  5. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    05 Máscara

Como funciona

O card passa uma máscara branca pelo quad e volta, sem parar. Onde a máscara vale 1 a imagem nova apareceria, onde vale 0 a antiga continua, e entre as duas há uma borda esfumada. Direção gira a borda para qualquer ângulo, Suavidade da borda a esfuma, e Duração define quanto tempo leva uma passada.

1. Progresso

Uma transição precisa de um número que vá de 0 a 1 ao longo da duração. Aqui ele vem do relógio, para que a prévia se repita sozinha: fract(i.time / p_duration) é um dente de serra que sobe de 0 a 1 a cada duração, e 1.0 - abs(1.0 - 2.0 * x) o dobra numa onda triangular que sobe e depois desce. Esticar isso por 1,25 e limitar a 0..1 achata o topo e a base, então o wipe descansa um instante todo revelado e todo escondido antes de voltar. Num jogo ou numa página, esse passo inteiro vira um único uniform animado por um script (os guias de plataforma mostram como).

2. Suavizar início e fim

Um progresso linear faz a borda sair em velocidade máxima e parar de repente, o que parece mecânico. Passá-lo por um smoothstep de 0 a 1 é o ease-in-out mais barato: a cúbica p * p * (3 - 2p) começa plana, acelera no meio e achata de novo, então a borda arranca devagar e assenta no lugar. Qualquer outra curva do fundamento de easing pode entrar aqui.

3. Posição ao longo da transição

A forma de um wipe linear é a distância de cada pixel ao longo de uma direção. radians transforma a Direção num ângulo, cos e sin transformam o ângulo num vetor unitário, e o produto escalar do UV centralizado com esse vetor mede o quanto cada pixel avançou nessa direção.

Essa distância não vai de 0 a 1 sozinha. Numa diagonal, os cantos do quad ficam mais longe do centro do que as bordas, por 0.5 * (|x| + |y|) do vetor de direção. Dividir pelo dobro desse alcance e somar 0,5 leva o primeiro canto que o wipe toca a 0 e o último a 1 em qualquer ângulo, então um wipe diagonal dura o mesmo que um horizontal e nunca termina antes da hora.

4. Frente

A frente é onde a borda está no momento. mix(-p_softness, 1.0, eased) a leva de uma suavidade antes do 0 até exatamente 1. Começar uma suavidade antes faz com que, no progresso 0, até a parte esfumada da borda fique fora do quad, então a máscara vale 0 sem resto, e no progresso 1 vale 1 sem resto.

5. Máscara

smoothstep(front, front + p_softness, along) vale 0 atrás da frente e 1 à frente dela, com uma rampa suave do tamanho da Suavidade da borda no meio. Um menos ele é a máscara: os pixels por onde a frente passou valem 1. O papel de saída do card é uma máscara, então o valor não é codificado como cor: a prévia o mostra como ele é, branco onde a imagem nova estaria e preto onde a antiga continua. Quem usa a máscara a lê do canal vermelho e a aplica como a opacidade de uma cobertura ou como o fator de mistura entre duas imagens.

Um step(front, along) duro também funcionaria, mas numa diagonal ele mostra uma escadinha de um pixel de altura. Uma suavidade de poucos pixels já a esconde, e é por isso que a suavidade nunca chega a zero.

Monte no GLSL

O botão de copiar entrega um shader de fragmento WebGL2 completo. Desenhe-o num quad ou num triângulo de tela inteira com um shader de vértice que escreva v_uv de 0 no canto inferior esquerdo até 1 no superior direito. O main() também lê v_position, v_normal e v_view, então o shader de vértice precisa declará-los e escrevê-los, mas zeros servem: o efeito só usa uv.

Do jeito que está, o shader se repete para sempre em cima de u_time, o que serve para uma prévia. Uma transição de verdade roda uma vez, quando você manda. Troque as duas primeiras linhas de effect() por um uniform e controle-o pelo JavaScript:

uniform float u_progress; // 0 = escondido, 1 = revelado

// em effect(), no lugar de wave e progress:
float progress = u_progress;
const start = performance.now();
function frame(now) {
  const t = Math.min((now - start) / 600, 1); // um wipe de 600 ms
  gl.uniform1f(progressLocation, t);
  draw();
  if (t < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

O easing continua no shader, então o script só precisa mandar uma rampa reta.

Para usar a máscara como transição entre duas imagens, e não como uma camada por cima, mantenha a forma e a borda e misture duas texturas com ela no seu próprio shader de fragmento: fragColor = mix(texture(oldScene, uv), texture(newScene, uv), mask);. As duas cenas precisam ser renderizadas em texturas antes, e essa é a versão de pós-processamento desta técnica.

Monte no Godot

Num VisualShader (modo CanvasItem), monte o eixo a partir da direção com Radians, Cos e Sin num Vector2Compose, calcule num nó Dot o produto escalar dele com o UV menos 0,5 e ligue o resultado num SmoothStep cujas bordas são a frente e a frente mais a suavidade; um OneMinus disso vai para Alpha.

Adicione um ColorRect chamado Wipe dentro de um CanvasLayer, ponha as âncoras em Full Rect para ele cobrir a janela, dê a ele um novo ShaderMaterial e cole o shader nele. É um shader canvas_item e, do jeito que é exportado, desenha a própria máscara, opaca: branco por onde o wipe passou, preto no resto.

Para uma transição de cena, transforme a máscara numa cobertura: uma cor cujo alfa é a máscara, para o jogo aparecer por onde o wipe ainda não passou. Troque a última linha de fragment():

COLOR = vec4(0.0, 0.0, 0.0, frag_out.r);

Depois faça o wipe rodar uma vez, na hora certa. Adicione um uniform e troque as duas primeiras linhas de effect():

uniform float amount : hint_range(0.0, 1.0) = 0.0;

// em effect(), no lugar de wave e progress:
float progress = amount;

e anime-o por um script, trocando a cena enquanto a tela está toda coberta:

func change_scene(path: String) -> void:
	var material := $CanvasLayer/Wipe.material as ShaderMaterial
	var tween := create_tween()
	tween.tween_method(func(v: float) -> void: material.set_shader_parameter("amount", v), 0.0, 1.0, 0.5)
	await tween.finished
	get_tree().change_scene_to_file(path)
	tween = create_tween()
	tween.tween_method(func(v: float) -> void: material.set_shader_parameter("amount", v), 1.0, 0.0, 0.5)

O CanvasLayer precisa morar num autoload (ou sobreviver à troca de outro jeito) para a cobertura continuar na tela enquanto a próxima cena carrega. O easing fica dentro do shader, então um tween linear basta.

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

linear-wipe.fragGLSL
#version 300 es// Wipes & transitions → Linear wipe, 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_duration; // Duration (s): 2.4 (0.5 to 6.0)uniform float p_direction; // Direction (degrees): 20.0 (0.0 to 360.0)uniform float p_softness; // Edge softness: 0.06 (0.002 to 0.4)in 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;};vec4 effect(Inputs i) {	// A triangle wave, stretched past 0..1 and clamped, so the wipe rests a moment when fully shown and fully hidden	float wave = 1.0 - abs(1.0 - 2.0 * fract(i.time / p_duration));	float progress = clamp(wave * 1.25 - 0.125, 0.0, 1.0);	float eased = smoothstep(0.0, 1.0, progress);	float angle = radians(p_direction);	vec2 axis = vec2(cos(angle), sin(angle));	// The quad's corners project to between -reach and +reach on the axis, so this maps corner to corner onto 0..1 at	// any angle, and the wipe takes the whole duration to cross	float reach = 0.5 * (abs(axis.x) + abs(axis.y));	float along = dot(i.uv - 0.5, axis) / (2.0 * reach) + 0.5;	// The front starts one softness before 0 and ends at 1, so the soft edge is fully off the quad at both ends	float front = mix(-p_softness, 1.0, eased);	float mask = 1.0 - smoothstep(front, front + p_softness, along);	return vec4(vec3(mask), 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);	// A mask shows as is: coverage in .r, not encoded; opaque, so alpha is 1	fragColor = vec4(clamp(frag_out.rgb, 0.0, 1.0), 1.0);}
linear-wipe.gdshaderShader do Godot
// Wipes & transitions → Linear wipe, 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 duration : hint_range(0.5, 6.0, 0.1) = 2.4;uniform float direction : hint_range(0.0, 360.0, 1.0) = 20.0;uniform float softness : hint_range(0.002, 0.4, 0.002) = 0.06;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};vec4 effect(Inputs i) {	// A triangle wave, stretched past 0..1 and clamped, so the wipe rests a moment when fully shown and fully hidden	float wave = 1.0 - abs(1.0 - 2.0 * fract(i.time / duration));	float progress = clamp(wave * 1.25 - 0.125, 0.0, 1.0);	float eased = smoothstep(0.0, 1.0, progress);	float angle = radians(direction);	vec2 axis = vec2(cos(angle), sin(angle));	// The quad's corners project to between -reach and +reach on the axis, so this maps corner to corner onto 0..1 at	// any angle, and the wipe takes the whole duration to cross	float reach = 0.5 * (abs(axis.x) + abs(axis.y));	float along = dot(i.uv - 0.5, axis) / (2.0 * reach) + 0.5;	// The front starts one softness before 0 and ends at 1, so the soft edge is fully off the quad at both ends	float front = mix(-softness, 1.0, eased);	float mask = 1.0 - smoothstep(front, front + softness, along);	return vec4(vec3(mask), 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);	// A mask shows as is: coverage in .r, not encoded	COLOR = vec4(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