Amostragem de texturas
Leia uma imagem num shader com texture(), escolha como ela se repete nas bordas e faça ela rolar, ladrilhar ou virar páginas.
Como funciona
Uma textura é uma imagem que a GPU consegue ler de dentro de um shader: uma grade de 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)
texture()
No GLSL ES 3.00 uma textura chega como um uniform Coordenadas UV Uma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela.sampler2D, e texture(sampler, coord) faz a leitura. A coordenada
é normalizada: vai de 0 a 1 na imagem inteira, nos dois eixos, seja qual for o tamanho dela em pixels. É o mesmo
intervalo de uma UVUma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela.uv = gl_FragCoord.xy / resolution
// no escopo do arquivo:
uniform sampler2D uImage; // ligado a uma unidade de textura pelo código do host
// dentro de main():
vec4 color = texture(uImage, uv); // rgba; uma imagem de 8 bits dá 0..1 por canal
O resultado é sempre um vec4. Uma textura de um canal só devolve o valor em r, com g e b em 0 e a em 1, então
uma máscara guardada no canal vermelho é texture(uMask, uv).r. Os cards deste site declaram um sampler por textura,
chamado t_<nome>.
Os centros dos texels ficam em posições de meio texel: numa imagem de 256 texels de largura, o centro da primeira
coluna está em 0.5 / 256.0. textureSize(sampler, 0) devolve o tamanho em texels como um ivec2, o que dá o passo de
um texel para o seguinte:
vec2 texel = 1.0 / vec2(textureSize(uImage, 0)); // um texel, em unidades de UV
vec4 right = texture(uImage, uv + vec2(texel.x, 0.0)); // o vizinho da direita
Quando você quer um texel exato, sem mistura nenhuma, texelFetch(sampler, ivec2(x, y), 0) recebe coordenadas
inteiras de texel, ignora a repetição e a filtragem e devolve valores indefinidos fora da imagem. É a ferramenta certa
para tabelas de consulta e texturas de dados.
Qual lado é para cima
A coordenada de textura (0, 0) é a primeira linha de texels na memória. Imagens na web guardam primeiro a linha de
cima, então uma textura WebGL enviada sem cuidado aparece de cabeça para baixo sob uma UV com a origem embaixo à
esquerda. Ou você inverte no envio (gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)), ou inverte no shader com
vec2(uv.x, 1.0 - uv.y). As engines cuidam disso por você; é no WebGL escrito à mão que isso morde.
Modos de repetição
Uma coordenada fora de 0–1 é permitida. O que ela lê depende do modo de repetição (wrap) da textura, definido na textura ou no sampler pelo código do host, não no GLSL:
- Repeat ladrilha a imagem: 1.25 lê o mesmo que 0.25.
- Clamp to edge repete para sempre os texels da borda: tudo depois de 1 lê a última coluna.
- Mirrored repeat ladrilha espelhando uma cópia sim, outra não, o que esconde a emenda de uma imagem que não foi feita para ladrilhar.
Texturas que ladrilham usam repeat; sprites, cópias da tela e faixas de consulta usam clamp to edge, senão as bordas pegam texels do lado oposto ao serem filtradas. O WebGL2 aceita repeat em texturas de qualquer tamanho, ao contrário do WebGL 1, que exigia potências de dois.
Ladrilhar, deslocar e rolar
Com o modo repeat, basta mover e escalar a coordenada:
vec2 tiled = uv * 4.0; // quatro cópias na horizontal, quatro na vertical
vec2 offset = tiled + vec2(0.3, 0.0); // deslocada em 0.3 de cópia
vec2 scrolled = tiled + vec2(0.1, 0.0) * time; // anda 0.1 cópia por segundo
vec4 c = texture(uImage, scrolled);
Multiplicar a coordenada deixa a imagem menor na tela, porque cada cópia cobre menos da superfície. Prefira o modo de
repetição ao fract() para ladrilhar: o fract salta de 1 para 0 entre as cópias, e a GPU enxerga esse salto quando
escolhe o nível de mipmap, o que aparece como uma linha fina em cada emenda (a página de filtragem e mipmaps explica
o porquê).
Flipbooks
Um flipbook guarda os quadros de uma animação numa textura só, numa grade de colunas e linhas. Cada quadro mostra uma célula: divida a UV pelo tamanho da grade e some o canto da célula.
const vec2 grid = vec2(4.0, 4.0); // 4 colunas, 4 linhas: 16 quadros
float frame = mod(floor(time * 12.0), 16.0); // 12 quadros por segundo, em loop
vec2 cell = vec2(mod(frame, grid.x), grid.y - 1.0 - floor(frame / grid.x));
vec4 c = texture(uImage, (cell + uv) / grid);
O grid.y - 1.0 - coloca o quadro 0 na linha de cima, onde as folhas de sprites costumam começar, quando a origem da
textura está embaixo à esquerda. Deixe alguns texels de margem em volta de cada quadro na folha: a filtragem e os
mipmaps leem um pouco além da borda da célula, e sem margem o quadro vizinho vaza para dentro.
Coordenadas calculadas
A coordenada pode ser qualquer expressão: uma UV rolando, uma UV curvada por uma senoide ou um valor lido de outra
textura. Uma leitura cuja coordenada depende de uma leitura anterior se chama leitura dependente. No GLSL ES 3.00 ela
é permitida, mas cada uma espera a busca anterior terminar, então cadeias longas custam tempo. Um cuidado: dentro de um
if ou de um laço cuja condição muda de pixel para pixel, texture() pode devolver valores errados nas bordas do
desvio, porque a GPU escolhe o nível de mipmap comparando pixels vizinhos e alguns deles não executaram a chamada.
Amostre antes do desvio, ou use ali textureLod com um nível explícito.
Leitura complementar
- texture (abre em uma nova aba)Referência
- texelFetch (abre em uma nova aba)Referência
- Using textures in WebGL (abre em uma nova aba)Documentação