Truque de animação usando suavização exponencial
- Há uma técnica de animação simples que uso quase sempre desde que comecei a trabalhar com animação.
- Essa técnica é usada em vários lugares, como rotação e movimento de câmera, movimentação de personagens em jogos por turnos, deslocamento de elementos de UI e suavização de mudanças de volume em bibliotecas de áudio.
- Essa técnica não é nova; você talvez já tenha ouvido falar dela ou até a tenha usado, mas vou explicar alguns exemplos e os princípios matemáticos por trás dela.
Botão de alternância
- Ao criar componentes de UI, suponha, por exemplo, que você esteja fazendo um botão de alternância.
- A posição do interruptor do botão é calculada com base no estado: quando está ligado, fica em
max_x; quando está desligado, em min_x.
- Isso funciona bem, mas sem animação parece um pouco sem vida.
- A animação não só adiciona um toque visual, como também ajuda o usuário a entender o que está acontecendo.
- Em vez de mover o indicador do toggle imediatamente para a nova posição, ele passa a se mover de forma suave.
- Agora é preciso atualizar a animação, e isso tem a desvantagem de parecer que ela se move em velocidade constante.
- Dá para adicionar uma função de easing aqui, por exemplo
3t^2-2t^3 ou sqrt(t).
- As diferenças entre essas funções de easing ficam mais visíveis quando a animação é reproduzida lentamente.
- Agora, em vez de atualizar a posição do interruptor, é preciso acompanhar o estado da animação.
- Ao usar
sqrt, é necessário usar explicitamente funções de easing diferentes dependendo da direção da animação.
- Qual delas parece melhor é uma questão de gosto, mas
sqrt parece a melhor. Ela faz o interruptor começar muito rápido, mas desacelerar bem à medida que se aproxima do destino.
- A desvantagem dessa versão é que, mesmo no caso mais simples, ela exige bastante gerenciamento e, se o usuário clicar no meio da animação, ocorre uma descontinuidade com um salto repentino.
Movimento de câmera
- Suponha uma situação em que o mapa e a câmera rolam ou se deslocam ao redor.
- Também faz sentido adicionar animação aqui.
- É apresentado um código que interpola com velocidade constante.
- A tremulação que acontece depois que a animação termina ocorre porque
target.x - position.x alterna entre valores positivos e negativos.
- Em vez de
sign(delta), é necessária uma função que faça clamp do delta.
- Esse método é consideravelmente complexo para algo simples.
- Fica estranho quando a velocidade da animação é maior do que a conclusão da animação.
- Também seria possível ignorar a entrada do usuário enquanto a animação está ativa, mas isso proporciona uma experiência extremamente irritante.
- A solução perfeita, claro, é a suavização exponencial.
- O código quase não muda em comparação com o exemplo do botão de alternância.
Como funciona por dentro
- É explicado o que é
1 - exp(- speed * dt) e como isso funciona.
- Começa-se pela versão mais simples, tornando a velocidade proporcional à distância entre a
position atual e a nova posição para onde deve se mover, target, fazendo com que o movimento seja mais rápido.
- Esse método não precisa manter nenhum estado além da posição atual e da posição-alvo, e se ajusta automaticamente mesmo que
target mude de repente.
- Mas há um pequeno problema: se
speed * dt for maior que 1, a posição ultrapassa o alvo.
- Para resolver isso, é possível limitar o valor em 1.
- O motivo de
speed * dt ficar grande demais é que o valor de speed é grande demais ou dt é grande demais.
- No caso de animações, seria ótimo se tudo funcionasse perfeitamente ao aplicar
dt.
Equação diferencial (ah, não)
- É apresentada uma abordagem em duas etapas para resolver o problema.
- O fato de
position += (target - position) * speed * dt funcionar para dt pequeno, mas falhar para dt grande, é um problema típico de solução numérica de equações diferenciais.
- É examinado o que essa equação resolve.
- É explicado que
position += (target - position) * (1 - exp(- speed * dt)) é a fórmula correta para qualquer dt.
Escolhendo a velocidade
- Em geral, pensamos em animação em termos de duração.
- Ao usar a fórmula exponencial, tecnicamente a animação nunca termina e se completa ao longo de um tempo infinito.
- O que o parâmetro
speed significa é que 1 / speed é o tempo em que position fica e = 2.71828... vezes mais próxima de target.
Suavização exponencial
- Se você procurar por "suavização exponencial", vai encontrar um artigo da wiki que parece totalmente sem relação, mas que na verdade traz uma fórmula muito parecida com a discutida neste post.
- Se supusermos que
dt é sempre o mesmo e que target muda a cada iteração, podemos indexar os valores pelo número da iteração e calcular algo como position[i] = (target[i] - position[i - 1]) * factor.
Título do último parágrafo
- Tenho a ideia deste post há vários meses e fico feliz por finalmente tê-lo concluído.
- Obrigado por acompanhar e ler o log de desenvolvimento.
Opinião do GN⁺
- Este artigo explica a técnica de suavização exponencial usada para tornar animações mais suaves e naturais. Essa técnica contribui para melhorar a experiência do usuário e aumentar a intuitividade da interface.
- A suavização exponencial também pode ser útil para simular movimento físico, por exemplo, tornando mais naturais o movimento de personagens ou o deslocamento de câmera no desenvolvimento de jogos.
- Essa técnica é especialmente eficaz para representar visualmente mudanças de estado em elementos de interface do usuário. Por exemplo, ela pode deixar mais suave o movimento de sliders ou interruptores.
- De um ponto de vista crítico, a suavização exponencial pode dificultar o controle preciso da velocidade e da duração da animação. Isso pode ser uma limitação quando o designer quer ajustar com precisão um efeito de animação específico.
- Outras bibliotecas ou frameworks de animação com funcionalidades semelhantes incluem GreenSock Animation Platform(GSAP) e anime.js, que oferecem controle mais refinado da animação junto com várias funções de easing.
- Ao adotar a técnica de suavização exponencial, é preciso encontrar um equilíbrio entre a naturalidade da animação e a precisão do controle. A vantagem dessa escolha é a melhoria da experiência do usuário; a desvantagem é que o ajuste preciso do timing da animação pode ser difícil.
1 comentários
Opiniões do Hacker News
Resumo do primeiro comentário:
smoothstep(), mas sim uma abordagem sem estado (stateless) que lida com entradas variadas de forma consistente.exponential smoothing) tem o problema de se aproximar do destino sem nunca realmente alcançá-lo.inertial scrolling), menciona que foi útil adicionar um termo de atrito (falso).Resumo do segundo comentário:
exponential lerp) é útil, mas pouco conhecido, e alguns jogos sofrem com problemas de animação por usarem interpolação linear menos precisa.Resumo do terceiro comentário:
sqrt) é melhor do que cúbica (cubic) para interruptores do tipo toggle.Resumo do quarto comentário:
Resumo do quinto comentário:
lazy-easy.Resumo do sexto comentário:
Resumo do sétimo comentário:
Resumo do oitavo comentário:
Resumo do nono comentário:
Resumo do décimo comentário: