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.