Rasterização
Como triângulos matemáticos se transformam em pixels discretos — o algoritmo central de toda GPU, desde consoles até placas de vídeo modernas.
Triângulos são primitivas universais
Qualquer superfície 3D pode ser aproximada por triângulos. Um modelo humano num jogo tem decenas de milhares de triângulos. Uma cena de filme Pixar pode ter bilhões. Por que triângulos? Porque são a forma mais simples com área — e sempre planares (três pontos sempre definem um plano único).
O processo de converter um triângulo em pixels é chamado de rasterização. O algoritmo clássico usa scanlines: para cada linha horizontal, encontra onde as arestas do triângulo a intersectam, e preenche todos os pixels entre essas interseções. GPUs modernas usam o teste baricêntrico, que é mais paralelizável.
Veja a rasterização acontecer
Escolha um triângulo e clique em Rasterizar. O algoritmo percorre cada linha horizontal (scanline) e preenche os pixels dentro do triângulo.
Scanline e teste baricêntrico
// Rasterização por scanline function rasterize(A, B, C, paintPixel) { const yMin = Math.min(A.y, B.y, C.y); const yMax = Math.max(A.y, B.y, C.y); for (let y = yMin; y <= yMax; y++) { // Encontrar onde as arestas cortam a linha y const xIntersections = getEdgeIntersections(A, B, C, y); const xL = Math.min(...xIntersections); const xR = Math.max(...xIntersections); for (let x = xL; x <= xR; x++) { paintPixel(x, y); } } }
Teste baricêntrico (usado na GPU)
// Teste baricêntrico (moderno — paralelizável na GPU) function edgeFn(ax, ay, bx, by, px, py) { return (bx - ax) * (py - ay) - (by - ay) * (px - ax); } function insideTriangle(A, B, C, px, py) { // Testa o pixel no centro (px+0.5, py+0.5) const d0 = edgeFn(A.x, A.y, B.x, B.y, px, py); const d1 = edgeFn(B.x, B.y, C.x, C.y, px, py); const d2 = edgeFn(C.x, C.y, A.x, A.y, px, py); // Se todos positivos (ou todos negativos): dentro! return (d0 >= 0 && d1 >= 0 && d2 >= 0) || (d0 <= 0 && d1 <= 0 && d2 <= 0); } // Vantagem: GPU testa TODOS os pixels de uma bounding box // em paralelo — sem o loop serial de scanline. // Além disso, d0/d1/d2 são as coordenadas baricêntricas // que permitem interpolar cor, UV e normal por pixel.
Rasterizador em JavaScript
Mini projeto: implemente fillTriangle(ctx, A, B, C, color) usando o algoritmo de scanline — sem usar ctx.fill() do Canvas. Confirme visualmente que o resultado é idêntico ao método nativo.
Projeto principal: implemente Gouraud shading: cada vértice tem uma cor diferente. Use coordenadas baricêntricas para interpolar a cor de cada pixel. Renderize um triângulo com vértices vermelho, verde e azul — o centro deve ser cinza.
Desafio extra: implemente um Z-buffer manual: mantenha um array zBuffer[y][x] com profundidade infinita inicial. Ao rasterizar cada triângulo, interpole Z baricêntricamente e só pinte o pixel se o novo Z for menor que o armazenado. Teste com dois triângulos sobrepostos.
Teste sua intuição
Onde você encontra isso
Triangle strips e fans
Meshes otimizadas usam "triangle strips" onde triângulos adjacentes compartilham vértices — cada novo triângulo precisa só de 1 vértice extra (não 3). Uma tira de N triângulos usa N+2 vértices no lugar de 3N. Reduz largura de banda de memória em 3x.
Rasterização vs. ray tracing
Rasterização projeta e processa triângulos um de cada vez. Ray tracing lança raios da câmera e intersecta com todos os objetos — muito mais realista (reflexos, refrações corretos) mas muito mais lento. GPUs RTX têm hardware dedicado para ray tracing (RT cores).
Tile-based rendering (mobile)
GPUs de celular (Mali, Adreno) dividem a tela em tiles de 16×16 pixels e rasterizam tile por tile — tudo na memória on-chip ultra-rápida, sem precisar ir à RAM externa a cada pixel. Isso economiza ~5x de energia comparado ao rasterizador "imediato" de GPUs desktop.