Computação Gráfica II

Trabalho Prático

Exercícios



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

struct UV  // cria-se uma estrutura para guardar as coordenadas de textura
{
float u,v;
};

class Mesh
{
// (...)


UV *uvs; //adiciona-se um atributo coordenadas de textura

// (...)

};

2. E na implementação da classe Mesh (mesh.cpp) estas:

Mesh::Mesh()
{

uvs=0; // inicializa com um pointer nulo

}

Mesh::~Mesh()
{
// (...)
delete [] uvs; // libera memória das coordenadas de textura

}

void Mesh::initVerts(int p_nbrVerts)
{
// (...)
uvs =new UV[nbrVerts]; //aloca memória para o array de coordenadas de textura

}

3. No programa principal, adicionar a função textureMagic() e modificar a drawMesh():

void textureMagic()
{
glEnable(GL_TEXTURE_2D);
unsigned int temp;
glGenTextures(1,&temp); // Cria a textura
glBindTexture(GL_TEXTURE_2D,temp);
DCImage img;
// 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);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}

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)
{
glVertexPointer(3,GL_FLOAT,0,m->verts);
glTexCoordPointer(2,GL_FLOAT,0,m->uvs);
glDrawElements (GL_TRIANGLES,m->nbrTris*3,GL_UNSIGNED_INT,m->tris);
}

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:

void init()
{

// (...)

glEnableClientState(GL_TEXTURE_COORD_ARRAY);
fromFile=ObjLoader::ReadMesh("spaceship.3ds");
textureMagic();

// (...)

}


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
Model::loadBMP( const char *filename, Image *image ){

    FILE *file;
    unsigned long size;             // tamanho da imagem em bytes.
    unsigned long i;                // contador padrao.
    unsigned short int planes;      // numero de planos na imagem (deve ser 1)
    unsigned short int bpp;         // numero de bits por pixel (deve ser 24)
    char temp;                      // temporario para conversao bgr-rgb.
    char k[4];

    // verifica se o arquivo existe.
    if ((file = fopen(filename, "rb"))==NULL)
    {
    printf("File Not Found : %s\n",filename);
    return 0;
    }
    
    // procura no cabacalho do bmp até o width/height:
    fseek(file, 18, SEEK_CUR);

    // read the width
    if ((i = fread(k, 4, 1, file)) != 1) {
    printf("Erro lendo largura de %s.\n", filename);
    return 0;
    }

    //AND// reverse(k,4);
    image->sizeX=*(int*)k;
    
    printf("Largura de %s: %lu\n", filename, image->sizeX);
    
    // read the height
    if ((i = fread(k, 4, 1, file)) != 1) {
    printf("Erro lendo altura de %s.\n", filename);
    return 0;
    }

    //AND// reverse(k,4);
    image->sizeY=*(int*)k;

    printf("Altura de %s: %lu\n", filename, image->sizeY);
    
    // calculate the size (asumindo 24 bits ou 3 bytes por pixel).
    size = image->sizeX * image->sizeY * 3;

    // le os planos
    if ((fread(k, 2, 1, file)) != 1) {
    printf("Erro lendo planos de %s.\n", filename);
    return 0;
    }
   
    reverse(k,2);
    planes=*(int*)k;
    planes=1; // :)

    if (planes != 1) {
    printf("Planos de %s nao eh 1: %u\n", filename, planes);
    return 0;
    }

    // read the bpp
    if ((i = fread(&bpp, 2, 1, file)) != 1) {
    printf("Erro lendo bpp de %s.\n", filename);
    return 0;
    }
    bpp=24;
    if (bpp != 24) {
    printf("Bpp de %s nao eh 24: %u\n", filename, bpp);
    return 0;
    }
    
    // seek passa o resto do cabecalho do bitmap.
    fseek(file, 24, SEEK_CUR);

    // le os dados.
    image->data = (char *) malloc(size);
    if (image->data == NULL) {
    printf("Erro alocando memoria para os dados da imagem com a cor corrigida");
    return 0;    
    }

    if ((i = fread(image->data, size, 1, file)) != 1) {
    printf("Erro lendo os dados da imagem de %s.\n", filename);
    return 0;
    }

    for (i=0;i<size;i+=3) { // reverte todas as cores. (bgr -> rgb)
    temp = image->data[i];
    image->data[i] = image->data[i+2];
    image->data[i+2] = temp;
    }
    
    // feito!
    return 1;
}

Atenção para as mudanças ao carregar arquivos .bmp na textureMagic():

typedef struct {
  unsigned long sizeX;
  unsigned long sizeY;
  char *data;
} Image;

void textureMagic()
{
glEnable(GL_TEXTURE_2D);
unsigned int temp;
glGenTextures(1,&temp);
// Cria a textura
glBindTexture(GL_TEXTURE_2D,temp);
Image img;
// 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);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}


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.