Filtragem e mipmaps
Como a GPU mistura texels quando uma textura aparece maior ou menor que o próprio tamanho, e por que texturas distantes cintilam sem mipmaps.
Como funciona
Os pixels da tela e os texelsUm elemento de uma textura, a versão do pixel numa textura. Uma amostra mistura texels próximos ou devolve o mais próximo. Texel Um elemento de uma textura, a versão do pixel numa textura. Uma amostra mistura texels próximos ou devolve o mais próximo.texel size = 1 / textureSize(sampler, 0)
Ampliação: nearest ou linear
Quando uma textura aparece maior que o próprio tamanho, cada pixel cai em algum lugar entre os centros dos texels.
- Nearest devolve o texel mais próximo. As bordas continuam duras e os texels viram quadrados visíveis: a escolha certa para pixel art e para dados que não podem ser misturados, como índices de uma paleta.
- Linear (bilinear) mistura os quatro texels mais próximos, com pesos pela proximidade da amostra a cada centro. A imagem fica suave em vez de quadriculada. É a escolha padrão para fotos e para a maioria das superfícies.
A filtragem bilinear também é uma mistura de graça: uma amostra posta exatamente entre dois texels devolve a média deles pelo preço de uma leitura. Kernels de desfoque usam isso para ler metade das vezes.
Redução e aliasing
Quando uma textura aparece menor que o próprio tamanho, a filtragem bilinear continua lendo só quatro texels por pixel e pula tudo o que fica entre eles. Quais texels são lidos muda quando a superfície anda uma fração de pixel, então os detalhes finos viram um ruído que rasteja e cintila, e padrões regulares como tijolos ou listras produzem moiré. Isso é aliasing: a textura tem mais detalhe do que os pixels conseguem mostrar, e as amostras pontuais pegam esse detalhe ao acaso.
Mipmaps
Uma cadeia de mipmapsUma cadeia de cópias de uma textura, cada uma com metade do tamanho da anterior. A GPU lê as cópias menores em superfícies distantes para que elas não cintilem. Mipmap Uma cadeia de cópias de uma textura, cada uma com metade do tamanho da anterior. A GPU lê as cópias menores em superfícies distantes para que elas não cintilem.level = log2(texels per pixel)
A GPU escolhe um nível por pixel a partir de quantos texels o pixel cobre. Ela mede isso comparando a coordenada de textura dentro de cada bloco de 2×2 pixels que sombreia junto: se a coordenada anda 4 texels de um pixel para o próximo, o nível 2 (4 = 2²) tem mais ou menos um texel por pixel. O nível é o log na base 2 desse passo:
// no escopo do arquivo:
uniform sampler2D uImage;
// dentro de main(): mais ou menos o nível que a GPU escolheria
vec2 texCoord = uv * vec2(textureSize(uImage, 0)); // em texels
vec2 dx = dFdx(texCoord); // variação até o próximo pixel à direita
vec2 dy = dFdy(texCoord); // variação até o próximo pixel acima
float level = 0.5 * log2(max(dot(dx, dx), dot(dy, dy)));
Os modos que usam mipmaps diferem no jeito de tratar os níveis:
- Mipmap nearest (por exemplo
LINEAR_MIPMAP_NEAREST) lê um nível só, bilinear dentro dele. É barato, mas a troca de nível pode aparecer como uma linha visível atravessando um piso. - Trilinear (
LINEAR_MIPMAP_LINEAR) lê os dois níveis mais próximos de forma bilinear e mistura os dois pela parte fracionária do nível. Custa duas leituras bilineares e esconde a troca.
No WebGL o filtro de redução de uma textura começa como NEAREST_MIPMAP_LINEAR. Até você chamar gl.generateMipmap ou
escolher um filtro sem mipmaps, a textura fica incompleta e toda leitura devolve preto. Texturas em que você renderiza
também não têm mipmaps, a não ser que você os gere depois de cada renderização.
Filtragem anisotrópica
Num piso visto em ângulo rasante, a área coberta por um pixel é uma faixa longa e fina: cobre muitos texels em
profundidade e poucos na largura. O nível é escolhido pelo lado mais longo, então a textura fica borrada também na
largura. A filtragem anisotrópica tira várias amostras ao longo do lado comprido, num nível mais nítido, o que mantém
nítidas as superfícies rasantes. É uma configuração do sampler; no WebGL2 ela vem da extensão
EXT_texture_filter_anisotropic, com no máximo 16 amostras na maioria do hardware.
Assumindo o controle no shader
Três chamadas do GLSL substituem a escolha automática:
texture(sampler, uv, bias)somabiasao nível: negativo fica mais nítido (e com mais aliasing), positivo fica mais suave.textureLod(sampler, uv, level)lê um nível exato. Ela não precisa dos pixels vizinhos, então é segura dentro de desvios e laços, e é assim que cadeias de desfoque leem um nível reduzido específico.textureGrad(sampler, uv, dPdx, dPdy)usa a taxa de variação que você passa em vez de medi-la.
O textureGrad corrige a emenda que ladrilhar com fract() deixa. Em cada borda de ladrilho a coordenada repetida
salta de 1 para 0, a variação medida parece enorme e a GPU lê o menor nível numa fileira de pixels. Meça a variação na
coordenada antes de repeti-la:
vec2 tiled = uv * 4.0;
vec4 c = textureGrad(uImage, fract(tiled), dFdx(tiled), dFdy(tiled)); // sem emenda
Padrões feitos só de matemática não têm mipmaps, então uma listra procedural fina sofre aliasing igualzinho a uma textura sem filtragem. As mesmas derivadas que escolhem o nível de mipmap deixam um padrão se borrar na largura de um pixel, assunto da página de derivadas.
Leitura complementar
- Mipmap (abre em uma nova aba)Referência
- Anisotropic filtering (abre em uma nova aba)Referência
- textureLod (abre em uma nova aba)Referência