Trilha 15 · Gráficos 2D e 3D

Pipeline gráfico (GPU)

A jornada de um vértice 3D até um pixel na tela: CPU, vertex shader, rasterizador, fragment shader, Z-buffer e framebuffer — e como a GPU paraleliza tudo isso.

① Intuição

Uma fábrica de pixels

O pipeline gráfico é como uma linha de montagem: vértices entram num extremo e pixels saem no outro. Cada estágio processa os dados de um jeito diferente — alguns em hardware fixo, outros em shaders programáveis. O que torna a GPU tão rápida é que essa "linha de montagem" tem milhares de trabalhadores em paralelo, cada um processando um vértice ou pixel diferente ao mesmo tempo.

A CPU manda os pedidos (draw calls) com os vértices, texturas e matrizes. O vertex shader transforma cada vértice para espaço de tela. O rasterizador (hardware fixo) converte triângulos em fragmentos. O fragment shader decide a cor final de cada pixel. O Z-buffer descarta pixels ocultos. O framebuffer é a tela.

Por que a GPU é tão mais rápida que a CPU? Uma CPU moderna tem 8-16 núcleos otimizados para tarefas sequenciais com cache grande. Uma GPU tem 4.000-10.000 núcleos menores, otimizados para executar a mesma instrução em milhares de dados simultaneamente (SIMD). Para renderizar pixels — todos independentes — isso é perfeito.
② Visualização interativa

Explore cada estágio

Clique em cada etapa do pipeline para ver o que acontece nela, o que entra, o que sai, e dicas de performance.

💻
CPU / Aplicação
Etapa 1 de 6

A CPU executa a lógica do jogo/app, calcula matrizes de transformação (Model, View, Projection), e envia comandos de desenho à GPU via API gráfica (OpenGL, Vulkan, WebGL, DirectX).

ENTRADA
Código da aplicação (posições, transforms, texturas)
SAÍDA
Comandos de draw + buffers (VBO, IBO)
DICA DE PERFORMANCE
CPU→GPU é o gargalo mais comum: enviar muitos objetos pequenos um de cada vez mata performance. Use instancing e batching para enviar muitos objetos num único drawcall.
③ Explicação técnica

WebGL: um triângulo do zero

// Setup mínimo de WebGL — um triângulo
const gl = canvas.getContext("webgl");

// 1. Compilar shaders
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, `
  attribute vec2 aPos;
  void main() { gl_Position = vec4(aPos, 0, 1); }
`);
gl.compileShader(vs);

const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, `
  precision mediump float;
  void main() { gl_FragColor = vec4(1,0.4,0.4,1); }
`);
gl.compileShader(fs);

// 2. Linkar programa
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);

// 3. Enviar vértices (VBO)
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER,
  new Float32Array([0,0.5, -0.5,-0.5, 0.5,-0.5]),
  gl.STATIC_DRAW
);

// 4. Draw call → dispara o pipeline inteiro!
gl.useProgram(prog);
gl.drawArrays(gl.TRIANGLES, 0, 3);

Técnicas de otimização

// Técnicas de otimização do pipeline

// 1. Batching: um draw call, muitos objetos
gl.drawArraysInstanced(gl.TRIANGLES, 0, 3, 10000); // 10k instâncias

// 2. Frustum culling: não enviar o que não aparece
for (const obj of objects) {
  if (frustum.contains(obj.bounds)) {
    drawObject(obj);  // só renderiza se visível
  }
}

// 3. Early-z: GPU descarta fragmentos ocultos ANTES do fragment shader
// Renderize objetos opacos de frente para trás
opacos.sort((a, b) => a.distCam - b.distCam); // front-to-back

// 4. LOD: modelos simplificados para objetos distantes
const lod = dist > 100 ? "low" : dist > 30 ? "mid" : "high";
drawMesh(meshes[lod]);
Draw calls são caros: cada chamada gl.drawArrays() ou gl.drawElements() tem overhead de CPU-GPU communication — tipicamente 0.1-1ms. Um jogo moderno tenta manter abaixo de 2000 draw calls por frame (a 16ms por frame). Batching (juntar muitos objetos num draw call só) é a otimização número 1 em jogos.
④ Projeto para programar

Primeiro triângulo em WebGL

Mini projeto: renderize um triângulo colorido em WebGL do zero — sem bibliotecas. Escreva o vertex shader e fragment shader em GLSL, compile, linke o programa e faça o draw call.

Projeto principal: renderize um cubo 3D em WebGL com rotação animada. Implemente as matrizes MVP em JavaScript (use gl-matrix para as utilidades), passe como uniforms ao vertex shader, e desenhe com drawElements e um Index Buffer Object (IBO).

Desafio extra: implemente instanced rendering com drawArraysInstanced: renderize 10.000 cubos, cada um com posição e cor diferentes, num único draw call. Compare o FPS com 10.000 draw calls individuais.

⑤ Exercícios rápidos

Teste sua intuição

Por que GPUs são muito mais rápidas que CPUs para renderizar gráficos?
Quais estágios do pipeline são programáveis (shaders) vs. hardware fixo?
O que é o framebuffer e por que double buffering existe?
⑥ Aplicações no mundo real

Onde você encontra isso

🌐

WebGL e WebGPU

Browsers expõem o pipeline gráfico via WebGL (baseado em OpenGL ES 2.0) e WebGPU (API moderna com compute shaders). Three.js, Babylon.js e PlayCanvas abstraem o pipeline. WebGPU permite compute shaders — processamento de dados gerais na GPU diretamente do browser.

🤖

Machine learning na GPU

TensorFlow e PyTorch usam a GPU não para gráficos, mas para multiplicação de matrizes — a operação fundamental de redes neurais. CUDA (NVIDIA) e ROCm (AMD) expõem a GPU como processador SIMD genérico. Uma forward pass de GPT é bilhões de MADs (multiply-add) em paralelo.

📺

Render farms e raytracing

Filmes da Pixar e Disney usam raytracing em render farms com milhares de CPUs (não GPUs) — precisão de ponto flutuante máxima e cenas com bilhões de triângulos. Um frame de Toy Story 4 levava horas para renderizar. GPUs RTX com RT cores tornaram o raytracing viável em tempo real.

← Anterior: Rasterização Próxima: Shaders →