Texturas, o Glutão que Devora Sua VRAM
Na última vez, reduzimos os vértices pela metade e o modelo encolheu um pouco, mas não se tornou um raio—porque o verdadeiro consumidor de espaço ainda estava lá: texturas. Em um modelo PBR, as texturas geralmente ocupam mais de 80% do tamanho, e é essa parte que mais estoura na VRAM.
Este artigo é a cura para a "voracidade de VRAM" das texturas. Três pontos: por que PNG/JPG é condenável aos olhos da GPU; como são os formatos nativos de textura da GPU e por que você não pode usá-los diretamente; e como Basis Universal + KTX2 conectam tudo isso.
Revisão: por que JPG faz a VRAM explodir
No artigo anterior, apresentamos uma fórmula:
Uso de VRAM = largura * altura * 4 bytes (RGBA) * 1.333 (com mipmaps)
Uma textura de 4096×4096, seja ela um JPG de 1.5MB ou um PNG de 8MB no disco, se torna ~87MB na VRAM. Há uma razão: a GPU não entende JPG/PNG.
O sampler de textura da GPU entende uma coisa: dada uma coordenada UV, ler uma cor de um bloco de pixels de tamanho fixo. Ele requer que a textura esteja "disposta em pixels brutos" na VRAM. Então, antes do navegador carregar um JPG na GPU, ele deve primeiro descomprimi-lo completamente em pixels RGBA na CPU, depois empurrar todo o bloco para a VRAM.
Esse processo tem três problemas:
- Explosão de VRAM: os pixels brutos descomprimidos são enormes. 87MB não é exagero—é o que a fórmula calcula.
- Bloqueio de upload: mover um grande bloco de pixels da memória da CPU para a VRAM da GPU é uma operação lenta que bloqueia o primeiro quadro.
- Custo de decodificação na CPU: decodificar uma imagem grande é por si só demorado, especialmente em mobile.
Expandindo a metáfora da "esponja comprimida" da última vez: PNG/JPG é uma esponja espremida para facilitar o transporte; ao chegar na GPU, a esponja absorve água e se expande de volta ao tamanho total. Download ficou mais rápido; VRAM não economizou nada.
Os formatos nativos de textura da GPU: comprimidos na VRAM por natureza
Como a GPU não aceita PNG pré-comprimido, podemos manter a textura comprimida mesmo dentro da VRAM? A GPU decoda um pequeno bloco de pixels sob demanda durante a amostragem, com custo praticamente zero.
É exatamente isso que os formatos nativos de textura da GPU fazem. Famílias representativas:
| Família de formato | Nome completo | Principais plataformas | Observações |
|---|---|---|---|
| BC1-7 | Block Compression | Desktop (PC, Mac) | Veterano, compressão de blocos 4×4 pixels por geração |
| ETC1/2 | Ericsson Texture Compression | Mobile (Android/iOS antigos) | Padrão mobile antigo |
| ASTC | Adaptive Scalable Texture Compression | Mobile/VR (dispositivos mais recentes) | Flexível, melhor qualidade, ajustável por bloco |
| PVRTC | PowerVR | iOS antigos | Sendo substituído pelo ASTC |
Esses formatos compartilham uma característica: as texturas são armazenadas comprimidas em blocos de 4×4 pixels (blocks), e a GPU decoda um pequeno bloco sob demanda durante a amostragem—o que sai não é um pixel único, mas um bloco inteiro. O benefício é que o uso de VRAM encolhe por uma proporção fixa, independentemente do conteúdo.
Comparação:
| PNG/JPG (tradicional) | Formatos nativos da GPU | |
|---|---|---|
| Tamanho no disco | Pequeno (JPG especialmente) | Médio (comprimido por blocos, bitrate fixo) |
| Uso de VRAM | Grande (descomprimido em pixels brutos) | Pequeno (comprimido por blocos, residente) |
| Upload para GPU | Lento (decodificação CPU + grande transferência) | Rápido (apenas mover, sem decodificação) |
| Velocidade de amostragem | Rápida (já são pixels brutos) | Rápida (decodificação em tempo real por hardware) |
Formatos da GPU parecem a solução perfeita. Então por que não podemos simplesmente usá-los?
Eis o problema: dispositivos diferentes reconhecem formatos diferentes
Este é o maior obstáculo dos formatos de textura GPU—fragmentação.
- PCs desktop reconhecem BC1-7, não ASTC
- Celulares Android reconhecem ETC2/ASTC, na maioria não BC
- iOS (A7+) reconhece ASTC, dispositivos mais antigos PVRTC
- WebGPU/WebGL operam sobre as mesmas capacidades de hardware por trás do dispositivo
Se você quer que uma textura "exista como formato nativo da GPU em todos os dispositivos", precisa preparar uma cópia separada para cada plataforma. Um produto que envia desktop + Android + iOS significa que a mesma textura precisa de BC + ETC2/ASTC—três versões. O pacote triplica, o esforço de engenharia triplica.
Pior ainda, na web você não sabe que dispositivo o usuário usa para abrir sua página. Pré-gerar todos os formatos é impraticável, e detecção em tempo real vem tarde demais.
Basis Universal: codifique uma vez, transcodifique em todo lugar
Basis Universal (Basis para abreviar) nasceu para resolver essa fragmentação. Sua ideia em uma frase:
Primeiro codifique a textura em um "formato intermediário", depois em tempo real transcodifique-o para o formato nativo correspondente com base nas capacidades da GPU do dispositivo atual.
Fluxo de transcodificação (esquemático):
Textura de origem (PNG/JPG)
│ codificação offline uma vez (lenta, feita uma vez)
▼
Formato intermediário Basis (ETC1S ou UASTC)
│ empacotado em um contêiner KTX2
▼
Publicar na web ──┬── GPU Desktop ──→ transcodificação em tempo real → BC1/3/7
├── Android ────→ transcodificação em tempo real → ETC2
└── iOS/VR ─────→ transcodificação em tempo real → ASTC
Pontos importantes:
- A codificação offline acontece uma vez, produzindo uma representação intermediária compacta
- A transcodificação em tempo real é muito rápida (computação pura, alguns milissegundos), e transcodifica formatos por blocos—sem necessidade de descompressão por pixel
- O que entra na VRAM após a transcodificação é um formato nativo real da GPU, então o uso de VRAM é calculado em taxas de compressão por blocos, idênticas aos formatos nativos da GPU
O Basis oferece dois modos de codificação intermediária; o próximo artigo os detalha, mas memorize os nomes:
- ETC1S: taxa de compressão extremamente alta, adequado para difuso/albedo e outros mapas de cores
- UASTC: qualidade superior, adequado para normais e outros mapas sensíveis a precisão
KTX2: o contêiner padrão para texturas de GPU
Ainda há uma questão de engenharia: para onde vão os dados codificados do Basis, como são marcados e como se relacionam com glTF? A resposta é KTX2.
KTX2 (Khronos Texture 2) não é mais um formato de imagem—é um formato contêiner. Assim como um .zip não se importa se contém documentos ou imagens, o KTX2 apenas embala dados de textura de GPU (incluindo codificação Basis) em uma estrutura padrão com metadados (formato, níveis de mipmap, espaço de cores, etc.).
No glTF, o KTX2 se conecta por meio da extensão KHR_texture_basisu: a textura não é mais um arquivo PNG, mas um arquivo KTX2 contendo codificação Basis. Na hora do carregamento, o engine detecta as capacidades do dispositivo e transcodifica para BC/ETC/ASTC correspondente.
Vamos desatar os três papéis—não confunda:
| Nome | Papel | Analogia |
|---|---|---|
| Basis Universal | Esquema de codificação (como comprimir uma textura no formato intermediário) | Um "algoritmo de compressão" |
| KTX2 | Formato contêiner (como empacotar os dados codificados) | Uma "caixa" |
| KHR_texture_basisu | Extensão glTF (informa ao engine que esta é uma textura Basis) | Um "rótulo" |
Um arquivo KTX2 pode conter codificação Basis (cross-platform) ou um formato nativo (ex: BC7 bruto). Na web, 99% das vezes contém Basis, porque o que queremos é "codificar uma vez, transcodificar em todo lugar."
Exemplo de VRAM: uma textura de 4096 comparada
Combinando a fórmula e os formatos de GPU, aqui está o footprint real de uma textura RGBA de 4096×4096 sob diferentes opções:
| Opção | Tamanho no disco | Uso de VRAM (com mipmaps) | Velocidade de upload | Cross-platform |
|---|---|---|---|---|
| PNG | ~8MB | ~87MB | Lenta (precisa de decodificação) | ✅ |
| JPG | ~1.5MB | ~87MB | Lenta (precisa de decodificação) | ✅ |
| WebP | ~2MB | ~87MB | Lenta (precisa de decodificação) | ✅ |
| KTX2 (ETC1S) | ~2-3MB | ~11-14MB | Rápida | ✅ (transcodificação) |
| KTX2 (UASTC) | ~6-8MB | ~22MB | Rápida | ✅ (transcodificação) |
De onde vêm os valores de VRAM: a compressão por blocos da GPU geralmente conta em 4bpp (4 bits por pixel) ou 8bpp. 4096×4096 a 4bpp são cerca de 8MB, ×1.333 com mipmaps ≈ 11MB. UASTC na maioria transcodifica para 8bpp, então cerca de 22MB.
O ponto não é o número exato em qualquer linha, mas estes dois:
- Formatos tradicionais (PNG/JPG/WebP) têm uso de VRAM praticamente idêntico—todos pixels brutos descomprimidos, 87MB. Não importa quão pequenos no disco, VRAM não é economizada.
- KTX2 reduz a VRAM para 1/4 a 1/8, e o tamanho no disco também é competitivo.
É por isso que VR e mobile web quase sempre usam KTX2—quantas texturas de 87MB cabem na VRAM de 2GB de um celular? A 11MB, você cabe sete.
Matriz de suporte de plataforma: quais GPUs reconhecem quais formatos
O Basis nos protege dos detalhes, mas entender o mapeamento subjacente ajuda na resolução de problemas. Aqui está o suporte atual de dispositivos principais para formatos nativos:
| Plataforma / dispositivo | BC1-7 | ETC2 | ASTC | PVRTC |
|---|---|---|---|---|
| PC Desktop (D3D11/12, Vulkan, WebGPU) | ✅ | ❌ | Parcial (GPUs mais recentes) | ❌ |
| macOS (Metal) | ✅ (máquinas mais recentes) | ❌ | ✅ | ❌ |
| Android (principal) | ❌ | ✅ | ✅ | ❌ |
| iOS (A8+) | ❌ | ✅ | ✅ | ✅ (dispositivos mais antigos) |
| WebGL 2 | Depende de extensões | ✅ | Parcial | ❌ |
| WebGPU | ✅ (desktop) | ✅ | ✅ (dependente do dispositivo) | ❌ |
O Basis sonda essas capacidades em tempo real e transcodifica a mesma codificação intermediária no melhor resultado. É por isso que essa camada do Basis é praticamente insubstituível na web—você não pode prever o dispositivo do usuário antes de publicar.
Fluxo de upload comparado: tradicional vs formatos GPU
Finalmente, fixe a diferença em um diagrama de fluxo.
PNG/JPG tradicional:
Arquivo PNG ──download──> Memória CPU ──Decodificação CPU (lenta)──> Bloco de pixels RGBA ──Upload (grande, lento)──> VRAM (87MB)
KTX2 + Basis:
Arquivo KTX2 ──download──> Memória CPU ──Transcodificação em tempo real (rápida)──> Formato por blocos da GPU ──Upload (pequeno, rápido)──> VRAM (11MB)
O segundo elimina o grande passo de "decodificação por pixel na CPU" e os dados carregados são uma ordem de grandeza menores. Primeiro quadro mais rápido, menos VRAM—esse é o valor central dessa abordagem.
Próximo passo
Teoria concluída; o próximo artigo é prático. Usaremos toktx e gltf-transform para comprimir texturas em KTX2 de fato, carregá-las no Three.js / Babylon.js e discutir como escolher entre ETC1S e UASTC e como ajustar os parâmetros de compressão.