19. Mapeamento de textura

Vimos como é possível produzir imagens mais realistas aplicando efeitos de iluminação usando um modelo relativamente simples como o de reflexão de Phong. Esses efeitos funcionam bem para superfícies lisas com cores uniformes como, por exemplo, calotas de carros, paredes de gesso e copos de plástico, mas para renderizar superfícies com acabamentos mais complexos, com uma pia de mármore, precisamos também usar modelos mais complexos e detalhados. Por exemplo, poderíamos tentar modelar uma superfície por meio de uma textura matemática como um tabuleiro de xadrez ou tijolos em uma parede, mas isso pode ser inviável para texturas mais complexas com padrões irregulares.

Uma alternativa para evitar o uso de modelos matemáticos é aplicar texturas. A ideia é tirar uma foto (ou fotos) de uma textura que aparece na natureza e mapear essa foto na superfície de um objeto, como se estivéssemos embrulhando o objeto com a foto.

19.1. Texturas e espaço de textura

Embora originalmente projetado para preencher superfícies texturizadas, o processo de mapeamento de textura pode ser usado para mapear (ou “embrulhar”) qualquer imagem digitalizada em uma superfície. Por exemplo, suponha que queremos renderizar uma imagem da Mona Lisa na capa de um livro, ou envolver um mapa (imagem 2D) da Terra em torno de uma esfera, ou desenhar um gramado em um campo de futebol. Poderíamos baixar uma fotografia digitalizada dessas texturas e depois mapear essas imagens sobre as superfícies como parte do processo de renderização.

As imagens podem ser armazenadas em vários formatos como PNG, JPEG, GIF, PPM, PDF, etc. Não vamos discutir esses formatos. Em vez disso, vamos considerar que uma imagem é simplesmente uma matriz bidimensional de valores RGB. As imagens são normalmente indexadas linha por linha com origem no canto superior esquerdo.

Para simplificar nossa discussão, vamos supor que a imagem de uma textura é quadrada, de dimensões \(n \times n\) (em versões mais antigas do OpenGL \(n\) precisava ser uma potência de 2. O WebGL 2.0 não possui essa restrição, mas vamos mantê-la em nossa descrição pois elas tendem a criar texturas mais eficientes). Ao invés de pixel, vamos chamar cada elemento da textura como texel, de texture element.

Observe que o número de pixels em uma superfície em geral não vai corresponder ao número de texels. Assim, ao invés de usar índices na matriz de texels, vamos utilizar uma função que mapeia um ponto \((s, t)\) no espaço de textura bidimensional para um valor RGB. Ou seja, dado qualquer par \((s, t)\), \(0 \le s, t < 1\), a imagem da textura define que o valor de \(T(s, t)\), que é um valor RGB. Observe que o intervalo \([0, 1)\) não depende do tamanho das imagens. Com isso podemos usar imagens de tamanhos diferentes sem a necessidade de modificar o processo de mapeamento de textura.

Por exemplo, assumindo que uma matriz de textura \(I[n][n]\) seja indexada por linha e coluna de 0 a \(n-1\) com a origem no canto superior esquerdo. Nosso espaço de textura \(T(s, t)\) é definido por seu eixo horizontal \(s\) e eixo vertical \(t\) com origem no canto inferior esquerdo (seguindo as convenções do WebGL). Podemos então aplicar a seguinte função para determinar um ponto no espaço da imagem para o elemento correspondente da textura (veja a Figura Fig. 19.1):

\(T(s, t) = I[ floor((1-t)n)] [floor(s\;n)]\;\;\;\;\), para \(0 \le s, t < 1\).
Espaço da textura.

Fig. 19.1 Espaço da textura.

Em muitos casos, é conveniente pensar que a textura é uma função infinita. Fazemos isso imaginando que a imagem da textura se repete ciclicamente em todo o plano. Isso é útil ao aplicar uma textura pequena, como um pedaço de grama, a uma superfície muito grande, como a superfície de um campo de futebol. Isso às vezes é chamado de textura repetida. Neste caso, podemos modificar a função acima para:

\(T(s, t) = I[ floor((1-t)\;n)\; mod \; n] [floor(s\;n) \; mod \; n]\;\;\;\;\), para \(0 \le s, t < 1\).

19.2. Função de desembrulho e parametrizações

Suponha que desejamos “embrulhar” uma imagem de textura bidimensional na superfície de uma esfera de raio unitário. Precisamos definir uma função de embrulho (wrapping function) que consiga fazer isso. A superfície reside no espaço tridimensional, então a função de embrulho precisaria mapear um ponto \((s, t)\) no espaço da textura para o ponto correspondente \((x, y, z)\) no espaço tridimensional. Ou seja, a função de embrulho pode ser pensada como uma função \(W(s, t)\) que mapeia um ponto no espaço de textura bidimensional para um ponto \((x, y, z)\) no espaço tridimensional.

Mais tarde veremos que não é a função de embrulho que precisamos calcular, mas sim sua inversa. Então, vamos considerar o problema de calcular uma função \(W^{-1}\) que mapeia um ponto \((x, y, z)\) na esfera para um ponto \((s, t)\) no espaço de parâmetros. Essa é chamada de função de desembrulho.

Isso normalmente é feito primeiro calculando uma parametrização bidimensional da superfície. Isso significa que associamos cada ponto na superfície do objeto com duas coordenadas \((u, v)\) no espaço da superfície. Implicitamente, podemos pensar nisso como três funções, \(x(u, v)\), \(y(u, v)\) e \(z(u, v)\), que mapeiam o par de parâmetros \((u, v)\) para as coordenadas \((x, y, z)\) do ponto na superfície.

Para determinar a função de desembrulho vamos mapear um ponto \((x,y,z)\) para o par de parâmetros correspondente \((u,v)\) e então mapear esse par de parâmetros para o ponto desejado \((s,t)\) no espaço de textura.

A seguir vamos ver como fazer isso com uma esfera.

19.3. Exemplo: parametrizando uma esfera

Vamos usar uma esfera de raio unitário, centrada na origem. Queremos encontrar a função de desembrulho \(W^{-1}\) que mapeia qualquer ponto \((x, y, z)\) na esfera para um ponto \((s, t)\) no espaço de textura.

Primeiro precisamos criar uma parametrização de superfície para a esfera. Um ponto na esfera pode ser representado por dois ângulos, que correspondem à latitude e longitude do ponto.

Usaremos uma abordagem um pouco diferente. Qualquer ponto na esfera pode ser expresso por dois ângulos, \(\varphi\) e \(\theta\), que podem ser chamadas de coordenadas esféricas. Esses ângulos farão o papel dos parâmetros \(u\) e \(v\) mencionados acima.

Parametrização de uma esfera.

Fig. 19.2 Parametrização de uma esfera.

Considere um vetor da origem até o ponto desejado na esfera. Seja \(\varphi\) o ângulo em radianos entre este vetor e o eixo z (pólo norte). Então \(\varphi\) está relacionado, mas não é igual à latitude. Temos \(0 \le \varphi \le \pi\). Seja \(\theta\) o ângulo no sentido anti-horário da projeção desse vetor no plano \(xy\). Assim \(0 \le \theta < 2\pi\), como ilustrado na Figura Fig. 19.2.

Nossa próxima tarefa é determinar como converter um ponto \((x, y, z)\) na esfera em um par \((\theta, \varphi)\). Será um pouco mais fácil abordar este problema no sentido inverso, determinando o valor \((x, y, z)\) que corresponde a um determinado par de parâmetros \((\theta, \varphi)\).

A coordenada \(z\) é apenas \(\mbox{cos} \varphi\), e claramente isso varia de 1 a -1 à medida que \(\varphi\) aumenta de 0 a \(\pi\). Para determinar o valor de \(\theta\), consideremos a projeção desse vetor no plano \(xy\). Como a componente vertical tem comprimento \(\mbox{cos} \varphi\), e o comprimento total é 1 (já que é uma esfera de raio unitário), pelo teorema de Pitágoras o comprimento horizontal é \(l = 1 - \mbox{cos} 2 \varphi = \mbox{sin} \varphi\). Os comprimentos das projeções nos eixos \(x\) e \(y\) são \(x = l \mbox{cos} \theta\) e \(y = l \mbox{sin} \theta\). Juntando tudo isso, segue que as coordenadas \((x, y, z)\) correspondentes às coordenadas esféricas (\(\theta\), \(\varphi\)) são

\(z(\varphi,\theta) = \mbox{cos} \varphi\),

\(x(\varphi,\theta) = \mbox{sin} \varphi \cdot \mbox{cos}\theta\),

\(y(\varphi,\theta) = \mbox{sin} \varphi \cdot \mbox{sin}\theta\).

Mas o que precisamos saber é como mapear \((x, y, z)\) para (\(\theta\), \(\varphi\)). Para fazer isso, observe primeiro que \(\varphi = \mbox{arccos} z\). A princípio, parece que é mais díficil obter \(\theta\), mas há uma maneira fácil de calcular seu valor. Observe que \(y/x = \mbox{sin} \theta/ \mbox{cos} \theta = \mbox{tan} \theta\).

Portanto, \(\theta = \mbox{arctan}(y/x)\). Resumindo:

\(\varphi = \mbox{arccos} z \;\;\;\) e \(\;\;\;\theta = \mbox{arctan}(y/x)\).

Lembre-se que isso pode ser calculado com precisão usando atan2(y, x).

O passo final é mapear o par de parâmetros (\(\theta\), \(\varphi\)) para um ponto no espaço \((s, t)\). Para obter a coordenada \(s\), apenas dimensionamos \(\theta\) do intervalo \([0, 2\pi]\) a \([0, 1]\). Assim, \(s = \theta/(2 \pi)\).

O valor de \(t\) é um pouco mais complicado de obter. O valor de \(\varphi\) aumenta de 0 no pólo norte a \(\pi\) no pólo sul, mas o valor de \(t\) diminui de 1 no pólo norte para 0 no pólo sul. Depois de brincar um pouco com os fatores de escala, descobrimos que \(t = 1 - (\varphi/\pi)\). Assim, como \(\varphi\) vai de 0 a \(\pi\), essa função vai de 1 a 0, que é exatamente o que queremos. Em resumo, a função de desembrulho desejada é \(W^{-1}(x, y, z) = (s, t)\), onde:

\(s = \theta / (2 \pi)\;\;\;\;\) onde \(\;\;\; \theta = \mbox{arctan}(y/x)\), e

\(t = 1 - \varphi / \pi \;\;\;\;\) onde \(\;\;\; \varphi = \mbox{arccos}(z)\).

Observe que nos pólos norte e sul há uma singularidade no sentido de que não podemos derivar um valor único para \(\theta\). Esse fenômeno é bem conhecido dos cartógrafos. (Qual é a longitude do pólo norte ou sul?)

Para resumir, a função de desembrulho \(W^{-1}(x, y, z)\) mapeia um ponto na superfície para um ponto \((s, t)\) no espaço de textura. Isso geralmente é feito por meio de um processo com duas etapas:

  • primeiro determinamos os valores dos parâmetros \((u, v)\) associados a esse ponto e
  • depois mapeamos \((u, v)\) para as coordenadas do espaço de textura \((s, t)\).

Para este exemplo, vamos simplesmente usar a função identidade, ou seja, \(W^{-1}(u,v) = (u,v)\). Em geral, podemos querer esticar, transladar ou girar a textura para obter o posicionamento desejado.

19.4. O processo de mapeamento de textura

Suponha que a função de desembrulho \(W^{-1}\) e uma parametrização da superfície sejam fornecidas. Vamos descrever de forma genérica do processo de mapeamento de textura, como ilustrado na Figura Fig. 19.3. Na próxima aula veremos um exemplo concreto de implementação no WebGL.

19.4.1. Projeção dos pixels para a superfície

Vamos começar considerando um pixel que desejamos desenhar. Esse pixel corresponde a uma região sobre a superfície do objeto, que se projeta sobre o pixel. Podemos determinar esse fragmento por 4 pontos sobre a superfície que correspondem aos 4 cantos do pixel. Por simplicidade, vamos supor que uma única superfície cubra todo o fragmento. Caso contrário, devemos calcular a média das contribuições dos vários fragmentos de superfícies para este pixel.

19.4.2. Parametrização

Calculamos os parâmetros \((u, v)\) para cada um dos quatro cantos do fragmento sobre a superfície. Isso geralmente requer uma função para converter as coordenadas \((x,y,z)\) de um ponto de superfície para sua parametrização \((u,v)\).

19.4.3. Desembrulhar e calcular a média

Em seguida, aplicamos a função de desembrulho para determinar a região correspondente do espaço de textura. Observe que essa região geralmente pode ter lados curvos, se a função de desembrulho for não linear. Calculamos a intensidade média dos texels nesta região do espaço de textura por um processo chamado filtragem (filtering). Por exemplo, isso pode envolver o cálculo da soma ponderada dos valores dos texels que se sobrepõem a essa região e, em seguida, atribuir a cor média correspondente ao pixel.

Processo de mapeamento de textura.

Fig. 19.3 Processo de mapeamento de textura.

19.5. Onde estamos e para onde vamos?

Nessa aula cobrimos os elementos matemáticos básicos do mapeamento de textura. Na próxima aula, consideraremos como fazer isso usando o WebGL.

19.6. Para saber mais