Wipe linear
Uma borda reta e suave que atravessa o quad em qualquer ângulo, acelerando e desacelerando, e depois volta.
- 01ObjetivoRevelar
- 02TécnicaWipes e transições
- 03ExecuçãoWipe linear
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.
clampClampclampclampclamp UVTelaTempo01 Progresso
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep Entrada02 Suavizar início e fim
dotDotdotdotdot Entrada03 Posição ao longo da transição
mixMixmixmixmix Entrada04 Frente
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep Entrada05 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
smoothstepUm limiar suave. Vale 0 abaixo da primeira borda, 1 acima da segunda e segue uma curva em S com as pontas planas entre elas. Smoothstep Um limiar suave. Vale 0 abaixo da primeira borda, 1 acima da segunda e segue uma curva em S com as pontas planas entre elas. Easing Uma curva que entorta um progresso de 0 a 1 para que o movimento acelere ou desacelere em vez de andar em velocidade constante.t = clamp((x − e0) / (e1 − e0), 0, 1); s = t² (3 − 2t)ease-in-out: e = p² (3 − 2p)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. Produto escalar Multiplica dois vetores componente a componente e soma os resultados. Para vetores unitários é o cosseno do ângulo entre eles, de 1 (mesma direção) a -1 (opostos). 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.radians transforma a Direção num
ângulo, cos e sin transformam o ângulo num vetor unitário, e o produto escalarMultiplica dois vetores componente a componente e soma os resultados. Para vetores unitários é o cosseno do ângulo entre eles, de 1 (mesma direção) a -1 (opostos).dot(a, b) = |a| |b| cos θuv = gl_FragCoord.xy / resolution
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
#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);}// 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
- dot (abre em uma nova aba)Referência
- smoothstep (abre em uma nova aba)Referência
- mix (abre em uma nova aba)Referência
- radians (abre em uma nova aba)Referência
- Easing Functions Cheat Sheet (abre em uma nova aba)Referência
- <easing-function> (abre em uma nova aba)Documentação
- CanvasItem shaders (abre em uma nova aba)Documentação