Easing e tweening
Transforme o tempo num progresso de 0 a 1, curve esse progresso com uma curva de easing e monte com ele animações em etapas, escalonadas e de vaivém.
Como funciona
Um tween leva um valor de um estado a outro num tempo definido: uma cortina da esquerda para a direita, um clarão se
apagando, um painel deslizando para dentro. Num shader são três passos. Transforme o tempo num progresso de 0 a 1,
curve o progresso com uma curva de easingUma curva que entorta um progresso de 0 a 1 para que o movimento acelere ou desacelere em vez de andar em velocidade constante. 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. Interpolação linear (mix) Mistura de a até b por um fator t, em linha reta. O GLSL chama de mix; o HLSL e a maioria dos editores de nós chamam de lerp.ease-in-out: e = p² (3 − 2p)mix(a, b, t) = a (1 − t) + b t
float p = clamp((time - startTime) / duration, 0.0, 1.0); // progresso: 0 antes, 1 depois
float e = p * p * (3.0 - 2.0 * p); // com easing (a curva do smoothstep)
float x = mix(-0.2, 1.2, e); // o valor animado
Manter os três separados é o ponto: a curva de easing pode mudar sem mexer no tempo, e o mesmo progresso pode mover vários valores ao mesmo tempo.
Curvas de easing
Movimento em velocidade constante parece mecânico, porque objetos reais aceleram e desaceleram. Curvas de easing são curvas de modelagem que mantêm 0 em 0 e 1 em 1, com nomes que dizem onde elas são suaves:
float easeInQuad(float p) { return p * p; } // começa devagar
float easeOutQuad(float p) { return 1.0 - (1.0 - p) * (1.0 - p); } // termina devagar
float easeInOutQuad(float p) { // devagar nas duas pontas
return p < 0.5 ? 2.0 * p * p : 1.0 - 2.0 * (1.0 - p) * (1.0 - p);
}
float easeOutCubic(float p) { float q = 1.0 - p; return 1.0 - q * q * q; }
Potências maiores deixam a parte lenta mais longa e a parte rápida mais brusca. Ease-out combina com coisas que chegam à tela, porque elas se acomodam no lugar; ease-in combina com coisas que saem, porque elas ganham velocidade; ease-in-out combina com movimentos de uma posição de repouso para outra.
Duas famílias passam das pontas de propósito. Um easing “back” ultrapassa o alvo e volta, como um painel que passa um pouco do lugar e retorna:
float easeOutBack(float p) {
const float k = 1.70158; // o exagero clássico, cerca de 10%
float q = p - 1.0;
return 1.0 + (k + 1.0) * q * q * q + k * q * q;
}
Um easing elástico também ultrapassa, com um seno que vai diminuindo por cima. O de quique, uma série de arcos de queda, fica dentro de 0–1, mas bate na ponta várias vezes. Curvas que ultrapassam produzem valores acima de 1 ou abaixo de 0, então confira se o que elas movem aguenta isso: uma cor pode precisar de um clamp, enquanto uma posição pode simplesmente passar do alvo.
O CSS descreve a maioria dos easings como curvas cubic-bezier(). Um shader não consegue calcular essas curvas de
forma barata, porque achar o valor num dado instante exige resolver uma equação cúbica, então os shaders escrevem a
curva direto como um polinômio, como os de cima.
Animação em etapas e escalonada
Muitas animações têm fases: uma máscara cresce, depois uma cor muda, depois tudo some. Dê a cada fase a própria janela do progresso geral remapeando-o:
float stage(float p, float a, float b) { return clamp((p - a) / (b - a), 0.0, 1.0); }
float grow = easeOutCubic(stage(p, 0.0, 0.5)); // primeira metade
float shift = stage(p, 0.3, 0.8); // se sobrepõe ao fim do crescimento
float fade = easeInQuad(stage(p, 0.7, 1.0)); // os últimos 30%
As janelas podem se sobrepor, o que costuma parecer mais suave do que uma depois da outra à risca. Escalonar é o mesmo
remapeamento com um atraso por elemento: numa fileira de ladrilhos, stage(p, i * 0.05, i * 0.05 + 0.4) começa o
ladrilho i um instante depois do anterior, a cascata usada em transições e em revelações de interface.
Vaivém e loops
Um tween que deve se repetir precisa de um progresso que se repita. fract(time / period) recomeça em 0 a cada
período, o que dá um salto para trás. Para ir e voltar, dobre esse valor numa onda triangular que sobe e depois desce:
float loop = fract(time / 2.0); // 0..1 a cada 2 segundos
float pingPong = 1.0 - abs(loop * 2.0 - 1.0); // 0..1..0 a cada 2 segundos
float e = easeInOutQuad(pingPong); // com easing nos dois sentidos
Para segurar em cada ponta antes de virar, remapeie o valor dobrado: stage(pingPong, 0.1, 0.9) descansa em 0 e em 1
por um instante, um quinto de cada metade, antes de seguir.