3D em Tempo Real & Motion Graphics para Web
WebGL, Three.js, React Three Fiber, GLSL, WebGPU, GSAP, ScrollTrigger, Lottie e Rive — organizados como uma trilha única, com exercícios práticos e um plano direto para transformar isso em trabalho remunerado como creative developer.
Leia isto primeiro
Esta apostila foi desenhada para o mercado de trabalho, não para prova. Isso muda três coisas na forma de estudar:
- Cada módulo termina em código que roda. Não avance sem reproduzir os exemplos. O mercado paga por quem entrega, não por quem "entende o conceito".
- Os exercícios viram portfólio. Os projetos dos boxes Exercício foram escolhidos para se transformarem em peças publicáveis (CodePen, site pessoal, Awwwards).
- Os boxes roxos falam de dinheiro. Os boxes Mercado conectam a técnica ao que aparece em vaga, briefing e orçamento de freela.
Ferramentas mínimas
- Editor: VS Code (extensões: Shader languages support, glsl-canvas, ESLint, Prettier).
- Runtime: Node LTS +
npm create vite@latestcomo base de todo projeto. - Playground: CodePen / CodeSandbox para experimentos; Shadertoy para shaders.
- 3D DCC: Blender (gratuito) — obrigatório a partir do Módulo 09.
- Navegador: Chrome com DevTools (aba Performance) e a extensão Spector.js para inspecionar draw calls.
O fundo do topo desta página é um fragment shader rodando ao vivo em WebGL puro, sem biblioteca. Abra o código-fonte desta apostila (Ctrl+U) e leia o script no final do arquivo depois de terminar o Módulo 05 — você vai entender cada linha.
Como a GPU desenha um frame
Objetivo: entender o pipeline gráfico de ponta a ponta. Tudo o que vem depois — Three.js, shaders, otimização — são camadas em cima deste modelo mental.
1.1 O contrato dos 16 milissegundos
Um site a 60fps precisa produzir um frame novo a cada 16,6 ms. Nesse orçamento cabem: seu JavaScript, o layout do navegador, a pintura da página e a renderização 3D. "Tempo real" significa exatamente isso: a imagem é calculada do zero, todo frame, dentro desse prazo — diferente de um vídeo, que é pré-renderizado.
Quem estoura o orçamento entrega jank (engasgo visível). Praticamente toda decisão técnica desta apostila deriva dessa restrição.
1.2 O pipeline gráfico em 6 estágios
Quando você "desenha um cubo", isto acontece na GPU:
- Vertex data: a malha vira arrays de números (posições, normais, UVs) enviados à GPU em buffers.
- Vertex shader: um programa seu roda uma vez por vértice e responde uma pergunta: "onde este vértice cai na tela?" (transformação por matrizes model → view → projection).
- Rasterização: a GPU liga os vértices em triângulos e descobre quais pixels cada triângulo cobre. Etapa fixa, você não programa.
- Fragment shader: outro programa seu roda uma vez por pixel coberto e responde: "que cor este pixel tem?" (luz, textura, efeitos).
- Testes: depth test (quem está na frente), blending (transparência).
- Framebuffer: o resultado vai para a tela — ou para uma textura (base do pós-processamento, Módulo 07).
Vertex shader = ONDE. Fragment shader = QUAL COR. Grave esta frase. Ela resolve 80% da confusão inicial com shaders.
1.3 Por que a GPU é rápida (e burra)
A CPU tem poucos núcleos inteligentes; a GPU tem milhares de núcleos simples que executam o mesmo programa em paralelo sobre dados diferentes (SIMD). Uma tela full HD tem ~2 milhões de pixels: o fragment shader roda 2 milhões de vezes por frame, em paralelo. Consequências práticas:
- Shaders não têm memória entre pixels: cada execução é isolada.
- Enviar dados CPU→GPU é caro; manter dados na GPU é barato. (Por isso instancing vence loops de meshes — Módulo 08.)
if/elsedivergente em shader custa caro: os núcleos executam os dois ramos.
1.4 Sistemas de coordenadas e matrizes
A cadeia de transformações que todo vertex shader executa:
// espaço local → mundo → câmera → clip → tela
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
- modelMatrix — posição/rotação/escala do objeto no mundo.
- viewMatrix — o mundo visto da câmera (é a inversa da matriz da câmera).
- projectionMatrix — perspectiva (ou ortográfica): converte o frustum da câmera no cubo de clip space (−1 a 1).
Você raramente escreve essas matrizes na mão — Three.js as fornece — mas entender a ordem é o que permite efeitos avançados como billboarding, projeção de texturas e distorção de vértices no espaço certo.
Sem código: desenhe no papel o caminho de um vértice do cubo desde o array de posições até virar pixel. Depois explique em voz alta a diferença entre vertex e fragment shader como se ensinasse a alguém. Se travar, releia 1.2 — este módulo precisa estar sólido.
Entrevistas para vagas de creative developer quase sempre incluem "explique o pipeline de renderização". Quem responde com o modelo ONDE/QUAL COR + as três matrizes passa da triagem técnica. É pergunta de eliminação, não de desempate.
WebGL puro: o mínimo que importa
Objetivo: montar UMA cena em WebGL cru para nunca mais ter medo do que o Three.js esconde. Você não vai trabalhar assim no dia a dia — mas quem já viu o metal por baixo depura qualquer coisa.
2.1 O que é WebGL, de fato
WebGL é uma API JavaScript que expõe a GPU no navegador, baseada em OpenGL ES. Ela é uma máquina de estados: você configura estado global (qual buffer está ativo, qual programa, qual textura) e dispara drawArrays/drawElements. WebGL2 (suportado em ~97% dos navegadores) adiciona instancing nativo, mais formatos de textura e GLSL ES 3.0.
2.2 Hello Triangle — o ritual de iniciação
main.js — triângulo em WebGL puroconst gl = canvas.getContext('webgl2');
// 1. shaders como strings
const vsSource = `#version 300 es
in vec2 aPos;
void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }`;
const fsSource = `#version 300 es
precision highp float;
out vec4 outColor;
void main(){ outColor = vec4(1.0, 0.7, 0.3, 1.0); }`;
// 2. compilar e linkar
function compile(type, src){
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if(!gl.getShaderParameter(s, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(s));
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vsSource));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fsSource));
gl.linkProgram(prog);
// 3. dados de vértice → buffer na GPU
const verts = new Float32Array([ 0,0.8, -0.8,-0.8, 0.8,-0.8 ]);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, 'aPos');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
// 4. desenhar
gl.useProgram(prog);
gl.drawArrays(gl.TRIANGLES, 0, 3);
São ~40 linhas para um triângulo. É exatamente por isso que Three.js existe — e é exatamente isso que ele faz por baixo, milhares de vezes por frame.
2.3 Os conceitos que você leva para sempre
| Termo | O que é | Em Three.js vira… |
|---|---|---|
| Attribute | Dado por vértice (posição, UV, normal) | BufferAttribute |
| Uniform | Valor global do frame (tempo, mouse, matrizes) | material.uniforms |
| Varying | Valor interpolado do vertex → fragment | varying/out-in no GLSL |
| VAO/Buffer | Memória de vértices na GPU | BufferGeometry |
| Program | Par vertex+fragment compilado | Material (cada material = 1 programa) |
| Draw call | Uma ordem de desenho à GPU | 1 por mesh visível — a métrica nº 1 de performance |
- Reproduza o Hello Triangle e mude a cor via uniform (
gl.uniform3f) animada comrequestAnimationFrame. - Adicione um segundo attribute
aColorpor vértice, passe comovaryinge observe a interpolação (o famoso triângulo RGB).
Three.js essencial
Objetivo: dominar o vocabulário que 90% das vagas de web 3D pedem: cena, câmera, luz, material, geometria, loop, resize, interação e carregamento de modelos.
3.1 A cena mínima profissional
scene.js — boilerplate que você vai digitar mil vezesimport * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, innerWidth/innerHeight, 0.1, 100);
camera.position.set(3, 2, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); // NUNCA sem o clamp
document.body.appendChild(renderer.domElement);
const mesh = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.8, 0.28, 128, 32),
new THREE.MeshStandardMaterial({ color: '#8b7cff', roughness: 0.25 })
);
scene.add(mesh);
scene.add(new THREE.AmbientLight('#ffffff', 0.4));
const key = new THREE.DirectionalLight('#ffb454', 2.5);
key.position.set(4, 5, 3);
scene.add(key);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
const t = clock.getElapsedTime();
mesh.rotation.y = t * 0.4; // tempo absoluto, não += (frame-rate independent)
controls.update();
renderer.render(scene, camera);
});
addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
- Tela preta? Checklist: há luz na cena? A câmera olha para o objeto? O objeto está dentro de near/far? Chamou
render()? - Animar com
+= 0.01deixa a velocidade dependente do FPS. Use tempo doClockou multiplique pordelta. setPixelRatiosem clamp renderiza 9x mais pixels num celular 3x — a causa nº 1 de site 3D lento em mobile.
3.2 Materiais: qual usar quando
| Material | Custo | Uso |
|---|---|---|
| MeshBasicMaterial | Baixíssimo | Sem luz: UI 3D, wireframes, fundos, objetos emissivos simples |
| MeshLambert/Phong | Baixo | Estética estilizada/retro, mobile agressivo |
| MeshStandardMaterial | Médio | PBR padrão do mercado: metalness/roughness + env map |
| MeshPhysicalMaterial | Alto | Vidro (transmission), verniz (clearcoat), iridescência, sheen |
| ShaderMaterial | Você decide | Tudo que é premiado — Módulos 05–07 |
Para PBR convincente, environment map é mais importante que luzes: carregue um HDRI com RGBELoader e defina scene.environment. É o segredo do "look de produto" (e-commerce, configuradores).
3.3 Carregando modelos GLTF
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const draco = new DRACOLoader().setDecoderPath('/draco/');
const loader = new GLTFLoader().setDRACOLoader(draco);
loader.load('/models/sneaker.glb', (gltf) => {
scene.add(gltf.scene);
// animações embutidas:
const mixer = new THREE.AnimationMixer(gltf.scene);
gltf.animations.forEach(clip => mixer.clipAction(clip).play());
});
GLTF/GLB é o formato do mercado web. Esqueça OBJ e FBX para entrega final. Compressão e otimização de assets são o Módulo 09.
3.4 Interação: raycasting
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
addEventListener('pointermove', e => {
pointer.set((e.clientX/innerWidth)*2-1, -(e.clientY/innerHeight)*2+1);
});
// dentro do loop:
raycaster.setFromCamera(pointer, camera);
const hits = raycaster.intersectObjects(scene.children, true);
document.body.style.cursor = hits.length ? 'pointer' : 'default';
Visualizador de produto: um objeto (pode ser um GLB gratuito do Sketchfab/Poly Haven) com HDRI, órbita suave, 3 botões HTML que trocam a cor do material com transição, e hover que destaca a peça via raycasting. Publique no CodePen/Vercel. Este exato padrão é vendido como "configurador 3D" por R$ 4–15 mil no mercado brasileiro de freela.
Palavras que aparecem em vagas e que este módulo cobre: Three.js, GLTF, PBR, raycasting, render loop, responsive canvas. Coloque-as literalmente no seu LinkedIn/CV junto do link do exercício — recrutador filtra por keyword antes de olhar talento.
React Three Fiber & ecossistema
Objetivo: dominar o stack que domina as vagas: R3F + drei + zustand. A maioria dos estúdios digitais (e quase todos os sites premiados recentes) monta 3D declarativo dentro de React/Next.js.
4.1 A ideia central: cena como componente
React Three Fiber (R3F) é um renderer de React para Three.js. Cada elemento JSX em minúsculas vira um objeto Three.js; props viram propriedades; args viram argumentos do construtor. Não há custo extra de performance no loop — o React só roda quando o estado muda; a renderização 3D continua no rAF.
App.jsximport { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei';
import { useRef, useState } from 'react';
function Knot(){
const ref = useRef();
const [hover, setHover] = useState(false);
useFrame((state, delta) => {
ref.current.rotation.y += delta * 0.4;
// interpolação exponencial estável em qualquer FPS:
ref.current.scale.setScalar(THREE.MathUtils.damp(
ref.current.scale.x, hover ? 1.15 : 1, 8, delta));
});
return (
<mesh ref={ref}
onPointerOver={() => setHover(true)}
onPointerOut={() => setHover(false)}>
<torusKnotGeometry args={[0.8, 0.28, 128, 32]} />
<meshStandardMaterial color="#8b7cff" roughness={0.25} />
</mesh>
);
}
export default function App(){
return (
<Canvas camera={{ position: [3,2,5], fov: 35 }} dpr={[1,2]}>
<Knot />
<Environment preset="city" />
<ContactShadows opacity={0.5} blur={2} />
<OrbitControls enableDamping />
</Canvas>
);
}
Note o que sumiu: renderer, resize, loop, dispose. R3F gerencia tudo — inclusive descarte automático de memória quando componentes desmontam, algo doloroso no Three.js puro.
4.2 As três regras de ouro do R3F
- Nunca use setState dentro de
useFrame. Mutação direta de refs para o que muda a cada frame; estado React só para eventos discretos (clique, troca de cena). - Nunca crie objetos no corpo do componente sem memo.
new Vector3()por render = lixo para o GC. UseuseMemo, constantes fora do componente, ou os helpers de string do R3F. - Estado global fora do React:
zustandcomgetState()/subscribe transiente dentro douseFrameevita re-renders. É o padrão dos exemplos oficiais da Poimandres.
4.3 drei: o cinto de utilidades
@react-three/drei condensa anos de código de produção. Os que mais caem em projeto real:
<Environment />,<ContactShadows />,<AccumulativeShadows />— o "look caro" instantâneo.<ScrollControls />+useScroll— scrollytelling nativo (Módulo 15).<Text />(SDF, nítido em qualquer zoom),<Html />(DOM ancorado no 3D).useGLTF(com draco embutido) +gltfjsx, a CLI que converte um GLB em componente JSX editável — ferramenta de produtividade absurda.<Instances />,<Merged />,<Detailed />(LOD),<PerformanceMonitor />— performance declarativa.<MeshTransmissionMaterial />— o vidro grosso dispersivo que você vê em todo site premiado.
4.4 Física e o resto do ecossistema
- @react-three/rapier — física (WASM) declarativa:
<RigidBody>em volta do mesh e pronto. - @react-three/postprocessing — efeitos com merge automático de passes (Módulo 07).
- leva — painel de controles para tunar valores ao vivo. Use em TODO projeto; metade do trabalho de creative dev é ajuste fino.
- maath — helpers matemáticos (damp, random em superfícies).
Refaça o visualizador de produto do Módulo 03 em R3F + drei: useGLTF, Environment com HDRI, ContactShadows, troca de material via zustand, hover com damp, painel leva para roughness/metalness. Compare a contagem de linhas com a versão vanilla e escreva um post curto "Three.js vs R3F" no LinkedIn com os dois links — conteúdo técnico comparativo performa muito bem para atrair recrutador.
Shaders GLSL do zero ao ruído
Objetivo: escrever vertex e fragment shaders próprios. Esta é a linha que separa "usa Three.js" de "creative developer". Salários e orçamentos mudam de faixa aqui.
5.1 GLSL em 10 minutos
GLSL é C simplificado, fortemente tipado, sem ponteiros. O que você precisa:
float a = 1.0; // SEMPRE com ponto — 1 é int e dá erro
vec2 uv = vec2(0.5); // vec2(0.5, 0.5)
vec3 cor = vec3(uv, 1.0); // construção por composição
float r = cor.r; // swizzle: .xyzw .rgba .stpq
vec2 inv = cor.yx; // reordenar componentes é grátis
As funções que constroem 95% dos efeitos — decore-as:
| Função | O que faz | Uso típico |
|---|---|---|
| mix(a,b,t) | Interpolação linear | Gradientes, blending de cores |
| step(edge,x) | 0 ou 1 (corte seco) | Listras, máscaras duras |
| smoothstep(a,b,x) | 0→1 suave | Bordas antialiased, glow, máscaras |
| fract(x) | Parte fracionária | Repetição/tiling de padrões |
| length(v) / distance(a,b) | Distância | Círculos, campos radiais, SDFs |
| dot(a,b) | Produto escalar | Iluminação (N·L), fresnel |
| clamp, abs, sin, pow, floor | — | Modelagem de curvas e ritmo |
5.2 ShaderMaterial no Three.js
material.jsconst material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uMouse: { value: new THREE.Vector2() },
uColorA:{ value: new THREE.Color('#8b7cff') },
uColorB:{ value: new THREE.Color('#ffb454') },
},
vertexShader: /* glsl */`
uniform float uTime;
varying vec2 vUv;
varying float vWave;
void main(){
vUv = uv;
vec3 p = position;
float wave = sin(p.x * 3.0 + uTime * 2.0) * 0.15;
p.z += wave; // deslocamento de vértice
vWave = wave; // manda pro fragment
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
}`,
fragmentShader: /* glsl */`
uniform vec3 uColorA, uColorB;
varying vec2 vUv;
varying float vWave;
void main(){
vec3 col = mix(uColorA, uColorB, vUv.y + vWave);
gl_FragColor = vec4(col, 1.0);
}`
});
// no loop: material.uniforms.uTime.value = clock.getElapsedTime();
position, uv, normal e as matrizes já chegam prontos no ShaderMaterial — Three.js injeta. Com RawShaderMaterial nada é injetado.
5.3 Desenhando com matemática: o kit SDF 2D
// círculo com borda suave e SEM serrilhado:
float circle(vec2 uv, vec2 c, float r){
float d = distance(uv, c);
return smoothstep(r, r - fwidth(d)*1.5, d);
}
// grid de repetição:
vec2 gv = fract(uv * 10.0) - 0.5;
// listras animadas:
float stripes = step(0.5, fract(uv.x * 8.0 + uTime));
5.4 Ruído: a alma do orgânico
Quase todo efeito "vivo" (água, fumaça, aurora, terreno, distorção) é ruído — funções pseudoaleatórias suaves. A hierarquia:
- random(hash):
fract(sin(dot(uv, vec2(12.9898,78.233))) * 43758.5453)— ruído branco, base de tudo. - Value/Perlin/Simplex noise: interpolações suaves do hash. Na prática: copie as implementações clássicas de Ashima/Ian McEwan (padrão da indústria, licença MIT) ou use o pacote
glsl-noise/módulos dolygia. - FBM (Fractal Brownian Motion): soma de oitavas de ruído — nuvens, mármore, terrenos:
float fbm(vec2 p){
float v = 0.0, a = 0.5;
for(int i = 0; i < 5; i++){
v += a * snoise(p);
p *= 2.0; a *= 0.5;
}
return v;
}
// domain warping — o truque dos shaders premiados:
float n = fbm(uv + fbm(uv + uTime * 0.1));
Estude em The Book of Shaders (tem tradução PT) na ordem: shaping functions → cores → formas → ruído. Depois passe uma semana no Shadertoy só lendo shaders alheios e comentando linha a linha. Ler shader é uma habilidade separada de escrever.
- Plano subdividido (256×256) com ondas por vertex shader + cor por altura — um "oceano estilizado".
- Fragment shader fullscreen com FBM + domain warping + paleta com
mixem 3 cores — um fundo "aurora". (O hero desta apostila é exatamente isso; compare com o código no fim do arquivo.) - Publique os dois no Shadertoy/CodePen com descrição do processo.
"GLSL" no CV com portfólio de shaders reais coloca você em outra prateleira: vagas de creative developer em estúdios (nacionais e internacionais/remotas) listam shaders como diferencial decisivo, e freelas de "fundo interativo tipo Stripe/Linear" são vendidos a partir de R$ 3–8 mil por peça.
Técnicas avançadas de shader
Objetivo: os padrões que aparecem em site premiado: fresnel, dissolve, partículas em GPU (GPGPU/FBO), raymarching e patch de materiais nativos.
6.1 Fresnel — o efeito mais rentável por linha de código
Bordas de objetos brilhando conforme o ângulo de visão. Base de hologramas, force fields, vidro estilizado, rim light:
// vertex: passe normal e posição no espaço do mundo
vNormal = normalize(normalMatrix * normal);
vViewDir = normalize(cameraPosition - (modelMatrix * vec4(position,1.0)).xyz);
// fragment:
float fresnel = pow(1.0 - clamp(dot(vNormal, vViewDir), 0.0, 1.0), 3.0);
gl_FragColor = vec4(uColor * fresnel, fresnel);
6.2 Dissolve com ruído
O clássico de transição de objetos (aparecer/desaparecer "queimando"):
float n = snoise(vUv * 6.0);
if(n < uProgress) discard; // recorta o pixel
float edge = smoothstep(uProgress, uProgress + 0.08, n);
vec3 col = mix(uEdgeColor * 3.0, uBaseColor, edge); // borda emissiva p/ bloom
Anime uProgress de 0→1 com GSAP (Módulo 13) e você tem uma transição de cena vendável.
6.3 Partículas em GPU: de Points ao GPGPU
Nível 1 — THREE.Points + shader
Milhares de pontos numa BufferGeometry, animados no vertex shader por atributo aleatório + uTime. Custo quase zero de CPU; suficiente para poeira, estrelas, campos de fundo.
gl_PointSize = uSize * (1.0 / -viewPosition.z); // atenuação por distância
// no fragment, desenhe um disco suave:
float d = length(gl_PointCoord - 0.5);
float alpha = smoothstep(0.5, 0.1, d);
Nível 2 — GPGPU / FBO particles (o "muito avançado")
Para partículas com física e memória (fluidos, enxames, morphing entre formas), o estado (posição/velocidade) vive em texturas: cada pixel de uma textura RGBA float = uma partícula. Um shader de simulação lê a textura do frame anterior e escreve a próxima (ping-pong entre dois render targets); o shader de render lê a posição da textura para posicionar cada ponto.
- No Three.js:
GPUComputationRenderer(addon oficial) monta o ping-pong para você. - Padrões clássicos: curl noise para fluxo orgânico, atração ao mouse, morphing entre nuvens de pontos amostradas de modelos (
MeshSurfaceSampler). - 500 mil+ partículas a 60fps é factível em desktop médio.
6.4 Raymarching: cenas sem geometria
Técnica de renderizar num único quad fullscreen: para cada pixel, "marche" um raio pela cena definida por SDFs 3D (funções de distância). É como se faz metaballs/blobs gosmentos, fractais e nuvens volumétricas:
float sdSphere(vec3 p, float r){ return length(p) - r; }
float smin(float a, float b, float k){ // blend "gosmento" entre formas
float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
return mix(b, a, h) - k*h*(1.0-h);
}
// loop de marcha:
for(int i=0; i<64; i++){
float d = map(ro + rd * t); // map() = sua cena em SDFs
if(d < 0.001 || t > 20.0) break;
t += d;
}
Referência canônica: os artigos de Inigo Quilez (iquilezles.org) — a biblioteca de SDFs dele é o dicionário da área.
6.5 Estender materiais nativos: onBeforeCompile e CSM
O segredo sujo dos profissionais: você raramente reescreve PBR do zero. Em vez disso, injeta código no shader do MeshStandardMaterial via material.onBeforeCompile (substituindo chunks como #include <begin_vertex>) — ou usa a lib three-custom-shader-material, que torna isso ergonômico. Resultado: deformação por vértice + ruído mantendo sombras, env map e toda a iluminação PBR.
Escolha UM e leve à excelência: (a) galáxia de 200k partículas com curl noise reagindo ao mouse via GPGPU; (b) blob raymarched com smin + fresnel como hero de landing page; (c) modelo GLTF com dissolve emissivo disparado por clique. Grave um vídeo de 20s do resultado — vídeo curto de shader é o formato que mais gera contato de trabalho no X/LinkedIn/Instagram.
Pós-processamento & render targets
Objetivo: o "acabamento de cinema": bloom, DOF, grain, aberração cromática, transições — e a base técnica de tudo isso, o render target.
7.1 Render target: renderizar para textura
Um WebGLRenderTarget é um framebuffer fora da tela. Renderize a cena nele e o resultado vira uma textura que outro shader pode ler. Disso derivam: pós-processamento, espelhos/portais, refração de "vidro" lendo a cena atrás, GPGPU (Módulo 06), sombras e transições entre cenas.
const rt = new THREE.WebGLRenderTarget(w, h);
renderer.setRenderTarget(rt);
renderer.render(sceneA, camera); // vai para a textura
renderer.setRenderTarget(null);
quadMaterial.uniforms.tScene.value = rt.texture;
renderer.render(quadScene, quadCam); // quad fullscreen aplica o efeito
7.2 O stack moderno: pmndrs/postprocessing
Em produção, use a lib postprocessing (ou @react-three/postprocessing no R3F): ela funde vários efeitos em um único shader, muito mais rápido que o EffectComposer clássico encadeado.
import { EffectComposer, Bloom, DepthOfField, Noise, ChromaticAberration, Vignette }
from '@react-three/postprocessing';
<EffectComposer>
<Bloom intensity={0.9} luminanceThreshold={1.0} mipmapBlur />
<DepthOfField focusDistance={0.01} focalLength={0.05} bokehScale={3} />
<ChromaticAberration offset={[0.0012, 0.0008]} />
<Noise opacity={0.04} />
<Vignette darkness={0.6} />
</EffectComposer>
Com luminanceThreshold: 1, só brilha o que tem cor acima de 1.0. Então: materiais emissivos com emissiveIntensity alto (ou cores como [3, 1.2, 0.5]) brilham; o resto não. Nada de camadas/máscaras complicadas.
7.3 Efeitos custom e transições de cena
Um efeito próprio na lib postprocessing é uma classe Effect com um fragment que recebe inputColor e UV. Receita de transição de cena premiada: renderize cena A e cena B em dois render targets e misture no shader com uma máscara de ruído/wipe controlada por uProgress — o mesmo dissolve do Módulo 06, agora em tela cheia.
- Color grading: LUTs via
LUT3DEffect— exporte a LUT do DaVinci/Photoshop e o site ganha o mesmo grade do vídeo da campanha. - Tone mapping:
ACESFilmic(padrão do Three) + trabalhar luz em valores HDR é o que faz a cena parecer "renderizada de verdade". - Custo: cada passe fullscreen roda por pixel; em mobile, limite-se a bloom + vignette + grain. DOF e SSR são os vilões.
Monte uma cena noturna: objetos emissivos + bloom seletivo + grain + vignette + leve aberração cromática, com painel leva expondo os parâmetros. Depois implemente uma transição por dissolve entre duas cenas disparada por botão. Este combo é literalmente o visual de metade dos sites do Awwwards.
Performance: 60fps de verdade
Objetivo: a habilidade que clientes pagam caro e poucos têm: fazer rodar liso em celular médio. Metodologia de diagnóstico + as otimizações por ordem de impacto.
8.1 Diagnóstico antes de otimizar
- Meça:
stats.js/r3f-perfna tela, aba Performance do DevTools para ver se o gargalo é CPU (script) ou GPU (frames longos sem script). - Inspecione: Spector.js mostra cada draw call do frame.
renderer.infomostra calls, triângulos, texturas e programas. - Teste no pior dispositivo-alvo, não no seu desktop. Chrome DevTools → CPU throttling 4x + um Android de verdade.
| Sintoma | Gargalo provável | Ataque |
|---|---|---|
| CPU alta, GPU folgada | Draw calls / JS no loop | Instancing, merge, cortar trabalho por frame |
| GPU alta em tela grande | Fragment-bound (fill rate) | Baixar DPR, simplificar shaders/pós, menos transparência |
| GPU alta mesmo em tela pequena | Vertex-bound | Menos polígonos, LOD, frustum culling |
| Travadas periódicas | GC / upload de textura | Zerar alocações no loop, pré-aquecer com compile() |
8.2 Draw calls: a métrica rainha
Cada mesh visível = 1 draw call, e cada call tem custo fixo de CPU. Alvo prático: < 100 calls em mobile, < 300 em desktop.
- InstancedMesh: N cópias da mesma geometria+material em 1 call. 10.000 cubos, uma chamada. Atualize matrizes via
setMatrixAt+instanceMatrix.needsUpdate; varie cor por instância cominstanceColor; varie movimento por instância com atributos custom no shader. - Merge:
BufferGeometryUtils.mergeGeometriespara estática que compartilha material. - Atlas de texturas para permitir merge/instancing entre objetos "diferentes".
8.3 A lista de impacto (em ordem)
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))— e considere 1.5 em cenas pesadas. Ganho imediato de até 4x em fill rate.- Instancing/merge até draw calls caberem no alvo.
- Texturas: KTX2/Basis (Módulo 09), potências de 2, mipmaps, nunca maiores que o necessário na tela.
- Sombras: desligue o que puder;
shadow.mapSize1024 no máximo em mobile; sombras estáticas → bake ouContactShadows. - Transparência e overdraw: camadas de alpha empilhadas destroem mobile. Use
alphaTest/discardquando der. - Zero alocação no loop: reuse
Vector3/Matrix4pré-criados; nada denew,map, spread dentro do rAF. - Dispose:
geometry.dispose(),material.dispose(),texture.dispose()ao remover objetos — vazamento de VRAM derruba a aba em SPAs. - Qualidade adaptativa: meça FPS por alguns segundos e degrade (DPR, pós, sombras) —
<PerformanceMonitor>do drei faz isso declarativamente. - Pré-compilação:
renderer.compile(scene, camera)(oucompileAsync) antes de revelar a cena, para evitar o engasgo do primeiro frame. - Demanda: cena parada não precisa renderizar —
frameloop="demand"no R3F ou invalidação manual.
Site lindo que derrete o celular do recrutador = reprovação silenciosa. Todo projeto público seu deve abrir em um Android intermediário sem aquecer. Coloque no rodapé do case: "60fps em Moto G / draw calls: 47" — isso comunica senioridade instantaneamente.
Pegue seu projeto do Módulo 06 e faça uma auditoria escrita: draw calls antes/depois, DPR, tamanho de texturas, FPS em throttling 4x. Otimize até dobrar o FPS ou cortar as calls pela metade e publique o antes/depois como estudo de caso. Auditorias assim são um serviço vendável por si só (retainers de performance).
Assets: Blender → Web
Objetivo: o pipeline de conteúdo — modelar/otimizar no Blender, bake de luz, compressão, e entregar um GLB que carrega em 2s no 4G.
9.1 O que você precisa do Blender (e só isso)
Você não precisa virar 3D artist. Precisa de: navegação, modelagem básica/edição de malha, modificador Decimate, UV unwrap, materiais Principled BSDF, e exportação GLTF. Isso é ~2 semanas de prática direcionada (o clássico "donut" do Blender Guru + um curso curto de low poly).
9.2 Regras de asset para tempo real
- Orçamento de polígonos: herói de cena 20–80k tris; objetos de apoio 1–5k. Use Decimate/retopologia.
- Aplicar transformações (Ctrl+A) e escala 1,1,1 antes de exportar — evita 90% dos bugs de "modelo gigante/deitado".
- Materiais: só Principled BSDF vira PBR no GLTF. Procedurais do Blender NÃO exportam — precisam de bake para textura.
- Bake de iluminação: para cenas estáticas, asse luz+AO em lightmap e use
MeshBasicMaterialcom a textura — visual de render offline com custo de material sem luz. É assim que ambientes ricos rodam em mobile.
9.3 Compressão: gltf-transform é seu melhor amigo
terminal — pipeline de otimização padrãonpm i -g @gltf-transform/cli
# tudo de uma vez: dedup, prune, resize, Draco + KTX2
gltf-transform optimize in.glb out.glb --compress draco --texture-compress ktx2
# ou por etapas, com controle:
gltf-transform resize in.glb tmp.glb --width 1024 --height 1024
gltf-transform etc1s tmp.glb tmp2.glb # KTX2 universal (UI/albedo)
gltf-transform draco tmp2.glb out.glb # geometria ~10x menor
- Draco: comprime geometria (10–20x). Custo: decodificação em WASM no load.
- Meshopt: alternativa que decodifica quase instantaneamente — melhor para muitos modelos pequenos.
- KTX2/Basis: textura comprimida que permanece comprimida na VRAM (JPG/PNG são descomprimidos na GPU!). Corta uso de memória em 4–8x — o upgrade mais subestimado para mobile. Carregue com
KTX2Loader. - Alvo de entrega: herói de landing page ≤ 2–4 MB total de assets 3D.
9.4 Onde conseguir assets (com licença limpa)
- Poly Haven — HDRIs, texturas e modelos CC0 (uso comercial livre). Base de todo freela.
- Sketchfab — filtre por licença CC; cheque atribuição antes de usar em cliente.
- Quaternius / Kenney — packs low poly CC0 excelentes para protótipos e jogos.
Modele (ou baixe e edite) um objeto de produto no Blender, asse AO, exporte GLB, rode o pipeline gltf-transform e documente: tamanho antes/depois, tempo de load em 4G simulado. Meta: sair de >20 MB para <3 MB sem perda visível.
WebGPU & TSL — o futuro (que já começou)
Objetivo: entender o que muda com WebGPU, escrever materiais em TSL (a nova linguagem de nós do Three.js) e usar compute shaders — o assunto que separa candidatos em 2026.
10.1 Por que WebGPU existe
WebGL espelha OpenGL de 2007: máquina de estados global, sem acesso a recursos modernos da GPU. WebGPU é uma API nova (inspirada em Vulkan/Metal/DX12) com:
- Compute shaders: GPU para cálculo genérico de verdade — partículas, física, IA — sem a gambiarra de texturas do GPGPU clássico.
- Menos overhead de CPU: pipelines pré-validados, bind groups — milhares de objetos ficam baratos.
- WGSL: nova linguagem de shader (substitui GLSL nesse contexto).
- Suporte: Chrome/Edge estáveis há tempo, Safari e Firefox chegando/chegado nas versões recentes — mas em produção você sempre mantém fallback WebGL2 por enquanto.
10.2 WebGPURenderer no Three.js
import * as THREE from 'three/webgpu';
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init(); // assíncrono!
// se WebGPU não existir, este renderer cai sozinho para WebGL2 —
// e é por isso que os materiais agora são escritos em TSL, não GLSL.
10.3 TSL: shaders em JavaScript
TSL (Three.js Shading Language) descreve o shader como grafo de nós em JS; o Three compila para WGSL ou GLSL conforme o backend. É o novo caminho oficial — ShaderMaterial GLSL continua funcionando no WebGL, mas o ecossistema novo é TSL:
import { uniform, uv, sin, time, mix, color, positionLocal, vec4 }
from 'three/tsl';
const uColorA = uniform(color('#8b7cff'));
const uColorB = uniform(color('#ffb454'));
const mat = new THREE.MeshStandardNodeMaterial();
// deslocamento de vértice + cor — mantendo TODO o PBR:
const wave = sin(positionLocal.x.mul(3).add(time.mul(2))).mul(0.15);
mat.positionNode = positionLocal.add(vec4(0, 0, wave, 0).xyz);
mat.colorNode = mix(uColorA, uColorB, uv().y.add(wave));
Repare: é o exercício do Módulo 05, agora com iluminação PBR de graça e portátil entre backends. TSL também elimina classes inteiras de bug de string GLSL e permite compor materiais como funções JS.
10.4 Compute: partículas sem ping-pong
Com Fn().compute(count) em TSL, você escreve a simulação de N partículas lendo/escrevendo storage buffers diretamente — o GPGPU do Módulo 06 sem texturas-gambiarra, com milhões de partículas. A estrutura: um instancedArray de posições/velocidades, um kernel de update rodado por frame com renderer.compute(), e um material de pontos/instâncias lendo o buffer.
Em entrevista, saber articular "WebGPU: compute, menos overhead, WGSL; TSL como camada portátil; fallback WebGL2 em produção" já coloca você à frente — pouquíssimos candidatos estudaram isso. Um demo de compute particles no portfólio é sinal forte de atualização técnica.
Porte seu shader do Módulo 05 para TSL com MeshStandardNodeMaterial e rode com WebGPURenderer. Depois siga o exemplo oficial webgpu_compute_particles do repositório do Three.js e modifique-o (força do mouse, cores por velocidade).
Princípios de animação aplicados a interface
Objetivo: o olho. Ferramenta sem princípio produz movimento amador. Aqui traduzimos os 12 princípios da Disney + prática de motion design para o vocabulário de web.
11.1 Os princípios que importam na web
| Princípio | Tradução para UI/web |
|---|---|
| Easing (slow in/out) | NADA se move linearmente. Entradas desaceleram (ease-out), saídas aceleram (ease-in). Linear só para rotação contínua e marquees. |
| Timing | Micro-interações 150–250ms; transições de layout 300–500ms; momentos hero 600–1200ms. Mais que isso vira lentidão percebida. |
| Antecipação | Um recuo de 2–4% antes do movimento principal (botão que "arma" antes de pular). |
| Follow-through / overshoot | Elementos passam 2–8% do alvo e voltam (back/elastic ease) — vida sem exagero. |
| Staggering (ação secundária) | Itens de lista entram com 40–90ms de atraso entre si, nunca todos juntos. |
| Arcos | Movimento em curva (motion path) parece natural; linha reta parece mecânica. |
| Squash & stretch | Em UI: deformação sutil de scale no impacto (0.95/1.05). Em 3D/canvas: livre. |
| Encenação (staging) | Uma coisa importante por vez. Coreografia guia o olho; caos compete por atenção. |
11.2 Easing como identidade
Curvas de easing são a "voz" do produto. Construa uma paleta de 3 e use consistentemente:
/* paleta típica de produto */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* entradas — "expo out" */
--ease-in: cubic-bezier(0.7, 0, 0.84, 0); /* saídas */
--ease-move: cubic-bezier(0.65, 0, 0.35, 1); /* deslocamentos em tela */
Alternativa moderna: animação por spring/física (stiffness/damping em vez de duração) — é o modelo do Framer Motion e do linear() do CSS; movimentos interrompíveis ficam naturais porque a velocidade é preservada.
11.3 Coreografia de página
Padrão profissional de entrada de uma seção hero: (1) máscara/loader sai; (2) título revela por linhas com stagger; (3) subtítulo e CTA entram 100–200ms depois; (4) mídia/3D entra por último com o maior movimento. Um único "beat" claro. Estude decompondo sites do Awwwards em câmera lenta (DevTools → Animations → 10%).
Escolha um site premiado, grave a entrada da home e faça um "motion breakdown" por escrito: ordem, durações estimadas, easings, staggers. Recrie apenas com CSS. Este tipo de análise publicada demonstra olho — e olho é o que diretor de arte procura num dev.
CSS, WAAPI & scroll-driven animations
Objetivo: extrair o máximo da plataforma antes de importar biblioteca — inclusive as novidades que rodam fora da main thread.
12.1 A regra do compositor
Anime só transform, opacity e filter: o navegador executa na thread do compositor, imune a jank de JavaScript. Animar width/top/margin força layout+paint a cada frame. will-change: transform promove a camada — use pontualmente, remova depois.
/* reveal padrão de produção */
.reveal{ opacity:0; transform:translateY(24px);
transition: opacity .6s var(--ease-out), transform .6s var(--ease-out); }
.reveal.in{ opacity:1; transform:none; }
/* dispare .in com IntersectionObserver */
12.2 Keyframes compostos e stagger em CSS puro
@keyframes rise{ from{ opacity:0; transform:translateY(30px) } }
.item{ animation: rise .6s var(--ease-out) backwards;
animation-delay: calc(var(--i) * 70ms); }
<!-- no HTML: style="--i:0" , "--i:1" ... -->
12.3 Web Animations API (WAAPI)
Keyframes do CSS com controle de JS — sem tocar em classes, com play/pause/reverse e finished como Promise:
const anim = el.animate(
[ { opacity: 0, transform: 'translateY(24px)' },
{ opacity: 1, transform: 'none' } ],
{ duration: 600, easing: 'cubic-bezier(.16,1,.3,1)', fill: 'both' }
);
await anim.finished;
anim.reverse();
WAAPI é a base sobre a qual libs como Motion (Framer Motion vanilla) aceleram por hardware. Para orquestração complexa, ainda assim, GSAP ganha (Módulo 13).
12.4 Scroll-driven animations nativas — a grande novidade
CSS agora anima dirigido pelo scroll, na thread do compositor, sem uma linha de JS:
/* barra de progresso de leitura */
.progress{ transform-origin:left;
animation: grow linear;
animation-timeline: scroll(root); } /* timeline = scroll da página */
@keyframes grow{ from{ transform:scaleX(0) } to{ transform:scaleX(1) } }
/* reveal quando o elemento cruza o viewport */
.card{ animation: rise linear both;
animation-timeline: view(); /* timeline = posição no viewport */
animation-range: entry 0% entry 60%; }
Suporte: Chrome/Edge sim; Safari/Firefox em adoção — em produção use como progressive enhancement com @supports (animation-timeline: view()) e fallback via IntersectionObserver/GSAP. Cobre parallax simples, reveals e progress bars com custo zero de JS.
12.5 View Transitions API
document.startViewTransition() anima entre estados do DOM (e entre páginas em MPA) com morphing automático de elementos compartilhados via view-transition-name. É o caminho nativo para o efeito "a imagem do card vira a imagem do detalhe" — antes exclusividade de FLIP manual (Módulo 13).
Construa uma landing page SEM bibliotecas: reveals por IntersectionObserver, stagger por variável CSS, barra de progresso e um parallax com animation-timeline, e uma transição de lista→detalhe com View Transitions. Documente o suporte de navegadores no README. "Sabe fazer sem lib" é argumento de contratação em times enxutos.
GSAP profissional
Objetivo: dominar a ferramenta padrão da indústria de sites premiados — timelines, ScrollTrigger, SplitText, Flip e arquitetura de animação em projetos reais. (Desde 2024 o GSAP é 100% gratuito, plugins incluídos.)
13.1 Tween, timeline e a gramática básica
gsap.to('.box', { x: 200, duration: 1, ease: 'power3.out' });
gsap.from('.title',{ y: 40, opacity: 0 });
gsap.set('.el', { transformOrigin: 'center bottom' });
// timeline: a razão de o GSAP dominar — sequência com controle total
const tl = gsap.timeline({ defaults: { ease: 'power3.out', duration: 0.8 } });
tl.from('.hero-title .line', { yPercent: 110, stagger: 0.08 })
.from('.hero-sub', { opacity: 0, y: 20 }, '-=0.4') // overlap
.from('.hero-cta', { opacity: 0, y: 20 }, '<0.1') // 0.1s após o início do anterior
.add('media') // label
.from('.hero-media', { scale: 1.15, opacity: 0, duration: 1.2 }, 'media');
tl.pause(); tl.play(); tl.reverse(); tl.timeScale(2); tl.seek('media');
Position parameters ('-=0.4', '<', labels) são o que transforma sequência em coreografia. Staggers aceitam objeto: stagger: { each: 0.06, from: 'center', grid: 'auto' }.
13.2 ScrollTrigger: o motor do scrollytelling
gsap.registerPlugin(ScrollTrigger);
// 1) disparo simples
gsap.from('.card', {
y: 60, opacity: 0, stagger: 0.1,
scrollTrigger: { trigger: '.cards', start: 'top 75%' }
});
// 2) scrub: a animação É o scroll
gsap.to('.horizontal-track', {
xPercent: -100,
scrollTrigger: {
trigger: '.horizontal',
start: 'top top',
end: '+=3000', // 3000px de scroll viram a duração
scrub: 1, // 1s de suavização (catch-up)
pin: true, // segura a seção na tela
anticipatePin: 1
}
});
// 3) debug SEMPRE com markers durante o dev
scrollTrigger: { ..., markers: true }
- pin + scrub é a dupla por trás de 90% dos "sites que contam história ao rolar".
ScrollTrigger.batch()para revelar dezenas de cards com eficiência.- Layout que muda depois do load (imagens, fonts) →
ScrollTrigger.refresh(). - Responsivo profissional:
gsap.matchMedia()cria/destroi animações por breakpoint (e porprefers-reduced-motion!) com cleanup automático.
13.3 SplitText e tipografia cinética
const split = SplitText.create('.hero-title', { type: 'lines,words', mask: 'lines' });
gsap.from(split.words, { yPercent: 120, stagger: 0.02, ease: 'expo.out', duration: 1 });
O reveal "texto subindo de dentro de uma máscara" é a assinatura tipográfica dos sites premiados. Espere as fontes (document.fonts.ready) antes de splitar.
13.4 Flip: animar mudanças de layout
Técnica FLIP (First, Last, Invert, Play): capture o estado, mude o DOM (reordenar grid, mover para outro container, fullscreen), e o plugin anima a diferença como transform barato:
const state = Flip.getState('.card');
container.appendChild(card); // mudança brusca de layout
Flip.from(state, { duration: 0.7, ease: 'power3.inOut', absolute: true });
13.5 Arquitetura de animação em projeto real
gsap.context()/useGSAP()(React): escopo de seletores +revert()automático no unmount — obrigatório em SPA para não vazar triggers.- Módulos por seção: cada seção exporta
init()/destroy(); um orquestrador central decide o que vive em cada página/breakpoint. - Smooth scroll: o padrão de mercado é Lenis + integração com o ticker do GSAP; ScrollSmoother é alternativa oficial do GSAP.
- quickTo/quickSetter para o que segue o mouse (cursores custom, magnetic buttons) sem criar tweens novos por evento.
Uma landing de "estúdio fictício": preloader com contador, hero com SplitText mascarado, seção horizontal com pin+scrub, grid com Flip ao filtrar, magnetic button com quickTo, e Lenis. Tudo dentro de matchMedia com versão reduzida para mobile e reduced-motion. Esta única página cobre 80% do que agências pedem em teste técnico.
Lottie, Rive & vetor animado
Objetivo: o canal designer→dev: rodar animações do After Effects e do Rive na web, controlá-las por código e saber quando usar cada formato.
14.1 Lottie: After Effects exportado como JSON
O motion designer anima no AE, exporta com o plugin Bodymovin, e você toca o JSON com lottie-web (ou @lottiefiles/dotlottie-web, formato .lottie comprimido):
import lottie from 'lottie-web';
const anim = lottie.loadAnimation({
container: document.querySelector('#hero-anim'),
path: '/anim/hero.json',
renderer: 'svg', // svg = nítido/estilizável; canvas = mais rápido p/ cenas densas
loop: false, autoplay: false
});
// controle por código — o que diferencia dev de embed:
anim.playSegments([0, 45], true); // intro
btn.onmouseenter = () => anim.playSegments([45, 90], true); // hover loop
// dirigido por scroll (com GSAP):
gsap.to({ f: 0 }, {
f: anim.totalFrames - 1,
onUpdate(){ anim.goToAndStop(this.targets()[0].f, true); },
scrollTrigger: { trigger: '#hero-anim', scrub: true, pin: true, end: '+=1500' }
});
- Regras para o designer: shape layers (nada de raster escondido), sem efeitos não suportados, composições enxutas. Combine cedo — retrabalho de Lottie é o atrito clássico designer/dev.
- Performance: JSONs > 300 KB ou dezenas de camadas com máscaras derrubam mobile; prefira .lottie, lazy load e
destroy()fora da tela.
14.2 Rive: animação com lógica embutida
Rive é editor + runtime próprios com state machines: o designer define estados (idle, hover, pressed, sucesso) e inputs; o dev só alimenta os inputs. Interatividade rica (personagens que seguem o cursor, botões com física, jogos leves) com arquivo minúsculo (.riv binário) e render em canvas/WebGL rápido.
import { Rive } from '@rive-app/canvas';
const r = new Rive({
src: '/anim/robot.riv', canvas, autoplay: true,
stateMachines: 'Main',
onLoad(){
const inputs = r.stateMachineInputs('Main');
const look = inputs.find(i => i.name === 'lookX');
addEventListener('pointermove', e => look.value = e.clientX / innerWidth * 100);
}
});
14.3 Qual formato quando
| Cenário | Escolha |
|---|---|
| Designer trabalha em AE; ilustração/ícones animados; onboarding | Lottie |
| Mascote/UI interativa com estados e lógica; jogos leves | Rive |
| Coreografia de DOM/texto/layout da própria página | GSAP/CSS |
| Cena com câmera, luz, profundidade, material | Three.js/R3F |
| Vídeo cinematográfico sem interação | <video> otimizado (sério — às vezes é a resposta certa) |
Baixe uma animação gratuita do LottieFiles e implemente: intro + loop de hover por segmentos + versão scrubada por scroll. Depois recrie um botão com estados (idle/hover/press/success) no editor do Rive e conecte os inputs. Você acabou de cobrir os dois pedidos mais comuns de "animação do designer" em agência.
Scrollytelling: 3D dirigido por scroll
Objetivo: juntar as duas metades da apostila no formato mais pedido do mercado premium: a página que conta uma história 3D conforme o usuário rola (estilo Apple/Vision Pro).
15.1 A arquitetura
O padrão é sempre o mesmo, independente da stack:
- O scroll vira um progresso normalizado 0→1 (por seção ou global), suavizado (Lenis/scrub).
- Esse progresso alimenta uma timeline que move câmera, objetos, uniforms e DOM.
- O canvas fica fixed atrás do conteúdo; o HTML rola por cima e fornece a altura de scroll.
15.2 Implementação A — GSAP + Three.js (vanilla)
const tl = gsap.timeline({
scrollTrigger: { trigger: '#story', start: 'top top', end: 'bottom bottom', scrub: 1 }
});
// capítulos como posições da timeline:
tl.to(camera.position, { x: 2, y: 1, z: 4, ease: 'none' })
.to(mesh.rotation, { y: Math.PI, ease: 'none' }, '<')
.to(material.uniforms.uProgress, { value: 1, ease: 'none' })
.to('#chapter-2', { opacity: 1, y: 0 }, '<');
// regra: com scrub, ease 'none' nos tweens — o easing vem do dedo do usuário.
Para a câmera seguir um trajeto artístico, anime um progresso 0→1 e leia posições de uma CatmullRomCurve3 (curve.getPointAt(p)) + lookAt em alvos por capítulo.
15.3 Implementação B — R3F + drei ScrollControls
<Canvas>
<ScrollControls pages={4} damping={0.15}>
<Scene /> {/* lê useScroll() no useFrame */}
<Scroll html> {/* DOM que rola junto */}
<section>Capítulo 1</section>
...
</Scroll>
</ScrollControls>
</Canvas>
// dentro de Scene:
const scroll = useScroll();
useFrame(() => {
const p = scroll.offset; // 0..1 global
const cap2 = scroll.range(0.25, 0.25); // 0..1 dentro do capítulo 2
camera.position.lerp(alvo[capAtual], 0.1);
material.uProgress = cap2;
});
Alternativa comum em produção: Lenis controlando o scroll da página + ScrollTrigger emitindo o progresso para dentro do estado (zustand) que a cena R3F consome — desacopla DOM e canvas.
15.4 Truques de produção
- Sequência de imagens: o efeito Apple clássico às vezes nem é 3D — é um vídeo exportado como frames desenhados num canvas 2D conforme o scroll. Saiba oferecer: custa menos e roda em tudo.
- Capítulos com folga: deixe "zonas mortas" (10–15% de scroll sem mudança) entre capítulos para o texto respirar.
- Mobile: reduza páginas de scroll, congele DOF/pós, e teste o pin com barra de endereço que recolhe (unidades
svh/dvh). - Acessibilidade: todo o conteúdo textual precisa existir e fazer sentido com a cena estática (Módulo 17).
Página de produto fictício em 4 capítulos: câmera em curva, material trocando por uniform, texto revelando por capítulo, dissolve no final. Versão desktop (scrub) e mobile (simplificada). Este é O projeto que abre porta de agência — capriche por 2–3 semanas e trate como case, com página de "making of".
Transições de página & "SPA feel"
Objetivo: a cola que faz o site parecer um produto contínuo: preloaders, transições entre rotas, curtains, e WebGL persistente entre páginas.
16.1 O ciclo de uma transição
Toda transição de página é o mesmo autômato: leave (anima saída, bloqueia interação) → swap (troca conteúdo/rota, reseta scroll, refresh de triggers) → enter (anima entrada, libera). Ferramentas por contexto:
- MPA moderno: View Transitions API cross-document (progressive enhancement).
- Sites de agência (vanilla/CMS):
barba.jsou Taxi.js interceptando navegação + hooks de GSAP. - Next/React: Framer Motion (
AnimatePresence) ou orquestração própria com GSAP no App Router.
16.2 Padrões visuais canônicos
- Curtain: um painel (ou 3, com stagger) sobe cobrindo a tela no leave e revela a próxima página no enter.
- Elemento compartilhado: a thumb do card vira o hero do detalhe (FLIP/View Transitions).
- Transição em WebGL: um plano fullscreen com shader (dissolve/ripple do Módulo 07) por cima de tudo — o canvas persiste entre rotas e a transição roda nele.
- Preloader honesto: progresso real de assets (
THREE.LoadingManager→onProgress) alimentando um contador — e a saída do preloader é a primeira frase da coreografia do hero.
16.3 WebGL persistente entre páginas
Padrão avançado de estúdio: um único canvas fixo vive fora do container de rotas; cada página registra suas "cenas" (planos com texturas/efeitos posicionados sobre elementos DOM — técnica de DOM sync: medir getBoundingClientRect e projetar para unidades de mundo). Na troca de rota, os objetos da página antiga animam para fora, os novos para dentro — o WebGL nunca é destruído, então não há flash nem recompilação de shaders. É assim que funcionam os sites em que "as imagens têm ondulação" e sobrevivem à navegação.
Adicione ao projeto do Módulo 13: preloader com progresso real, curtain de 3 painéis entre duas páginas (barba.js ou View Transitions), e uma imagem DOM-sincronizada com shader de hover (distorção por ruído seguindo o mouse). Cheque: scroll resetado, triggers atualizados, foco de teclado movido para o novo conteúdo.
Acessibilidade & performance de motion
Objetivo: o que separa profissional de amador aos olhos de um tech lead: movimento que respeita pessoas e métricas.
17.1 prefers-reduced-motion não é opcional
Distúrbios vestibulares são comuns; parallax e zoom em tela cheia causam náusea real. Reduced motion ≠ sem animação: mantenha opacidade/cor, corte deslocamento grande, parallax, autoplay e scroll-hijacking.
/* CSS */
@media (prefers-reduced-motion: reduce){
*{ animation-duration:.01ms !important; transition-duration:.01ms !important }
}
// GSAP — por design, não por gambiarra:
const mm = gsap.matchMedia();
mm.add('(prefers-reduced-motion: no-preference)', () => { /* coreografia completa */ });
mm.add('(prefers-reduced-motion: reduce)', () => { /* fades simples */ });
17.2 Regras práticas de a11y para sites criativos
- Conteúdo em texto real (não em canvas/textura); a página deve fazer sentido com JS/animações desligados.
- Nada pisca mais de 3x/segundo (risco fotossensível — WCAG 2.3.1).
- Scroll-hijacking mantém teclado funcionando (PgDn, setas, âncoras); pinos não podem prender o foco.
- Cursor custom nunca remove o cursor real sem alternativa; magnetic buttons continuam clicáveis por teclado.
- Canvas decorativo:
aria-hidden="true"; canvas com conteúdo: descrição textual adjacente.
17.3 Core Web Vitals em site criativo
| Métrica | Risco típico em site criativo | Defesa |
|---|---|---|
| LCP (<2.5s) | Esperar 5 MB de GLB para mostrar algo | Hero em imagem/CSS primeiro; 3D hidrata depois; assets do Módulo 09 |
| INP (<200ms) | Main thread ocupada por JS de animação | Compositor-only, Lenis leve, dividir trabalho, web workers/OffscreenCanvas |
| CLS (<0.1) | Canvas/imagens sem dimensão, fonts trocando | Reservar espaço, font-display: swap + métricas ajustadas, animar só transform |
Padrão de carregamento profissional: página funcional e bonita sem o 3D → 3D entra como realce (progressive enhancement) com requestIdleCallback/interseção → preloader só quando a experiência exige (e aí que seja parte da direção de arte).
Times sérios reprovam candidato criativo que ignora reduced-motion e Web Vitals. Inversamente: um case que mostra Lighthouse verde COM WebGL pesado é raro e memorável. Use isso como diferencial explícito na apresentação do portfólio.
Portfólio que contrata
Objetivo: transformar os exercícios desta apostila num portfólio que passa em triagem de agência e gera inbound de freela.
18.1 A anatomia do portfólio de creative dev
- 3 a 5 cases, não 15 experimentos. Qualidade brutal em poucos. O resto vai para uma página "lab/playground".
- O próprio site é o case nº 1. Ele precisa demonstrar: coreografia de entrada, transição de página, um momento WebGL memorável, e abrir liso no celular. Recrutador julga em 30 segundos no celular.
- Cada case conta processo: problema → direção → 2-3 desafios técnicos com trechos de código/GIFs → resultado com números (FPS, peso, draw calls, prazo). Vídeo de 30–60s no topo (nem todo mundo espera o load).
- Créditos honestos: se o design não é seu, credite. Estúdios valorizam quem sabe trabalhar COM designer.
18.2 Distribuição: portfólio parado não contrata
- Awwwards / FWA / CSSDA: submeta seu melhor projeto. Mesmo um "Honorable Mention" muda seu inbound — diretores de estúdio garimpam esses sites para contratar. Custa a taxa de submissão; trate como investimento de marketing.
- Codrops: escrever um tutorial/demos para o Codrops é historicamente o atalho nº 1 para reconhecimento internacional na área.
- X/Twitter + Instagram + LinkedIn: poste vídeos curtos de WIP semanalmente. A comunidade creative dev vive no X; clientes brasileiros vivem no Instagram/LinkedIn. Um clipe de 15s de shader roda mais que qualquer currículo.
- CodePen/Shadertoy: picks do CodePen ainda geram convite de trabalho. Mantenha 1 pen novo por semana durante a fase de construção.
18.3 O teste dos 30 segundos
Checklist do seu site pelo olhar de quem contrata: carrega rápido no 4G? algo memorável acontece sem rolar? dá para achar contato em 1 clique? tem vídeo dos cases? roda no iPhone antigo do diretor de arte? Se alguma resposta é "não", conserte antes de divulgar.
Escreva o case do projeto do Módulo 15 no formato problema→desafios→números ANTES de considerá-lo pronto. Se não há o que escrever, o projeto ainda não é um case — volte e crie os desafios (otimização, um shader autoral, uma interação única).
Mercado, vagas & precificação
Objetivo: mapa realista de onde está o dinheiro: cargos, empregadores, faixas, freela e como se posicionar. (Valores são ordens de grandeza para orientar negociação — pesquise o momento atual antes de fechar qualquer acordo.)
19.1 Os cargos e onde eles vivem
| Cargo | O que faz | Onde |
|---|---|---|
| Creative Developer / Creative Technologist | Sites premiados, campanhas, instalações | Estúdios digitais (BR: agências de publicidade digital e estúdios boutique; internacional/remoto: estúdios de premiação, produtoras) |
| Frontend com foco em motion/interaction | Design systems com motion, produto com "delight" | Startups de produto, fintechs, big techs |
| Dev 3D web / Graphics engineer | Configuradores, e-commerce 3D, mapas, gêmeos digitais, engines | Indústria, imobiliário, automotivo, empresas de visualização |
| Dev de jogos web / playable ads | Jogos HTML5, anúncios jogáveis | AdTech, estúdios de games casuais |
| Motion designer que codifica | Lottie/Rive pipelines, protótipos | Times de brand/marketing |
19.2 Faixas de referência (ordens de grandeza)
- CLT/PJ Brasil: front pleno com diferencial de motion/3D costuma ficar acima da média de front puro; sênior criativo em estúdio forte ou produto: R$ 12–25k+/mês PJ nas praças aquecidas.
- Remoto internacional: é onde a especialidade paga de verdade — creative devs sêniores freelancers cobram na faixa de US$ 60–120+/hora; contratos full-time remotos US$ 70–150k+/ano dependendo do estúdio/país. O portfólio (Módulo 18) é o passaporte; inglês funcional é pré-requisito.
- Freela BR (projetos): landing com motion caprichado R$ 5–20k; scrollytelling/site 3D completo R$ 15–60k+; configurador de produto R$ 10–40k. Varia com cliente e escopo — o teto é definido pelo valor para o negócio, não pela sua hora.
19.3 Precificação de freela sem se machucar
- Escopo fechado por entregáveis, nunca "site animado": nº de seções, nº de interações únicas, dispositivos suportados, rodadas de ajuste (2, e por escrito).
- Precifique por valor + risco: comece do seu custo/hora × horas × 1.5 de margem de imprevisto criativo (efeito "só mais um ajuste no easing" consome semanas), depois ajuste pelo valor para o cliente.
- 50% para começar, 50% na entrega (ou 40/30/30 com marco de aprovação de protótipo). Protótipo de movimento (uma seção real) como marco evita refação da direção inteira.
- Cobre à parte: otimização de assets 3D fornecidos "crus" pelo cliente, compra de licenças (fonts, modelos), manutenção pós-entrega.
- Retainer de performance/motion (X horas/mês) é a renda recorrente natural depois do primeiro projeto.
19.4 Como as vagas testam você
- Teste prático: reproduzir uma seção de site premiado a partir de um vídeo/Figma em 3–7 dias. (O Módulo 13 é literalmente o treino disso.)
- Perguntas recorrentes: pipeline gráfico (M01), draw calls e como reduzi-los (M08), diferença vertex/fragment (M01), como estruturar animação em SPA sem vazamento (M13), reduced-motion (M17), e "explique um efeito do seu portfólio linha a linha".
- Sinais que os estúdios procuram: gosto (referências que você cita), colaboração com designer, e cases com números.
Não se venda como "sei Three.js". Venda um resultado de negócio: "faço páginas de produto 3D que aumentam conversão", "transformo marcas em experiências que ganham prêmio e imprensa", "deixo sites pesados rodando a 60fps em qualquer celular". Especialista em resultado cobra 3–5x mais que generalista em ferramenta — com o mesmo conhecimento técnico desta apostila.
Roteiro de 6 meses + projetos-guia
Objetivo: o plano de execução. 10–15h/semana. Ao fim: 4 cases, presença pública e prontidão para teste técnico.
20.1 O cronograma
| Mês | Módulos | Entregável público |
|---|---|---|
| 1 | 01–03 | Visualizador de produto (Ex. 03) no ar + 4 pens de estudo |
| 2 | 04–05 | Versão R3F + 2 shaders publicados (oceano, aurora) + post comparativo |
| 3 | 06–08 | Peça de partículas/raymarching otimizada com auditoria antes/depois (Ex. 06+08) |
| 4 | 09–13 | Landing "estúdio fictício" completa com GSAP (Ex. 13) — seu futuro site pessoal |
| 5 | 14–17 | Scrollytelling 3D (Ex. 15) com Lottie/Rive de apoio, a11y e Vitals verdes |
| 6 | 18–19 | Portfólio no ar com 4 cases escritos; submissão a Awwwards; 20 candidaturas/propostas enviadas |
20.2 Rotina semanal que funciona
- 60% construir o projeto do mês, 20% estudar (docs, Book of Shaders, breakdowns), 20% publicar (1 vídeo curto ou pen por semana, sem exceção).
- Toda sexta: teste em celular real + throttling. Todo projeto nasce com reduced-motion.
- Recrie 1 efeito de site premiado por mês do zero — é o exercício com melhor razão aprendizado/hora da área.
20.3 Depois dos 6 meses: trilhas de aprofundamento
- Trilha estúdio internacional: WebGPU/TSL a fundo (M10), OGL (lib mínima usada por vários estúdios), escrever para Codrops, inglês de entrevista.
- Trilha produto: Framer Motion/Motion One, design systems de animação, React Server Components + hidratação de canvas, métricas.
- Trilha jogos/experiências: física (Rapier), áudio reativo (Web Audio + FFT em uniforms), multiplayer leve (WebSocket), PlayCanvas/Needle.
- Trilha instalações: TouchDesigner, projeção, sensores — creative coding fora do navegador, diárias altas em eventos.
Se tiver que acompanhar um só número nos 6 meses: peças publicadas por semana. Consistência pública compõe: cada pen/vídeo é um bilhete de loteria permanente que trabalha por você para sempre.
Glossário & recursos
Glossário essencial
| Draw call | Uma ordem de desenho enviada à GPU; a principal métrica de custo de CPU em cena 3D. |
| Uniform / Attribute / Varying | Dado global do frame / dado por vértice / dado interpolado entre vertex e fragment. |
| UV | Coordenadas 2D (0–1) que mapeiam textura sobre a superfície. |
| Normal | Vetor perpendicular à superfície; base de toda iluminação. |
| PBR | Physically Based Rendering — materiais por metalness/roughness reagindo a ambiente. |
| HDRI / env map | Imagem panorâmica de alta faixa dinâmica usada como iluminação/reflexo. |
| SDF | Signed Distance Function — descreve formas por distância; base de raymarching e texto nítido. |
| FBM | Soma fractal de oitavas de ruído; nuvens, terrenos, mármore. |
| Instancing | N cópias de uma geometria em 1 draw call. |
| Render target / FBO | Renderização para textura em vez da tela; base de pós-processamento e GPGPU. |
| Frustum culling | Não desenhar o que está fora do campo da câmera. |
| KTX2 / Basis | Textura comprimida que permanece comprimida na VRAM. |
| Draco / Meshopt | Compressões de geometria para GLTF. |
| Scrub | Vincular o progresso de uma animação ao scroll. |
| FLIP | First-Last-Invert-Play: animar mudanças de layout via transform barato. |
| Stagger | Atraso incremental entre elementos de um grupo animado. |
| Easing | Curva de aceleração de uma animação. |
| Jank | Engasgo perceptível por frames perdidos. |
| WGSL / TSL | Linguagem de shader do WebGPU / camada de nós JS do Three.js que compila para WGSL e GLSL. |
| State machine (Rive) | Grafo de estados de animação controlado por inputs. |
Recursos por módulo
- Docs oficiais: threejs.org (manual + exemplos — leia os exemplos, são a melhor escola), docs.pmnd.rs (R3F/drei), gsap.com/docs, developer.mozilla.org (WAAPI, scroll-driven, view transitions), rive.app/docs, lottiefiles.com.
- Shaders: The Book of Shaders (PT), iquilezles.org (Inigo Quilez), Shadertoy, lygia.xyz (biblioteca modular de GLSL/WGSL).
- Cursos pagos que o mercado respeita: Three.js Journey (Bruno Simon) — cobre do básico a shaders/R3F e é citado em vaga; cursos de motion/creative dev da Awwwards Academy.
- Inspiração/estudo: awwwards.com, tympanus.net/codrops (tutoriais + roundups mensais), openprocessing e o X da comunidade creative dev.
- Ferramentas: Spector.js, gltf.report, leva, r3f-perf, Lenis, barba.js, gltf-transform, Blender + gltfjsx.
- Assets CC0: Poly Haven, Kenney, Quaternius, ambientCG.