Introdução ao OpenGL usando SDL – parte 2

Assumindo que você já tenha passado pela primeira parte desse tutorial, o artigo irá expandir o tema iniciado no primeiro, de modo a manter tudo o que foi aprendido na memória. Nesse ponto você deve ter um conhecimento usável sobre rotação, translação e renderização de formas 3D simples. Assim, nesse artigo iremos cobrir os tópicos adicionais: carregamento de texturas, mapeamento de texturas e fumaça.

Para começo de conversa, iremos escrever uma função que carrega um textura usando SDL_image (assim não esqueça de incluir essa biblioteca na seu cabeçalho). A vantagem de usar SDL_image é que podemos carregar uma faixa grande de imagens como textura usando apenas uma função.

// Load texture
GLuint loadTexture(char filename[]){
 GLuint texture;
 SDL_Surface* imgFile = IMG_Load(filename);
 glGenTextures(1,&texture);
 glBindTexture(GL_TEXTURE_2D,texture);
 gluBuild2DMipmaps(GL_TEXTURE_2D, 3, imgFile->w, imgFile->h, GL_RGB, GL_UNSIGNED_BYTE, imgFile->pixels);
 return texture;
}

Como isso funciona? A função pega um caminho de arquivo, carrega essa imagem como uma textura, e retorna um manipulador para essa textura. Para ser mais especifico, o comando “gluBuild2Mipmaps” é a função de construção de texturas de fato, e usa o array de pixels brutos da imagem que carregamos para criar uma textura compatível com OpenGl. As texturas no OpenGl são armazenadas em manipuladores GLuint, para usar essa função para carregar uma textura você poderia fazer isso:

GLuint myTexture;
myTexture = loadTexture("myImage.png");

Agora, para usar um textura, nós precisamos ativar texturização. Isso é bem simples, o comando “glEnable(GL_TEXTURE_2D) faz isso por você. E porque você provavelmente irá querer saber como manipular múltiplas texturas, você pode configurar a renderização de texturas para qualquer uma usando glBindTexture(GL_TEXTURE_2D,TextureHandle) onde TextureHandle é um manipulador GLuint para uma textura previamente carregada. Todos os comandos de renderização terão a textura associada a TextureHandle ligadas a eles. Agora, antes de começar a mexer  com renderização usando texturas, eu gostaria de mostrar a você um método mais avançado de fazer isso. Ao contrário do método acima quando carrega um mipmap, essa função permite que você especifique se a imagem será convertida em mipmap quando é carregada. Vamos passar para a função…

// Load texture
GLuint loadTexture(const char filename [], bool mipmap){
 glPushAttrib (GL_ALL_ATTRIB_BITS);
 GLuint texture;
 SDL_Surface* imgFile = IMG_Load(filename);
 glGenTextures(1,&texture);
 glBindTexture(GL_TEXTURE_2D,texture);
 if(mipmap){
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
 gluBuild2DMipmaps(GL_TEXTURE_2D, 3, imgFile->w, imgFile->h, GL_RGB, GL_UNSIGNED_BYTE, imgFile->pixels);
 } else {
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexImage2D (GL_TEXTURE_2D, 0, 3, imgFile->w, imgFile->h, 0, GL_RGB, GL_UNSIGNED_BYTE, imgFile->pixels);
 }
 glPopAttrib ();
 return texture;
}

Essa função usa uma imagem e uma valor booleano para saber se deve fazer a conversão em mipmap. Se deve fazer, empilha GL_ATTRIB_BITS na pilha (porque estaremos alterando coisas que gostaríamos de desfazer) e procedemos com o carregamento da textura de forma similar ao primeiro método exceto que este usa métodos de filtro de textura mais específicos, e quando tudo for feito, nós retiramos GL_ATTRIB_BITS da pilha e retornamos o manipulador GLuint para a textura. O uso dessa função deve se parecer com isso:

GLuint myTexture;
GLuint myMipMapTexture;
myTexture = loadTexture("myImage.png", false);
myMipMapTexture = loadTexture("myImage.png", true);

Eu sei que você deve estar ansioso para começar a usar essas texturas, mas primeiro precisamos estabelecer algumas regras sobre tamanhos de texturas. As texturas no OpenGl (e na maioria dos renderizadores 3D) precisam ter uma resolução em uma potência de 2. Isso significa que a largura e altura precisa ser um número que ocorra na forma 2^x. Alguns desses valores, por exemplo, podem ser: 2, 4, 8, 16, 32 64, 128, 256, 512, 1024. Dessa forma, é correto que sua textura tenha o  tamanho de 512×512 mas não é se ela tiver o tamanho 500×500.
Certo, agora que cobrimos a maior parte do básico, iremos adiante e criaremos uma textura usando uma das resoluções que listamos. Nós criaremos um programa que ativa GL_TEXTURE_2D, carrega a textura usando nossa função loadTexture, conecta a textura ativa ao manipulador em uso para carregar a textura e depois inserimos essa textura no loop de renderização principal (como no de desenho de formas criado no artigo anterior).

 glBegin(GL_QUADS);
 glTexCoord2f(0,0); glVertex3f(-1, 1, 0);
 glTexCoord2f(1,0); glVertex3f( 1, 1, 0);
 glTexCoord2f(1,1); glVertex3f( 1,-1, 0);
 glTexCoord2f(0,1); glVertex3f(-1,-1, 0);
 glEnd();

O comando glTextCoord2f permite que configuremos as coordenadas X e Y (ou U e V) de nosso mapeamento de textura. O canto inferior esquerdo da textura é o (0,0) e o superior direito é (1,1). O plano da textura também é infinito, então o uso de algo como isso tornará mais fácil para que possamos usar imagens lado a lado.

 glBegin(GL_QUADS);
 glTexCoord2f(0,0); glVertex3f(-1, 1, 0);
 glTexCoord2f(2,0); glVertex3f( 1, 1, 0);
 glTexCoord2f(2,2); glVertex3f( 1,-1, 0);
 glTexCoord2f(0,2); glVertex3f(-1,-1, 0);
 glEnd();

Agora você verá como os 2 nos permitem passar do canto (1,1) em uma textura repetida. O plano inteiro da textura é dessa forma, você pode ladrilhar a textura em um objeto  quantas vezes você quiser passando dos cantos (0,0) e (1,1) da textura. Você pode usar 1/4 da textura passando a meio caminho de (1,1) dessa forma:

 glBegin(GL_QUADS);
 glTexCoord2f(00,00); glVertex3f(-1, 1, 0);
 glTexCoord2f(.5,00); glVertex3f( 1, 1, 0);
 glTexCoord2f(.5,.5); glVertex3f( 1,-1, 0);
 glTexCoord2f(00,.5); glVertex3f(-1,-1, 0);
 glEnd();

E você pode deslocar a textura inteira mantendo a diferença (0,0)-(1,1). Simplesmente crie um deslocamento que você adicionará ao x e ao y respectivamente.

 glBegin(GL_QUADS);
 glTexCoord2f(0+xOff,0+yOff); glVertex3f(-1, 1, 0);
 glTexCoord2f(1+xOff,0+yOff); glVertex3f( 1, 1, 0);
 glTexCoord2f(1+xOff,1+yOff); glVertex3f( 1,-1, 0);
 glTexCoord2f(0+xOff,1+yOff); glVertex3f(-1,-1, 0);
 glEnd();

No pedaço de código acima, pelo continuo incremento dos valores das variáveis do deslocamento (como a variável rotacional no artigo anterior) você pode criar uma textura deslizante. Mas, acho que cobrimos o suficiente sobre texturas para você poder usa-las, então que tal falarmos sobre fumaça?
Fumaça no OpenGl é incrivelmente simples:

 GLfloat fColor[4]= {0.0f, 1.0f, 0.0f, 1.0f};
 glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
 glFogi(GL_FOG_MODE, GL_EXP2);
 glFogfv(GL_FOG_COLOR, fColor);
 glFogf(GL_FOG_DENSITY, 0.15f);
 glFogf(GL_FOG_START, 1.0f);
 glFogf(GL_FOG_END, 5.0f);
 glEnable(GL_FOG);

Em primeiro lugar criamos um array de GLfloat para armazenar a cor da fumaça (verde no exemplo). Depois configuramos a cor clara do OpenGl para a mesma cor da nossa fumaça (sem isso a cor de fundo e a da fumaça seriam duas cores diferentes e as coisas pareciam muito estranhas). Depois configuramos o modo da fumaça… Isso realmente depende de como você quer que a fumaça parece, e existem três opções: GL_EXP, GL_EXP2 e GL_LINEAR.
Algumas vezes você tem que testar esses modos para decidir quais lhe parecem melhor. Agora que passamos o array GLfloat para o atributo GL_FOG_COLOR (que configura a cor da fumaça). Depois, você configura a densidade; Quanto mais alta a densidade, menor a visibilidade. Quanto menor a densidade, mais fraca sua fumaça será. Em seguida use GL_START_FOG e GL_FOG_END para definir a distância da fumaça. Por último, nós ativamos a fumaça. Abaixo segue todo o código junto, com o uso do deslocamento da textura:

#include <iostream>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
using namespace std;
// Width & Height of window
#define width 640
#define height 480
// Variable storing degree of rotation, texture offset, and texture handle
float Angle = 0.0;
float tOff = 0.0;
GLuint myTexture;
// Load texture
GLuint loadTexture(const char filename [], bool mipmap){
 glPushAttrib (GL_ALL_ATTRIB_BITS);
 GLuint texture;
 SDL_Surface* imgFile = IMG_Load(filename);
 glGenTextures(1,&texture);
 glBindTexture(GL_TEXTURE_2D,texture);
 if(mipmap){
 // Load mipmap texture
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
 gluBuild2DMipmaps(GL_TEXTURE_2D, 3, imgFile->w, imgFile->h, GL_RGB, GL_UNSIGNED_BYTE, imgFile->pixels);
 } else {
 // Load normal texture
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexImage2D (GL_TEXTURE_2D, 0, 3, imgFile->w, imgFile->h, 0, GL_RGB, GL_UNSIGNED_BYTE, imgFile->pixels);
 }
 glPopAttrib ();
 return texture;
}
// Kill program
void endProgram(int code) {SDL_Quit(); exit(code);}
// Handle SDL keypresses
void handleKeys(SDL_keysym* keysym, bool state) {
 switch(keysym->sym) {
 case SDLK_ESCAPE: endProgram(0); break;
 }
}
// Process SDL events
void processEvents() {
 SDL_Event event;
 while(SDL_PollEvent(&event)) {
 switch(event.type) {
 case SDL_KEYDOWN: handleKeys(&event.key.keysym, true ); break;
 case SDL_KEYUP : handleKeys(&event.key.keysym, false); break;
 case SDL_QUIT : endProgram(0); break;
 }
 }
}
void mainLoop() {
 while(true) {
 processEvents();
 glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // Clear color and depth buffer
 glLoadIdentity(); // Reset orientation
 // Distance away from screen
 glTranslatef(0, 0, -5);
 // Set rotation
 glRotatef(Angle, 1, 0, 0);
 glRotatef(Angle, 1, 1, 0);
 glBindTexture(GL_TEXTURE_2D, myTexture);
 glBegin(GL_QUADS);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f(-1, 1,-1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f( 1, 1,-1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f( 1,-1,-1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f(-1,-1,-1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f( 1,-1, 1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f( 1, 1, 1);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f( 1, 1,-1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f( 1,-1,-1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f(-1, 1, 1);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f( 1, 1, 1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f( 1,-1, 1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f(-1,-1, 1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f(-1,-1, 1);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f(-1, 1, 1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f(-1, 1,-1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f(-1,-1,-1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f( 1, 1,-1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f( 1, 1, 1);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f(-1, 1, 1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f(-1, 1,-1);
 glTexCoord2f(1+tOff,0+tOff); glVertex3f( 1,-1,-1);
 glTexCoord2f(1+tOff,1+tOff); glVertex3f( 1,-1, 1);
 glTexCoord2f(0+tOff,1+tOff); glVertex3f(-1,-1, 1);
 glTexCoord2f(0+tOff,0+tOff); glVertex3f(-1,-1,-1);
 glEnd();
 SDL_GL_SwapBuffers(); // Update screen
 // Increase degree of rotation to spin QUAD
 Angle+=.25;
 tOff+=.001;
 }
}
// Setup OpenGL
void setupOpengl(char* textureSrc) {
 // Setup perspective
 glViewport(0, 0, width, height);
 glMatrixMode(GL_PROJECTION);
 glEnable(GL_DEPTH_TEST);
 gluPerspective(45, (float)width/height, .1, 100);
 glMatrixMode(GL_MODELVIEW);
 // Setup texture
 glEnable(GL_TEXTURE_2D);
 myTexture = loadTexture(textureSrc, false);
 // Setup fog
 GLfloat fColor[4]= {0.0f, 0.3f, 0.4f, 1.0f};
 glClearColor(0.0f, 0.3f, 0.4f, 1.0f);
 glFogi(GL_FOG_MODE, GL_EXP2);
 glFogfv(GL_FOG_COLOR, fColor);
 glFogf(GL_FOG_DENSITY, 0.3f);
 glFogf(GL_FOG_START, 1.0f);
 glFogf(GL_FOG_END, 5.0f);
 glEnable(GL_FOG);
}
// Init everything
int main(int argc, char* argv[]) {
 if(argc==2){
 SDL_Init(SDL_INIT_VIDEO);
 SDL_SetVideoMode(width, height, 24, SDL_OPENGL | SDL_GL_DOUBLEBUFFER);
 setupOpengl(argv[1]);
 mainLoop();
 } else {cout << "Must specify texture!" << endl;}
 return 0;
}

Clique aqui para baixar o código fonte -> opengl_2.tar
Para compilar e executar:

g++ -o opengl2 ogl_part2.cpp -lSDL -lSDL_image -lGLU -lGL
./opengl2 texture.png

Esse exemplo em particular precisa de um argumento que especifica a textura a ser carregada.