No artigo passado, vimos como carregar imagens da SDL e analisar suas propriedades. Neste, veremos como mapear essas imagens na superfície dos nossos objetos, transformando-as em texturas.
Introdução
O mapeamento de texturas permite que você associe uma ou mais imagens a polígonos, o que gera um efeito muito realista. Por exemplo, você poderia associar o mapa mundi a superfície de uma esfera e desenhar o planeta terra. Hoje, essa técnica, chamada mapeamento de texturas (texture mapping), é usada em absolutamente todos os jogos.
As texturas podem ser de três tipos:
- Unidimensionais: Uma linha contendo um único pixel de altura. Útil para fazer degradée em superfícies, como o céu;
- Bidimensionais: Uma imagem tradicional, com largura e altura. De longe, o tipo mais usado. E será o tipo que estudaremos.
- Tridimensionais: Consiste numa imagem tridimensional de modo que, dado a coordenada x, y e z de uma figura, a mesma coordenada x, y e z na imagem corresponderá a cor naquele ponto. Geralmente não é usada pois consome muita memória e porque o interior dos objetos dificilmente é visto.
Uma vez mapeadas a um polígono, as texturas estão sujeitas a todas as transformações que ocorrem naquele polígono. Ou seja, elas irão rotacionar, mover ou escalar juntamente do polígono. Assim, podemos encarar a textura como se fosse efetivamente a “pele” de nossa geometria.
Mapeando texturas
Como vimos anteriormente, nem todos os polígonos do OpenGL são desenhados usando a primitiva GL_QUADS. Por outro lado, todas as texturas bidimensionais são criadas em quadrados perfeitos, como qualquer imagem tradicional. Então, como é possível mapear esse quadrado para um polígono arbitrário?
Além das coordenadas espaciais de um polígono, também podemos fornecer ao OpenGL coordenadas de textura, para cada vértice. Essas coordenadas tem a notação (s,t) embora existem autores que as chamem de (u,v). Cada vértice da figura deve conter sua própria coordenada de textura e é o próprio OpenGL que se encarrega de alongar ou achatar a imagem para que ela caiba dentro geometria, da forma como foi mapeada. A figura abaixo mostra como é feito um mapeamento de uma textura para um triângulo:

Note que pouco importa a coordenadas reais do triângulo, desde que ele mantenha a mesma proporção. Também é util saber que você poderia aplicar as coordenadas de uma única textura não para um, mas para vários polígonos e recobrir assim uma forma complexa, como uma esfera formada por vários triângulos ou mesmo o modelo de um rosto.
Criando as texturas
Cada textura no OpenGL é um objeto distinto. O OpenGL associa a cada textura um identificador, único. Usamos o comando void glGenTextures(GLsizei n, GLuint *textures); para criar novos objetos de texturas. Após criados, podemos associar esses objetos a imagens. A função possui dois parâmetros. O primeiro indica quantos objetos de textura você vai criar, o segundo, permite que o OpenGL devolva os identificadores desses objetos criados através de um array de inteiros que você deve fornecer.
Em seguida, precisamos informar ao OpenGL com que objeto de textura estamos trabalhando. Fazemos isso usando o comando void glBindTexture(GLenum target, GLuint texture);. Esse comando aceita dois parâmetros: o primeiro indica qual é o tipo da textura que iremos trabalhar e pode conter os valores GL_TEXTURE1D, GL_TEXTURE2D ou GL_TEXTURE3D. O segundo, indica o ID do objeto de textura que será manipulado.
Em seguida, basta carregar a textura dentro do objeto. No artigo passado, vimos que podemos usar a SDL para carregar imagens na memória. Após essa carga, é necessário dizer ao OpenGL que aquela imagem será uma textura. Uma vez carregada a textura se torna parte do que o OpenGL chama “texture state” (estado de textura). As três funções abaixo, transformam um array de bytes contendo os dados da imagem em texturas:
Use a função apropriada para texturas 1D, 2D e 3D. É importante ressaltar que o OpenGL copia os dados da imagem quando uma das funções é chamada. Por isso, é possível descartar a SDL image tão logo a textura tenha sido criada.
O parâmetro target indica em que área a textura deve ser criada. Ele pode conter os valores GL_TEXTURE_1D, GL_TEXTURE2D, GL_TEXTURE3D.
O parâmetro level indica qual é o nível do mipmap sendo carregado. Cobriremos mipmaping num próximo tópico. Por hora, usaremos a textura sem esse recurso, simplesmente fornecendo 0 nesse parâmetro.
O próximo parâmetro, chamado internalFormat diz ao OpenGL qual é o formato de destino em que o OpenGL irá armazenar a imagem carregada. Ele diz ao OpenGL quantos bits ele deve usar para armazenar cada componente de cor da textura, quantos componentes de cor existirão e se a textura é ou não comprimida.
Você pode obter uma lista completa desses parâmetros clicando nos links acima. Usaremos dois dos valores comuns: GL_RGB e GL_RGBA. Para descobrir qual dos dois usar, verificaremos se a imagem carregada tem 3 (RGB) ou 4 (RGBA) bytes por pixel usando a SDL, como visto no artigo anterior.
Os parâmetros width, height e depth (onde apropriados) dizem as dimensões da imagem. Também podemos ler os dois primeiros da SDL. É muito importante saber que até o OpenGL 2.0 esses valores tinham, obrigatoriamente, que ser potências de 2 (2,4,8,16,32,64,128,256, …). Não era necessário que a textura tivesse os dois lados iguais. Texturas fora dessa regra podem ser simplesmente ignoradas em implementações antigas do OpenGL. Muitas das implementações novas, já conformes com a 2.0, também tem perdas significativas de performance quando esse requisito não é atingido. Então, via de regra, sempre use texturas com tamanhos potências de 2.
O parâmetro border diz ao OpenGL que uma borda, deve ser adicionada ao redor da imagem. Geralmente, fornecemos 0 nesse valor. Bordas são geralmente usadas em filtros de texturas.
Os parâmetros format e type são parecidos com o internalFormat, mas dizem ao OpenGL em que formato a imagem de origem, que foi carregada na memória pela SDL, tem. Existe uma extensa lista de parâmetros para cada uma delas e nossa classe inteligente poderá defini-los dinamicamente usando o que vimos no artigo anterior. Por hora, no exemplo, apenas diferenciaremos entre RGB e ARGB.
Finalmente, no parâmetro data devemos fornecer um array de bytes com a imagem em si.
O OpenGL ainda possui comandos para obtenção de texturas a partir do Color Buffer (glCopyTexImage2D) e substiuição de texturas na memória (glTexSubImage3D e glCopyTexSubImage2D) .
Além de tudo isso, precisamos dizer ao OpenGL como filtrar a textura. O filtro usado quando a imagem é ampliada ou reduzida. Esse será um dos tópicos dos próximos artigos.
Confuso? Que tal então um exemplo de código? Este exemplo, mostra como carregar uma textura a partir de uma superfície SDL.
void createTexture(SDL_Surface* image)
{
//Criamos um objeto de textura e obtemos seu id
int id = 0; glGenTextures(1, &id);
//Dizemos ao OpenGL que iremos trabalhar com o objeto.
glBindTexture(GL_TEXTURE_2D, id);
//Filtro. Veremos isso nos próximos artigos.
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//Descobrimos o formato a partir da imagem
GLint format =
image->format->BytesPerPixel == 3 ? GL_RGB : GL_RGBA;
//Carregamos a imagem do disco
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, image->pixels);
//Como o OpenGL copiou a imagem, apagamos a SDL_Image.
SDL_FreeSurface(image);
}
Usando a textura
Uma vez carregada, apenas três passos são necessários para utilizar uma textura:
- Dar o glEnable em GL_TEXTURE1D, GL_TEXTURE2D ou GL_TEXTURE3D. Se você usar só texturas 2D (o que é muito comum) pode dar o glEnable logo no início do programa, como fizemos com a iluminação.
- Dizer ao OpenGL que textura será aplicada usando o comando glBindTexture(GLenum target, GLuint texture); antes de começar a desenhar os vértices da nossa figura;
- Associar cada vértice a uma coordenada de textura através do comando glTexCoord2f, como descrito no início do artigo.
Por exemplo, para o caso daquele triângulo, poderíamos fazer assim:
//Liga a propriedade de texturização. glEnable(GL_TEXTURE_2D) //Diz que usaremos a textura de mármore //MARMORE_MARROM seria uma constante com o ID da textura. glBindTexture(GL_TEXTURE_2D, MARMORE_MARROM); glBegin(GL_TRIANGLES); glTexCoord2f(1.0, 1.0f); glVertex2f(50.0f, 50.0f); glTexCoord2f(0.5, 0.5f); glVertex2f(25.0f, 25.0f); glTexCoord2f(0.0, 1.0f); glVertex2f(0.0f, 50.0f); glEnd();
Concluindo
Neste artigo, vimos de maneira básica como usar texturas. Você ainda pode baixar o exemplo sobre texturas que eu preparei para que vocês vejam o funcionamento de tudo na prática. Nos próximos artigos veremos o que são mipmaps e como configurar os filtros de texturas. Assim, você finalmente entenderá o que é são os filtros linear, bilinear, trilinear a anisotrópico, que os muitos jogos insistem em colocar nas configurações. Até lá!
Parabens, pra min foi perfeito o tutorial, tinha que fazer uma carregador de arquivo .obj e isso me ajudou muito na parte de texturas…
vlw! abraço
Oi
Consegui carregar a textura numa boa, so estou com um problema, ele carregou a imagem de cabeça pra baixo e tb tenho a impressao q ele carregou a textura em 4 quadrantes, ou seja, to com a imagem repetida 4 vezes na tela, 1 em cada quadrante, pelo menos foi o q pude entender qdo mandei o opengl colocar a textura num retangulo bem no meio ta tela, simplesmente apareceu o retangulo divido em 4 partes cada um com uma parte diferente da textura. Vc teria alguma ideia do porquê disso estar ocorrendo?
vlw
Oi. Havia mesmo um erro nas coordenadas da figura ali em cima, elas estavam mesmo de cabeça para baixo. Obrigado por alertar. Já corrigi o post.
Quanto a imagem ser carregada em quadrantes. Nunca vi isso ocorrer. Na verdade, não consegui nem entender o que você quis dizer com isso. Como está o seu código que faz o mapeamento da textura?
Oi
Consertei o erro dos quadrantes, o problema tava nas coordenadas do glTexCoord2f, mas ainda nao entendi o porquê dele estar colocando a imagem de cabeça pra baixo. Coloquei as seguintes coordenadas pra desenhar um retangulo bem no meio da tela:
Estranho mesmo, aparentemente está correto. Você alterou alguma coisa na matriz de projeção?
Só um comentário. Já que você está desenhando um retângulo, não era melhor usar GL_QUADS no lugar do GL_POLYGON?
Já alterei pra GL_QUADS. E em relação a matriz de projeção, apenas faço alteração nela qdo realizo o zoom ou o pan, será q é isso q está interferindo? Aqui vai o codigo do zoom qdo ele é chamado:
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // o uso deste comando garante que o zoom
// seja feito de forma absoluta e não relativa
gluOrtho2D (-win+nPanX, win+nPanX, -win+nPanY, win+nPanY);
glMatrixMode(GL_MODELVIEW);
glFlush();
repaint();
Este tutorial foi excelente, um dos mais perfeitos que já ví.
Isso é bom para mim e muitos outros, pois se torna uma ferramenta fundamental de difusão do conhecimento !
Muito obrigado, e parabens!
glGenTextures(GLsizei n, GLuint *textures);
esse parâmetro n indica o total de texturas usadas pelo programa inteiro?
Por exemplo, se eu fizer glGenTextures(12, texture_id) o máximo de texturas que vou poder usar são 12??
Obrigado
Não, ele indica quantos objetos de texturas você vai criar. É usado para criar vários objetos de uma única vez.
Em seguida, vc deve passar o array, que é preenchido com cada objeto de textura criado.
Se usar esse comando uma vez passando 12 e outra passando 18, vai ter criado 30 texturas.