Aula 3. Texturização
Neste exercício aprenderemos como mapear texturas em objetos usando OpenGL.
Uma textura é uma imagem que é mapeada sobre polígonos. Associando vértices (no espaço do objeto) a pontos de controle chamados coordenadas de textura u e v (no espaço da textura), se obtém uma interpolação no espaço do objeto. Texturas são essencialmente usadas para adicionar características a superfícies que seriam muito custosas para produzir usando polígonos.
Fig. 1. Transformações do espaço de textura para o espaço do objeto.
Existe uma consideração importante a se fazer ao usar texturas em OpenGL: por razões de performance, texturas devem ser imagens quadradas com o tamanho do lado sendo uma potência de 2. Portanto, bons tamanhos de textura são: 2x2, 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256, 512x512, ...
a. Um tutorial
O tutorial de textura (mostrado abaixo) demonstra como funciona a texturização em OpenGL. Especificamente, as coordenadas de textura para um polígono são fornecidas da mesma maneira que as próprias coordenadas cartesianas dos vértices dos polígonos. Os parâmetros de textura e atributos do ambiente são configuráveis. Um painel de comando separado permite a manipulação da matriz da textura. (fonte: http://www.xmission.com/~nate/tutors.html)
Baixem o tutorial e vejam como funcionam os diferentes parâmetros e atributos.
Podem usar a página de recursos neste site para entender os diferentes parâmetros:
glTexParameter, glTexEnv and glTexCoord
Fig. 2. Tutorial para texturização (by Nate Robins)
b. Usando texturas sobre uma malha
Façamos algo mais prático. Vamos mapear uma textura sobre uma malha.
Para isso, precisamos uma malha, uma imagem de textura e coordenadas de textura. Conforme visto anteriormente, coordenadas de textura associam cada vértice v em 3D com uma coordenada uv sobre o plano da imagem de textura (2D).
No programa da aula anterior devemos incluir algumas mudanças à classe Mesh para que ela possa lidar com texturas.
Vejamos uma a uma:
1. Na declaração da classe Mesh (mesh.h), adicionar as seguintes linhas se já não estiverem:
// mesh.h
|
2. E na implementação da classe Mesh
(mesh.cpp) estas:
|
3. No programa principal, adicionar a função textureMagic() e modificar a drawMesh():
void textureMagic() // Usa
a classe DCImage img.load("texture2.raw"); // Passa o nome do arquivo de imagem
que contém a textura glTexImage2D(GL_TEXTURE_2D,0,3,img.width,img.width,0,GL_RGB,GL_UNSIGNED_BYTE,img.data); |
Esta função é usada para
carregar arquivos de imagens em formato de mapa de bits bruto (.raw) e
usa a classe DCImage, que pode ser baixada aqui.
Para usar esta estratégia, a classe precisa ser incluída
no seu projeto. Ela simplesmente lê dados de uma imagem de
textura a partir de um arquivo .raw. A imagem de textura deve ser
baixada também aqui.
4. A função drawMesh() deve ser modificada para que inclua as coordenadas de textura.
void drawMesh(Mesh* m) |
5. Finalmente, a malha pode ser carregada de uma malha 3ds na função init(), sem esquecer de habilitar o client state para aceitar coordenadas de textura:
|
Exercício 1:
- Use o seu programa para carregar o seguinte personagem do jogo Counter-Strike(TM).
![]() ![]() |
Fig. 3. Um personagem do Counter-Strike(TM) com sua textura.
Arquivos necessários:
- Malha: man.h
- Textura: man.raw
Exercício 2:
Uma possibilidade em relação à imagem que
será usada é a função abaixo, que carrega
arquivos bitmap
padrão (.bmp). Experimentem.
Depois, sintam-se livres para pesquisar na Internet e encontrar
funções similares para carregar arquivos de outros
formatos (.jpg, .gif, etc.).
int |
Atenção para as mudanças ao carregar arquivos .bmp na textureMagic():
typedef struct { // Cria a textura glBindTexture(GL_TEXTURE_2D,temp); // Usa a classe Image loadBMP( "textura.bmp",
&img ); //
Passa o nome do arquivo de imagem que contém a textura glTexImage2D(GL_TEXTURE_2D, 0, 3, img.sizeX, img.sizeY, 0,
GL_RGB, GL_UNSIGNED_BYTE, img.data);
|
Comentário importante:
Se vocês quiserem usar uma malha com indexação separada de vértices e uvs, só poderão usar a maneira glBegin()/glEnd() de desenhar triângulos. Por que isso?
Na chamada da drawElements se assume que cada vértice é acompanhado por uma uv, mas se existem mais uvs que vértices surge um problema. Nesse caso, os vértices teriam que ser duplicados em um vertex array estendido e a lista de triângulos teria que ser reindexada para se adequar ao novo array.
Assim, a maneira mais fácil é mesmo
usar chamadas glBegin/glEnd, especialmente se uma performance
máxima não é requerida.