Pular para o conteúdo
Fundamento · TransformaçõesIniciante

Transformações 2D

Translade, gire e escale o espaço UV com vetores e uma mat2, e entenda por que você move o espaço em vez da forma.

Como funciona

Uma forma num shader de fragmento é uma função da coordenada do pixel: um círculo é “os pixels onde length(p) fica abaixo de 0.3”. Não existe um objeto círculo para mover. Para mover, girar ou redimensionar a forma, você muda a coordenada antes que a função da forma a veja. Todo padrão deste site, de um tabuleiro de xadrez a um campo de ruído, se move do mesmo jeito.

Translação

Subtrair um deslocamento da coordenada move a forma por esse deslocamento:

vec2 p = uv * 2.0 - 1.0;                // centralizada, -1..1
vec2 q = p - vec2(0.4, 0.2);            // o centro do círculo agora fica em (0.4, 0.2)
float circle = step(length(q), 0.3);

No começo parece invertido. A forma é desenhada onde q fica perto de zero, e q é zero no pixel p = (0.4, 0.2). Subtrair move a forma para a frente. Essa inversão é o coração deste fundamento: você está transformando o espaço em que a forma é desenhada, então toda operação roda como o inverso da que você quer que a forma faça.

Escala

Dividir a coordenada aumenta a forma: p / 2.0 faz a coordenada crescer na metade da velocidade, então tudo aparece com o dobro do tamanho. Multiplicar diminui a forma e repete mais vezes um padrão: uv * 8.0 encaixa oito casas do tabuleiro de um lado a outro. Dividir x e y por valores diferentes estica a forma, como faz a proporção de tela sem correção. Uma distância medida no espaço escalado também fica escalada; isso importa para os campos de distância, mais adiante na ordem.

Rotação

A rotação por um ângulo a em torno da origem leva (x, y) a (x cos a − y sen a, x sen a + y cos a). Isso é uma multiplicação por matriz, e o GLSL tem um tipo para isso:

mat2 rotate2d(float a) {
    float c = cos(a);
    float s = sin(a);
    return mat2(c, s, -s, c); // colunas: (c, s) e (-s, c)
}

vec2 q = rotate2d(-angle) * p; // gira a forma em +angle, no sentido anti-horário

O sinal de menos é o mesmo inverso de antes: para girar a forma no sentido anti-horário, gire o espaço no sentido horário. angle fica em radianos: uma volta completa é 2π, uns 6.283, e 90° é π / 2.

As matrizes do GLSL são column-major, organizadas por coluna: o construtor preenche a primeira coluna, depois a segunda. mat2(c, s, -s, c) tem (c, s) como primeira coluna, então é a matriz que os livros de matemática escrevem como [c −s; s c]. Ler os quatro números como linhas dá a matriz transposta, que numa rotação é a inversa: ela gira para o outro lado. Esse é o bug de rotação mais comum. O mesmo fato dá um atalho: p * m multiplica pela transposta, então p * rotate2d(angle) é o mesmo que rotate2d(-angle) * p.

Ordem e pivôs

Translação, rotação e escala não comutam: girar e depois mover não é o mesmo que mover e depois girar. Para girar uma forma em torno do próprio centro em vez da origem, mova o espaço para que o centro fique na origem, gire e desenhe ali:

vec2 center = vec2(0.4, 0.2);
// a forma, em center, girada em angle em torno do próprio centro
vec2 q = rotate2d(-angle) * (p - center);

Para uma transformação completa de uma forma (escalar por s, depois girar por a, depois mover até t), o espaço desfaz esses passos na ordem contrária: subtrai t, gira por -a, divide por s.

vec2 q = (rotate2d(-a) * (p - t)) / s;

Corrija a proporção de tela antes de qualquer rotação. Girar em UVs esticadas mistura x e y enquanto eles têm unidades diferentes, e a forma se inclina enquanto gira.

Transformando padrões e texturas

As mesmas operações valem para qualquer coisa que leia uma coordenada. Gire a uv antes do fract e a grade inteira gira. Escale ao longo do tempo e o padrão faz zoom. Some time * speed e ele rola. Para uma textura, gire em torno do meio centralizando antes: rotate2d(-a) * (uv - 0.5) + 0.5, e amostre com o resultado. Aplicar uma transformação diferente em cada pixel, vinda de uma onda ou de um ruído, é UV warping e distorção de domínio: a mesma ideia, com o deslocamento variando pela tela.

Uma mat2 guarda rotação e escala, mas não translação, e é por isso que o deslocamento é somado à parte. Incluir a translação na matriz exige mais uma dimensão, uma mat3 sobre vec3(p, 1.0); em 3D isso vira a mat4 das matrizes de modelo, visão e projeção.

Leitura complementar