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 |
#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.