Kris

Texturas: a Comilona que Devora sua VRAM

Compressão 3DCompressão de texturaWebGLWebGPU

No artigo anterior, cortamos os vértices pela metade e o modelo ficou menor, mas não virou uma silhueta esbelta — porque o verdadeiro vilão do tamanho ainda está lá: as texturas. Em um modelo PBR, as texturas geralmente representam mais de 80% do volume, e é essa parte que mais incha na memória da GPU.

Este artigo ataca especificamente o problema da "comilona de VRAM" das texturas. Vamos abordar três coisas: por que PNG/JPG têm um "pecado original" aos olhos da GPU; como são os formatos de textura nativos da GPU e por que não podemos usá-los diretamente; e como a combinação Basis Universal + KTX2 conecta esses dois mundos.

Revisão Rápida: Por Que o JPG Explode a VRAM

No artigo anterior, demos uma fórmula:

Ocupação de VRAM = Largura * Altura * 4 bytes (RGBA) * 1.333 (com mipmaps)

Uma textura de 4096×4096, seja um JPG de 1,5MB ou um PNG de 8MB no disco, ocupa cerca de 87MB na memória da GPU. A razão é uma só: a GPU não entende JPG/PNG.

A unidade de amostragem de textura (texture sampler) da GPU só sabe fazer uma coisa: dado um par de coordenadas UV, ler a cor de um bloco de pixels de tamanho fixo. Ela exige que a textura esteja na VRAM como "pixels brutos e achatados". Então, antes de enviar o JPG para a GPU, o navegador precisa descomprimi-lo completamente em pixels RGBA usando a CPU e depois enviar esse bloco inteiro para a VRAM.

Esse processo tem três problemas:

  1. Explosão de VRAM: Os pixels brutos descomprimidos ocupam muito espaço. 87MB não é exagero, é o que a fórmula calcula.
  2. Bloqueio no Upload: Transferir um bloco grande de pixels da memória da CPU para a VRAM da GPU é uma operação lenta que pode travar a renderização do primeiro frame.
  3. Custo de Descompressão na CPU: Descomprimir imagens grandes leva tempo, especialmente em dispositivos móveis.

Continuando com a metáfora da "esponja comprimida" do artigo anterior: PNG/JPG são esponjas espremidas, fáceis de transportar; mas quando chegam na GPU, a esponja absorve água e incha de volta ao tamanho original. O download foi rápido, mas a VRAM não economizou nada.

Formatos Nativos de Textura da GPU: Comprimidos por Natureza na VRAM

Já que a GPU não aceita PNG comprimido, será que podemos manter a textura comprimida também na memória da GPU? A GPU decodifica blocos de pixels individuais em tempo real, quase sem custo.

É exatamente isso que os formatos de textura nativos da GPU (GPU-native texture formats) fazem. As famílias principais:

FamíliaNome CompletoPlataformas PrincipaisCaracterísticas
BC1-7Block CompressionDesktop (PC, Mac)Clássico, compressão por blocos 4×4
ETC1/2Ericsson Texture CompressionMobile (Android/iOS antigos)Padrão antigo para mobile
ASTCAdaptive Scalable Texture CompressionMobile/VR (dispositivos novos)Flexível, melhor qualidade, ajustável por bloco
PVRTCPowerVRiOS antigoSendo gradualmente substituído por ASTC

O ponto em comum desses formatos: a textura é comprimida em pequenos blocos de 4×4 pixels, e a GPU decodifica apenas esse bloco sob demanda. O que é decodificado não é um pixel, mas um bloco. A vantagem é que a ocupação de VRAM é reduzida em uma proporção fixa, independentemente do conteúdo.

Comparando:

PNG/JPG (Tradicional)Formato Nativo da GPU
Tamanho no discoPequeno (JPG especialmente)Médio (compressão por bloco, taxa fixa)
Ocupação de VRAMGrande (descomprime para pixels brutos)Pequena (compressão por bloco, residente)
Upload para GPULento (descompressão na CPU + transferência grande)Rápido (transferência direta, sem descompressão)
Velocidade de amostragemRápida (já são pixels brutos)Rápida (decodificação por hardware em tempo real)

Parece que o formato de GPU é a solução perfeita. Então por que não podemos usá-los diretamente?

O Problema: Dispositivos Diferentes Reconhecem Formatos Diferentes

Este é o maior problema dos formatos de textura da GPU — a fragmentação.

  • PCs desktop reconhecem BC1-7, mas não ASTC
  • Celulares Android reconhecem ETC2/ASTC, mas a maioria não reconhece BC
  • iOS (após A7) reconhece ASTC, modelos antigos reconhecem PVRTC
  • WebGPU/WebGL usam as mesmas capacidades de hardware por trás do dispositivo

Se você quer que uma textura "exista em formato nativo de GPU em todos os dispositivos", precisa preparar uma versão para cada plataforma. Um produto que roda em desktop + Android + iOS precisa de três versões da mesma textura: BC + ETC2/ASTC. O tamanho do pacote triplica, o trabalho triplica.

Pior ainda: na Web, você não sabe qual dispositivo o usuário está usando para abrir a página. Pré-gerar todos os formatos é irreal, e detectar em tempo de execução é tarde demais.

Basis Universal: Uma Codificação, Transcodiicação em Qualquer Lugar

O Basis Universal (ou simplesmente Basis) foi criado para resolver essa fragmentação. A ideia em uma frase:

Primeiro, codifique a textura em um "formato intermediário". Depois, em tempo de execução, transcode (transcodifique) para o formato nativo correspondente à capacidade da GPU do dispositivo atual.

Fluxo de transcodificação (diagrama):

Textura Original (PNG/JPG)
      │  Codificação offline única (lenta, feita apenas uma vez)
      ▼
Formato Intermediário Basis (ETC1S ou UASTC)
      │  Empacotado no contêiner KTX2
      ▼
Publicado na Web ──┬── GPU Desktop ──→ Transcodiicação em runtime → BC1/3/7
                  ├── Android ───→ Transcodiicação em runtime → ETC2
                  └── iOS/VR ────→ Transcodiicação em runtime → ASTC

Pontos-chave:

  • A codificação offline é feita apenas uma vez, resultando em uma representação intermediária compacta
  • A transcodificação em runtime é extremamente rápida (puramente computacional, na ordem de milissegundos), e transcode formatos de bloco, sem necessidade de descompressão pixel a pixel
  • Após a transcodificação, o que vai para a VRAM é o formato nativo real da GPU, a ocupação de VRAM é calculada pela compressão por bloco, idêntica ao formato nativo da GPU

O Basis oferece dois modos de codificação intermediária, que serão detalhados no próximo artigo. Por enquanto, guarde os nomes:

  • ETC1S: Taxa de compressão extremamente alta, ideal para mapas de cor/difusão (albedo)
  • UASTC: Qualidade superior, ideal para mapas de normais e outras texturas sensíveis à precisão

KTX2: O Contêiner Padrão para Texturas de GPU

Ainda há um problema de engenharia: onde colocar os dados codificados pelo Basis, como marcá-los e como associá-los ao glTF? A resposta é KTX2.

KTX2 (Khronos Texture 2) não é mais um formato de imagem, mas um formato de contêiner — assim como um .zip não se importa se contém documentos ou imagens, o KTX2 apenas empacota dados de textura de GPU (incluindo os codificados pelo Basis) em uma estrutura padrão, com metadados (formato, níveis de mipmap, espaço de cores, etc.).

No glTF, o KTX2 é integrado através da extensão KHR_texture_basisu: a textura não é mais um arquivo PNG, mas um arquivo KTX2 contendo codificação Basis. Ao carregar, o motor detecta a capacidade do dispositivo e transcode para o BC/ETC/ASTC correspondente.

Vamos organizar a relação entre os três, para não confundir:

NomePapelAnalogia
Basis UniversalEsquema de codificação (como comprimir a textura em formato intermediário)Um "algoritmo de compressão"
KTX2Formato de contêiner (como empacotar os dados codificados)Uma "caixa"
KHR_texture_basisuExtensão glTF (informa ao motor que é uma textura Basis)Um "rótulo"

Um arquivo KTX2 pode conter internamente codificação Basis (multiplataforma) ou algum formato nativo (como BC7 diretamente). Na Web, 99% dos casos usam Basis, porque queremos exatamente "uma codificação, transcodificação em qualquer lugar".

Exemplo Prático de VRAM: Comparação com Textura 4096

Combinando a fórmula anterior com os formatos de GPU, vejamos a ocupação real de uma textura RGBA 4096×4096 em diferentes abordagens:

AbordagemTamanho no DiscoOcupação de VRAM (com mipmaps)Velocidade de UploadMultiplataforma
PNG~8MB~87MBLenta (requer descompressão)
JPG~1,5MB~87MBLenta (requer descompressão)
WebP~2MB~87MBLenta (requer descompressão)
KTX2 (ETC1S)~2-3MB~11-14MBRápida✅ (transcodificação)
KTX2 (UASTC)~6-8MB~22MBRápida✅ (transcodificação)

De onde vêm os números de VRAM: a compressão por blocos da GPU geralmente é calculada em 4bpp (4 bits por pixel) ou 8bpp. Uma textura 4096×4096 em 4bpp tem cerca de 8MB, com mipmaps multiplicamos por 1.333 ≈ 11MB. UASTC geralmente transcode para 8bpp, então cerca de 22MB.

O importante não são os números exatos de uma linha, mas estas duas conclusões:

  1. Formatos tradicionais (PNG/JPG/WebP) têm ocupação de VRAM quase idêntica — todos são pixels brutos descomprimidos, 87MB. Por menor que seja o disco, a VRAM não economiza.
  2. KTX2 reduz a VRAM diretamente para 1/4 a 1/8, e o tamanho no disco também não fica em desvantagem.

É por isso que VR e Web mobile quase sempre usam KTX2 — quantas texturas de 87MB cabem em um celular com 2GB de VRAM? Com 11MB, cabem 7.

Matriz de Suporte de Plataformas: Quais GPUs Reconhecem Quais Formatos

Embora o Basis nos proteja dos detalhes, entender o mapeamento subjacente ajuda na solução de problemas. Este é o suporte atual dos principais dispositivos para formatos nativos:

Plataforma / DispositivoBC1-7ETC2ASTCPVRTC
PC Desktop (D3D11/12, Vulkan, WebGPU)Parcial (GPUs novas)
macOS (Metal)✅ (modelos novos)
Android (principal)
iOS (A8+)✅ (modelos antigos)
WebGL 2Depende da extensãoParcial
WebGPU✅ (desktop)✅ (depende do dispositivo)

O Basis detecta essas capacidades em tempo de execução e transcode a mesma codificação intermediária para a mais adequada. É por isso que a camada Basis é praticamente insubstituível na Web — você não pode prever o dispositivo do usuário antes da publicação.

Comparação do Fluxo de Upload: Tradicional vs. Formato GPU

Finalmente, vamos consolidar a diferença com um diagrama de fluxo.

PNG/JPG tradicional:

Arquivo PNG ──download──> Memória CPU ──descompressão CPU (lenta)──> Bloco de pixels RGBA ──upload (grande, lento)──> VRAM (87MB)

KTX2 + Basis:

Arquivo KTX2 ──download──> Memória CPU ──transcodificação em runtime (rápida)──> Formato de bloco GPU ──upload (pequeno, rápido)──> VRAM (11MB)

O segundo fluxo elimina a grande etapa de "descompressão pixel a pixel na CPU" e a quantidade de dados enviados é uma ordem de magnitude menor. Renderização do primeiro frame mais rápida e economia de VRAM são os valores centrais dessa abordagem.

Próximos Passos

Teoria explicada, agora vamos à prática. No próximo artigo, usaremos toktx e gltf-transform para comprimir texturas de verdade em KTX2, carregá-las no Three.js / Babylon.js e discutir como escolher entre ETC1S e UASTC e ajustar os parâmetros de compressão.

Apoie-nos