Computação Gráfica II

Trabalho Prático

Exercícios



5. Texturização

Neste exercício aprenderemos como mapear texturas em objetos usando OpenGL.

Uma textura é uma imagem que eé mapeada sobre polígonos. Associando vértices (no espaço da tela) a pontos de controle chamados coordenadas de textura u e v (no espaço da textura), se obtém uma interpolação na tela. 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 da tela.

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: 2, 4, 8, 16, 32, 64, 128, 256, 512, ...

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 testura 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).

Na aula anterior já havíamos incluído algumas mudanças à classe Mesh para que ela possa lidar com texturas.

Agora, explicaremos estas mudanças.

1. Na declaração da classe Mesh (mesh.h), adicionamos as seguintes linhas:

// mesh.h

struct UV
{
float u,v;
};

class Mesh
{
// (...)


UV *uvs; //adding texture coordinates field

// (...)

};

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

Mesh::Mesh()
{

uvs=0;

}

Mesh::~Mesh()
{
// (...)
delete [] uvs;

}

void Mesh::initVerts(int p_nbrVerts)
{
// (...)
uvs =new UV[nbrVerts];

}

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

void textureMagic()
{
glEnable(GL_TEXTURE_2D);
unsigned int temp;
glGenTextures(1,&temp); // Create The Texture
glBindTexture(GL_TEXTURE_2D,temp);
DCImage img; //uses class DCImage
img.load("texture2.raw"); // give here the image file containing the texture
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 já vimos quando carregamos arquivos 3ds.
Para usar esta estratégia, a classe precisa ser incluída no seu project.

4. A função drawMesh() foi modificada para que incluísse 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():

void init()
{

// (...)

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;             // size of the image in bytes.
    unsigned long i;                // standard counter.
    unsigned short int planes;      // number of planes in image (must be 1)
    unsigned short int bpp;         // number of bits per pixel (must be 24)
    char temp;                      // temporary color storage for bgr-rgb conversion.
    char k[4];

    // make sure the file is there.
    if ((file = fopen(filename, "rb"))==NULL)
    {
    printf("File Not Found : %s\n",filename);
    return 0;
    }
    
    // seek through the bmp header, up to the width/height:
    fseek(file, 18, SEEK_CUR);

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

    //AND// reverse(k,4);
    image->sizeX=*(int*)k;
    
    printf("Width of %s: %lu\n", filename, image->sizeX);
    
    // read the height
    if ((i = fread(k, 4, 1, file)) != 1) {
    printf("Error reading height from %s.\n", filename);
    return 0;
    }

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

    printf("Height of %s: %lu\n", filename, image->sizeY);
    
    // calculate the size (assuming 24 bits or 3 bytes per pixel).
    size = image->sizeX * image->sizeY * 3;

    // read the planes
    if ((fread(k, 2, 1, file)) != 1) {
    printf("Error reading planes from %s.\n", filename);
    return 0;
    }
   
    reverse(k,2);
    planes=*(int*)k;
    planes=1; // :)

    if (planes != 1) {
    printf("Planes from %s is not 1: %u\n", filename, planes);
    return 0;
    }

    // read the bpp
    if ((i = fread(&bpp, 2, 1, file)) != 1) {
    printf("Error reading bpp from %s.\n", filename);
    return 0;
    }
    bpp=24;
    if (bpp != 24) {
    printf("Bpp from %s is not 24: %u\n", filename, bpp);
    return 0;
    }
    
    // seek past the rest of the bitmap header.
    fseek(file, 24, SEEK_CUR);

    // read the data.
    image->data = (char *) malloc(size);
    if (image->data == NULL) {
    printf("Error allocating memory for color-corrected image data");
    return 0;    
    }

    if ((i = fread(image->data, size, 1, file)) != 1) {
    printf("Error reading image data from %s.\n", filename);
    return 0;
    }

    for (i=0;i<size;i+=3) { // reverse all of the colors. (bgr -> rgb)
    temp = image->data[i];
    image->data[i] = image->data[i+2];
    image->data[i+2] = temp;
    }
    
    // we're done.
    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); // Create The Texture
glBindTexture(GL_TEXTURE_2D,temp);
Image img; //uses struct Image
loadBMP( "textura.bmp", &img );
// give here the image file containing the texture
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.