Sobre o portfólio

SEO

100 palavras

  • Unity
  • Multiplayer
  • LiveOps
  • C#

Leonardo Lycan é Lead Game Developer e especialista em desenvolvimento de jogos Unity, arquitetura multiplayer, sistemas de gameplay, LiveOps e produção técnica. Este portfólio reúne projetos publicados, protótipos jogáveis, estudos de produto e artigos sobre engenharia de jogos, IA aplicada, Web3, onboarding e experiências digitais. O trabalho combina liderança de equipes, programação C#, integração de serviços, otimização de performance e comunicação clara entre design, arte e negócio. Para projetos, consultoria ou colaboração, Leonardo oferece uma abordagem prática: entender o problema, construir uma solução robusta e transformar requisitos complexos em experiências memoráveis para jogadores e produtos digitais com impacto mensurável hoje.

Voltar

Desenvolvimento web · 23 de agosto de 2026

Animação sem vídeo: quando WebGL vale o custo?

Um estudo honesto sobre construir uma abertura cinematográfica com WebGL, GSAP e uma imagem estática — incluindo tentativas, frustrações e quando um vídeo teria sido a decisão melhor.

Animação sem vídeo: quando WebGL vale o custo?

A abertura da página About parece um pequeno filme: a cena começa quase preta, linhas de desenho aparecem, cinco feixes atravessam a caverna e a arte de Resonance termina completamente revelada. Ela dura 6,2 segundos, mas não existe um arquivo de vídeo por trás dela.

O resultado é produzido no navegador. Uma imagem responsiva vira textura de um canvas WebGL2; um fragment shader calcula desenho, ruído, luz, penumbra e revelação; requestAnimationFrame conduz o progresso; GSAP cuida da transição da interface. Foi uma solução tecnicamente interessante e uma experiência de aprendizado real. Também foi mais cara, frágil e demorada do que a aparência final sugere.

Este não é um argumento de que shader é melhor do que vídeo. É o registro de quando a escolha valeu a pena, quando não valeu e como eu reduziria o mesmo problema hoje.

Cena de Resonance usada como textura e estado final da animação da página About

A mesma imagem responsiva é o input do shader e o estado final da página.

O problema parecia menor do que era

A intenção inicial era simples: transformar uma única arte em uma entrada que comunicasse processo — do conceito à imagem final — sem colocar um vídeo pesado no topo da página.

Essa frase esconde decisões diferentes:

  • como extrair um desenho reconhecível de uma imagem colorida;
  • como fazer luzes parecerem volumes independentes, não cinco triângulos iguais;
  • como revelar a cor sem produzir uma troca brusca de camada;
  • como manter texto e imagem dentro da mesma progressão;
  • como adaptar resolução, proporção e densidade de pixels;
  • como terminar em um estado estático quando WebGL, movimento ou dados não fossem adequados.

Um vídeo teria condensado essas decisões em frames exportados. A solução procedural transferiu todas elas para código e runtime.

O que foi construído de fato

A implementação usa uma textura, um retângulo de tela inteira e um draw call por frame. A simplicidade geométrica termina aí. Dentro do fragment shader, cada pixel passa por várias etapas:

  1. a imagem é convertida parcialmente para luminância e contraste;
  2. um filtro de borda inspirado em Sobel encontra contornos;
  3. ruído fractal e grão criam uma revelação irregular de lápis;
  4. cinco sistemas de coordenadas definem direções, larguras e profundidades distintas para os feixes;
  5. halos, núcleos, trilhas e cabeças de luz recebem intensidades diferentes;
  6. máscaras combinam blueprint, iluminação e arte final;
  7. uma transição final iguala o canvas à imagem HTML que continuará visível.

O progresso não avança “um valor por frame”. Ele é calculado pelo timestamp entregue por requestAnimationFrame, evitando que telas de 120 Hz reproduzam a sequência duas vezes mais rápido. A documentação do MDN recomenda exatamente esse cuidado e informa que o navegador normalmente pausa essas chamadas em abas ocultas, reduzindo trabalho e consumo desnecessários.

O mesmo progresso alimenta variáveis CSS que revelam a interface. Isso foi uma vantagem real sobre um filme independente: texto, máscara e cena compartilham um relógio, em vez de tentar adivinhar o currentTime de outra mídia.

O canvas observa mudanças de tamanho, limita a densidade interna a 1,5 vezes o tamanho CSS e libera textura, buffers, programa e shaders ao terminar. Se o usuário pede menos movimento, ativa economia de dados ou abre a página sem WebGL2 disponível, a experiência abandona a sequência e apresenta o estado final.

Por que não usar vídeo parecia correto

Havia motivos legítimos para tentar uma solução no navegador:

  • um único estado visual: a textura animada e a imagem final vêm do mesmo asset;
  • sincronização com o DOM: títulos e ações podem responder ao mesmo progresso;
  • responsividade: o navegador escolhe uma imagem de 640, 960 ou 1600 pixels antes de enviá-la ao shader;
  • parametrização: direção, largura e intensidade dos feixes continuam editáveis;
  • fallback imediato: a página pode saltar diretamente para o resultado sem depender de um frame específico;
  • nenhum novo vídeo: não é necessário manter renderizações MP4 e WebM além da imagem usada pela página.

Vídeo não elimina decisões de performance. O elemento <video> ainda exige estratégia de poster, preload, codecs e fontes alternativas. A documentação do MDN lembra que navegadores não suportam todos os formatos da mesma forma; o web.dev observa que um vídeo com autoplay tende a iniciar o download imediatamente e que a primeira imagem precisa aparecer rápido para não prejudicar a percepção de carregamento.

Mas evitar um arquivo de vídeo não significa automaticamente criar uma solução mais leve. Bytes transferidos são apenas uma parte do custo. Shader, integração, QA, GPU, manutenção e tempo de direção também pertencem à conta.

Onde a implementação começou a doer

Direção de arte virou matemática

“A luz está artificial” não apontava para uma propriedade única. A correção podia estar na origem, direção, largura inicial, dispersão, halo, queda de intensidade, ruído ou momento de entrada. Cada ajuste precisava ser traduzido em coordenadas normalizadas e funções como smoothstep, exponenciais e misturas.

Os cinco feixes inicialmente corriam o risco de parecer um leque uniforme. Para quebrar essa leitura, cada um recebeu direção, largura, taper e força próprios. Isso resolveu o desenho, mas espalhou a direção de arte por dezenas de constantes GLSL sem nomes editoriais ou uma ferramenta de preview.

O fim pulava mesmo quando a geometria coincidia

O canvas ficava sobre a imagem HTML. Ao desaparecer, a luz volumétrica ainda presente no shader sumia antes da camada inferior assumir a cena. O olho interpretava isso como um salto de posição ou exposição, embora canvas e imagem compartilhassem 16:9.

A correção foi uma fase explícita de handoff: entre 83% e 98,5% do progresso, o shader abandona gradualmente o tratamento e converge para a mesma textura filtrada da imagem final. Esse detalhe não estava na ideia original, mas foi necessário para esconder a troca de renderer.

Carregamento virou estado de animação

O primeiro fluxo apenas aguardava image.decode(). Isso não bastava: navegadores podem rejeitar a promessa mesmo quando a imagem atual é utilizável, e uma espera indefinida deixaria a página presa na preparação. A implementação passou a verificar complete, naturalWidth e uma corrida com timeout de 1,2 segundo. Se a textura não estiver pronta, a página termina estática.

A performance não podia ser inferida no desktop principal

O shader faz amostras de borda e várias chamadas de ruído para todos os pixels do canvas. Em uma tela de alta densidade, o custo cresce com a área renderizada, não com a quantidade de elementos visíveis. Limitar o pixel ratio foi uma defesa prática, mas também uma admissão: nitidez e custo precisavam ser negociados.

O guia de boas práticas de WebGL do MDN recomenda tratar memória como um orçamento por pixel, considerar um back buffer menor e evitar assumir que uma configuração rápida em uma máquina será portátil. Ele também alerta que alpha: false, usado aqui para um canvas opaco, pode ser mais caro em algumas plataformas — exatamente o tipo de detalhe que precisa ser medido, não presumido.

A exploração deixou dívida visível

O commit de referência preserva 705 linhas no controlador da animação. A página também mantém aproximadamente 315 linhas de uma abordagem SVG marcada como legacy, escondida ao lado do canvas ativo. Isso é evidência útil do processo, mas não é uma arquitetura que eu manteria em produção.

Uma tentativa descartada deveria ter virado documentação ou sandbox, não continuar no HTML público. Manter duas representações aumenta o custo cognitivo, dificulta saber qual camada ainda importa e torna qualquer pedido pequeno mais arriscado.

Outro aprendizado veio dos próprios assets. Para esta imagem, os arquivos AVIF medidos ficaram maiores do que os WebP equivalentes nas três resoluções, embora o <picture> priorize AVIF. Formato moderno não garante arquivo menor; o output real precisa decidir a ordem das fontes.

WebGL ou vídeo: a comparação honesta

Critério Animação procedural Vídeo exportado
Iteração visual exige alterar código, parâmetros e recompilar rápida em uma ferramenta de motion, mas requer novo render
Sincronização com interface compartilha o mesmo progresso do DOM exige eventos, relógio da mídia ou uma sequência separada
Responsividade matemática e imagem podem reagir ao layout enquadramento e resolução são fixados na exportação
Consistência visual pode variar por GPU, navegador e precisão frames já estão decididos pelo encoder
Transferência reutiliza a imagem; adiciona JS/shader adiciona poster e uma ou mais versões do vídeo
Custo em runtime usa CPU/GPU durante a sequência usa download e decodificação dedicada de vídeo
Fallback pode terminar instantaneamente no estado final precisa de poster, primeiro frame ou imagem alternativa
Manutenção alta quando a arte vive em constantes técnicas baixa no código, alta quando a arte precisa ser reexportada

Vídeo venceria se a prioridade fosse entregar rapidamente uma composição fixa e aprovada. A sequência não responde ao cursor, ao scroll ou a dados; portanto, grande parte do potencial interativo de WebGL não é usada.

WebGL vence quando a progressão precisa controlar outras partes da interface, quando os parâmetros devem continuar editáveis ou quando a animação deve se adaptar a estados que ainda não existem no momento da exportação.

Valeu a pena neste caso?

Como investimento de produção para uma única abertura fixa: provavelmente não. O custo de construir e ajustar a solução ficou desproporcional aos 6,2 segundos que o visitante vê uma vez.

Como estudo de engenharia visual e peça de portfólio: sim. A implementação demonstra integração entre arte, shader, performance, estados de navegador e fallback. Também deixou aprendizados que um arquivo de vídeo pronto esconderia.

As duas respostas podem ser verdadeiras. Uma solução pode gerar conhecimento e ainda não ser a escolha econômica que eu repetiria.

O que eu faria diferente

1. Definiria frames de aceitação antes do shader

Eu aprovaria seis imagens: início, primeiro feixe, cruzamento das luzes, expansão, handoff e estado final. Cada frame teria critérios de composição, contraste e área revelada. Isso converteria “parece estranho” em uma comparação objetiva.

2. Construiria um laboratório isolado

O shader deveria nascer em uma página mínima com scrub de progresso, toggles por camada e controles nomeados. Integrar diretamente na About misturou direção de arte, responsividade, texto, navegação e fallback cedo demais.

3. Pré-calcularia o que não precisa mudar

Contorno, blueprint e parte do grão poderiam ser texturas produzidas offline. O shader ficaria responsável somente por máscaras de luz, expansão e mistura. Isso reduziria amostras por pixel, linhas de código e divergência entre máquinas sem perder a sincronização procedural.

4. Transformaria fases em dados

Entradas dos feixes, forças, larguras e cores deveriam viver em uma configuração legível, com nomes ligados ao storyboard. Hoje, constantes espalhadas no GLSL tornam comparação e ajuste desnecessariamente difíceis.

5. Mediria três protótipos antes de escolher

Eu produziria:

  1. um vídeo WebM/MP4 com poster;
  2. o shader simplificado com máscaras pré-calculadas;
  3. uma versão CSS/SVG limitada a opacidade e transform.

Depois compararia bytes, LCP, tempo de GPU, estabilidade de frame, esforço de edição e comportamento em Safari, Chrome, mobile e modo de movimento reduzido. A decisão viria da evidência, não do fascínio pela técnica.

6. Removeria a implementação vencida

O SVG experimental poderia permanecer no Git ou em uma página de laboratório, mas não no markup final. O runtime deve carregar somente a solução ativa e o fallback necessário.

A abordagem que eu escolheria hoje

Para a mesma direção, eu usaria uma solução híbrida:

  • imagem responsiva como estado final e fallback;
  • uma ou duas máscaras pré-calculadas para desenho e iluminação;
  • shader curto para mover os feixes e misturar as camadas;
  • progresso único para canvas e interface;
  • saída instantânea para prefers-reduced-motion, economia de dados, aba oculta ou falha de WebGL;
  • limite de resolução validado por benchmark, não por sensação.

Isso preserva a principal vantagem da implementação atual — a cena e o DOM no mesmo relógio — sem recalcular em tempo real detalhes que já eram conhecidos.

Se a animação continuasse completamente fixa, sem sincronização editorial e com prazo curto, eu escolheria vídeo. Exportaria ao menos WebM e MP4, usaria uma imagem final como poster/fallback e mediria o custo real antes de publicar.

A decisão não é “código ou criatividade”

Vídeo transforma movimento em mídia. WebGL transforma movimento em sistema. O primeiro fixa decisões e simplifica o runtime; o segundo mantém decisões abertas e transfere complexidade para engenharia.

A pergunta útil não é qual tecnologia parece mais avançada. É quais decisões ainda precisam continuar vivas depois que a animação é publicada.

Quando tudo já foi decidido, vídeo costuma ser honesto e eficiente. Quando layout, estado, interação ou dados ainda precisam alterar o resultado, uma animação procedural ganha valor. Entre os dois, máscaras pré-calculadas e um shader pequeno resolvem uma quantidade surpreendente de casos.

Referências