Jonh Edson Ribeiro de Carvalho | IC-UFF
Computação Visual e Interfaces
 
 
Tutorial OpenGL

Capítulo 5. Projeções geométricas

O propósito desta lição é entender o funcionamento dos principais tipos de projeções geométricas: paralelas e de perspectiva. O objeto utilizado nesta lição será um cubo com um dos cantos cortado, como mostra a Figura 5-1.

O programa que apresenta este cubo é mostrado no Exemplo 4-1. As teclas y e Y servem para girar o cubo em torno do eixo y contra e a favor do sentido dos ponteiros do relógio, respectivamente; as teclas x e X, controlam o giro em torno do eixo x. As teclas o e p define que os tipos de projeções serão ortográficas ou de perspectiva, respectivamente. Para finalizar o programa, basta digitar ESC. As teclas e suas respectivas ações estão definidas na função keyboard().

Exemplo 5-1. programa projecoes.c

#include <GL/glut.h>
#include <stdlib.h>

void init(void);
void display(void);
void keyboard(unsigned char key, int x, int y);
void reshape (int w, int h);

#define AZUL     0.0, 0.0, 1.0
#define VERMELHO 1.0, 0.0, 0.0
#define AMARELO  1.0, 1.0, 0.0
#define VERDE    0.0, 1.0, 0.0
#define CYAN     1.0, 0.0, 1.0
#define LARANJA  0.8, 0.6, 0.1
#define ROSEO    0.7, 0.1, 0.6
#define CINZA    0.6, 0.6, 0.6

static GLfloat vertices[30]={
  0.0,  30.0, 30.0, /* 0 */
  20.0, 30.0, 30.0, /* 1 */
  30.0, 20.0, 30.0, /* 2 */
  30.0,  0.0, 30.0, /* 3 */
  0.0,   0.0, 30.0, /* 4 */
  0.0,  30.0,  0.0, /* 5 */
  30.0, 30.0,  0.0, /* 6 */
  30.0,  0.0,  0.0, /* 7 */
  0.0,   0.0,  0.0, /* 8 */
  30.0, 30.0, 20.0  /* 9 */
}; 

static GLubyte frenteIndices[]    = {0,4,3,2,1};
static GLubyte trasIndices[]      = {5,6,7,8};
static GLubyte esquerdaIndices[]  = {0,5,8,4};
static GLubyte direitaIndices[]   = {2,3,7,6,9};
static GLubyte topoIndices[]      = {0,1,9,6,5};
static GLubyte fundoIndices[]     = {3,4,8,7};
static GLubyte trianguloIndices[] = {1,2,9};
    
static int eixoy, eixox;
int largura, altura;

int main(int argc, char** argv){
  int i;
  glutInit(&argc, argv);
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize (256, 256); 
  glutInitWindowPosition (100, 100); 
  glutCreateWindow (argv[0]);
  init();
  glutDisplayFunc(display); 
  glutKeyboardFunc(keyboard);
  glutReshapeFunc(reshape);
  glutMainLoop();
  return 0;
}

void init(void){
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glOrtho (-50, 50, -50, 50, -50 , 50);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE); 
}

void reshape (int w, int h){
  glViewport (0, 0, (GLsizei) w, (GLsizei) h);
  largura=w; 
  altura=h;
}

void display(void){
  glPushMatrix();
  glRotatef ((GLfloat) eixoy, 0.0, 1.0, 0.0);
  glRotatef ((GLfloat) eixox, 1.0, 0.0, 0.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, vertices);

  glColor3f (AZUL); /* frente */
  glDrawElements(GL_POLYGON, 5, GL_UNSIGNED_BYTE, frenteIndices);

  glColor3f (AMARELO); /* esquerda */
  glDrawElements(GL_POLYGON, 4, GL_UNSIGNED_BYTE, esquerdaIndices);

  glColor3f (VERMELHO); /* tras */
  glDrawElements(GL_POLYGON, 4, GL_UNSIGNED_BYTE, trasIndices);

  glColor3f (VERDE); /* direita */
  glDrawElements(GL_POLYGON, 5, GL_UNSIGNED_BYTE, direitaIndices);

  glColor3f (CYAN); /* topo */
  glDrawElements(GL_POLYGON, 5, GL_UNSIGNED_BYTE, topoIndices);

  glColor3f (LARANJA); /* fundo */
  glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, fundoIndices);

  glColor3f (CINZA); /* triangulo */
  glDrawElements(GL_POLYGON, 3, GL_UNSIGNED_BYTE, trianguloIndices);

  glDisableClientState (GL_VERTEX_ARRAY);

  glPopMatrix();
  glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y){
  switch (key) {
  case 27:
    exit(0);
    break;
  case 'a':
    printf("%d, %d\n",x,y);
    break;
  case 'y':
    eixoy = (eixoy + 5) % 360;
    glutPostRedisplay();
    break;
  case 'Y':
    eixoy = (eixoy - 5) % 360;
    glutPostRedisplay();
    break;
  case 'x':
    eixox = (eixox + 5) % 360;
    glutPostRedisplay();
    break;
  case 'X':
    eixox = (eixox - 5) % 360;
    glutPostRedisplay();
    break;
  case 'p':
    glLoadIdentity();
    gluPerspective(65.0, (GLfloat) largura/(GLfloat) altura, 20.0, 120.0);
    gluLookAt(0, 0, -90, 0, 0, 0, 0, 1, 0);
    glutPostRedisplay();
    break;
  case 'o':
    glLoadIdentity();
    glOrtho (-50, 50, -50, 50, -50 , 50);
    glutPostRedisplay();
    break;
  }
}

Para compilar e executar o programa projecoes.c, salve-o juntamente com o arquivo Makefile em um diretório e execute a seguinte seqüência de comandos:

$ make projecoes
$ projecoes

5.1. Descrição do programa projecoes.c

#define AZUL     0.0, 0.0, 1.0
#define VERMELHO 1.0, 0.0, 0.0
#define AMARELO  1.0, 1.0, 0.0
#define VERDE    0.0, 1.0, 0.0
#define CYAN     1.0, 0.0, 1.0
#define LARANJA  0.8, 0.6, 0.1
#define ROSEO    0.7, 0.1, 0.6
#define CINZA    0.6, 0.6, 0.6

Define nomes para as tonalidades de cor utilizadas nas faces do cubo. Cada linha contém o nome da cor e as respectivas componentes R, G e B.

static GLfloat vertices[30]={
  0.0,  30.0, 30.0, /* 0 */
  20.0, 30.0, 30.0, /* 1 */
  30.0, 20.0, 30.0, /* 2 */
  30.0,  0.0, 30.0, /* 3 */
  0.0,   0.0, 30.0, /* 4 */
  0.0,  30.0,  0.0, /* 5 */
  30.0, 30.0,  0.0, /* 6 */
  30.0,  0.0,  0.0, /* 7 */
  0.0,   0.0,  0.0, /* 8 */
  30.0, 30.0, 20.0  /* 9 */
}; 

Armazena em um vetor as posições de cada um dos vértices do cubo. Serão tomados posteriormente grupos de três elementos para compor as coordenadas x, y e z dos vértices. Os comentários que aparecem ao lado de cada linha referenciam as coordenadas correspondentes dos vértices da Figura 5-1.

static GLubyte frenteIndices[]    = {0,4,3,2,1};
static GLubyte trasIndices[]      = {5,6,7,8};
static GLubyte esquerdaIndices[]  = {0,5,8,4};
static GLubyte direitaIndices[]   = {2,3,7,6,9};
static GLubyte topoIndices[]      = {0,1,9,6,5};
static GLubyte fundoIndices[]     = {3,4,8,7};
static GLubyte trianguloIndices[] = {1,2,9};

Define vetores com índices para cada uma das faces do cubo, especificando os vértices que irão construí-las. A ordem em que os índices (números dos vértices) é incluída em cada vetor é importante, pois será esta a utilizada para introduzir cada vértice no desenho dos polígonos que formarão cada face. A parte frontal do polígono fica saindo do polígono, quando a seqüência de índices é especificada no sentido anti-horário, como mostra a Figura 5-2 para o polígono do topo (topoIndices[]).

static int eixoy, eixox;
int largura, altura;

Define as duas variáveis eixoy e eixox, para armazenar as rotações em torno dos eixos y e x, respectivamente, e outras duas para armazenar a altura e a largura da tela de desenho.

void init(void){
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glOrtho (-50, 50, -50, 50, -50 , 50);

Na função init(), glClearColor() define PRETO (R,G,B)=(0,0,0) como a cor de limpeza da área de desenho. A chamada à função glOrtho() decide inicialmente que os objetos serão desenhados utilizando projeções ortográficas. De acordo com os parâmetros passados para esta função, os planos de recorte serão:

  • esqueda = -50; direita= +50

  • fundo = -50; topo= +50

  • frente = -50; trás= +50

Este volume de recorte garante que todo o objeto ficará sempre visível quando as transformações forem efetuadas sobre o mesmo.

void reshape (int w, int h){
  glViewport (0, 0, (GLsizei) w, (GLsizei) h);
  largura=w; 
  altura=h;
}

A função reshape() é chamada cada vez que o tamanho da tela é alterado pelo usuário, atribuindo às variáveis largura e altura as novas dimensões da janela. Estas duas variáveis são utilizadas juntamente com a função glViewport() para tornar o cubo proporcional ao tamanho da janela.

  glPushMatrix();
  glRotatef ((GLfloat) eixoy, 0.0, 1.0, 0.0);
  glRotatef ((GLfloat) eixox, 1.0, 0.0, 0.0);

Utilizando a função glPushMatrix(), a posição e orientação do sistema de coordenadas original é guardado na pilha. Com as funções glRotatef() são realizadas rotações no objeto em torno dos eixos y e x, de modo possibilitar a visualização de outras faces.

  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, vertices);

Aqui entra uma característica nova do OpenGL: a possibilidade de desenhar objetos utilizando índices para referenciar as coordenadas dos seus vértices. Entretanto, esta característica deve ser habilitada com a chamada à função glEnableClientState(), caso contrário nada será desenhado. Os vértices do cubo são indexados através da chamada à função glVertexPointer(), que possui o seguinte protótipo:

void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);

O parâmetro size especifica o número de elementos que devem ser tomados do vetor pointer de cada vez para forma um vértice - neste caso, 3 elementos, uma para cada eixo coordenado. type especifica o tipo de dado contido no vetor e stride o deslocamento que deve ser realizado dentro do vetor entre vértices consecutivos. Como os elementos estão colados uns aos outros, stride = 0.

  glDrawElements(GL_POLYGON, 5, GL_UNSIGNED_BYTE, frenteIndices);

A função glDrawElements() realiza o traçado deprimitivas com base em um vetor de dados. Neste exemplo, A primitiva a ser traçada é um polígono (GL_POLYGON) com 5 vértices, indexados pelo vetor frenteIndices, que é do tipo GL_)UNSIGNED_BYTE. As chamadas seguintes para esta função desenham o restante das faces do cubo.

void keyboard(unsigned char key, int x, int y){
  case 'p':
    glLoadIdentity();
    gluPerspective(65.0, (GLfloat) largura/(GLfloat) altura, 20.0, 120.0);
    gluLookAt(0, 0, -90, 0, 0, 0, 0, 1, 0);
    glutPostRedisplay();
    break;

Na função keyboard() é introduzida uma chamada à função gluPerspective(). Esta chamada faz com que todas as projeções efetuadas daí em diante sejam projeções de perspectiva. Esta função possui o seguinte protótipo:

void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

O parâmetro fovy especifica o campo de visão, em graus, na direção y (65 graus). O parâmetro aspect define a relação de aspecto entre largura e altura, determinando o campo de visão na direção x (largura/altura). zNear e zFar especificam as distâncias entre o observador e o planos de recorte mais próximo e mais distante, respectivamente (20 e 120). A escolha deste valor assegura que o cubo não será recortado durante as transformações. Estas variáveis são ilustradas na Figura 5-3.

A chamada à função gluLookAt() permite definir o ponto de observação, um ponto de referência, para onde o observador está olhando e a direção do vetor que aponta para cima. A função gluLookAt() possui o seguinte protótipo:

void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);

Neste exemplo, o observador encontra-se sobre o eixo z, em z=-90, (eixox, eixoy, eixoz) = (0, 0, -90), está olhando para a origem, (centerx, centery, centerz) = (0, 0, 0) e a direção do vetor que aponta para cima é (upx, upy, upz) = ( 0, 1, 0), alinhado com o eixo y.

Antes de chamar as funções de projeção de perspectiva ou ortográfica, deve-se tomar cuidado para antes reiniciar a localização e orientação do sistema de coordenadas usando a função glLoadIdentity(), caso contrário a projeção será feita no sistema de coordenadas corrente, levando a resultados indesejados.

CopyLeft Webmaster |Atualizado em 22/11/2003
Contato   Pesquisas e Projetos  Publicações   Home