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.

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:
- a imagem é convertida parcialmente para luminância e contraste;
- um filtro de borda inspirado em Sobel encontra contornos;
- ruído fractal e grão criam uma revelação irregular de lápis;
- cinco sistemas de coordenadas definem direções, larguras e profundidades distintas para os feixes;
- halos, núcleos, trilhas e cabeças de luz recebem intensidades diferentes;
- máscaras combinam blueprint, iluminação e arte final;
- 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:
- um vídeo WebM/MP4 com poster;
- o shader simplificado com máscaras pré-calculadas;
- 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.













