Tutorial Básico sobre OpenGl

Neste artigo, iremos percorrer o código-fonte de um programa simples que usa OpenGL para entender os conceitos básicos relacionados ao tema, de modo a servir de base para aplicações mais complexas.

Para começar, segue abaixo a função main() de nosso programa. Estaremos explicando cada uma das funções dentro dele a seguir:

int main(int argc, char **argv) {
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(350,300);
glutInitWindowPosition(10,10);
glutInit(&argc,argv);
glutCreateWindow("3D");
glutDisplayFunc(Desenha);
glutReshapeFunc(AlteraTamanhoJanela);
glutMouseFunc(GerenciaMouse);
glutKeyboardFunc(GerenciaTeclado);
glutSpecialFunc(TeclasEspeciais);
Inicializa();
glutMainLoop();
}

glutInitDisplayMode

A função glutInitDisplayMode() avisa GLUT para utilizar dois buffers no desenho de cenas: um principal e outro auxiliar. Todos os objetos deverão desenhados no buffer auxiliar. Quando a função glutSwapBuffers() for chamada, o buffer auxiliar passa a ser o principal, e o principal toma o lugar do auxiliar. Assim, a imagem gerada é apresentada de uma só vez na tela, evitando cintilações e a visualização do processo de desenho, efeitos indesejáveis principalmente em animações.

GLUT_RGBA
Mascara de bits para selecionar o modo de janela RGBA. Esse é o padrão se nem GLUT_RGBA ou GLUT_INDEX forem especificados.
GLUT_RGB
Um apelido para GLUT_RGBA.
GLUT_INDEX
Mascara de bits para selecionar o modo de janela com um índice de cor. Isso sobrecarrega GLUT_RGBA se também for especificada.
GLUT_SINGLE
Mascara de bits para selecionar uma janela com buffer simples. Esse é o padrão caso nem GLUT_DOUBLE ou GLUT_SINGLE forem especificados.
GLUT_DOUBLE
Mascara de bits para selecionar uma janela com buffer duplo. Sobrecarrega GLUT_SINGLE se também for especificado.
GLUT_ACCUM
Mascara de bits para selecionar uma janela com buffer de acumulação.
GLUT_ALPHA
Mascara de bits para selecionar uma janela com um componente alpha para o buffer de cores.
GLUT_DEPTH
Mascara de bits para selecionar uma janela com buffer de profundidade.
GLUT_STENCIL
Mascara de bits para selecionar uma janela com um buffer de estêncil.
GLUT_MULTISAMPLE
Mascara de bits para selecionar uma janela com suporte a multi amostragem. Se multi amostragem não estiver disponível, uma janela sem esse recursos será exibida. Nota: tanto implementações do lado cliente quanto do lado servidor do OpenGL devem suportar a extensão GLX_SAMPLE_SGIS para que multi amostragem esteja disponível.
GLUT_STEREO
Mascara de bits para selecionar uma janela estéreo.
GLUT_LUMINANCE
Mascara de bits para selecionar uma janela com um modelo de cores “luminance”. Esse modelo fornece a funcionalidade do modelo de cor RGBA, mas os componentes verde e azul não são mantidos no buffer. Ao invés disso cada componente vermelho do pixel é convertido em um índice entre zero e  glutGet (GLUT_WINDOW_COLORMAP_SIZE)-1 e visto em um mapeamento de cores para determinar a cor do pixel na janela. O mapeamento de cor inicial de GLUT_LUMINANCE é inicializado como cinza linear, mas pode ser modificado com rotinas do GLUT.

glutInitWindowPosition, glutInitWindowSize.

void glutInitWindowSize(int width, int height);
void glutInitWindowPosition(int x, int y);

Atribui a posição inicial e o tamanho da janela criada. Os valores são dados em pixels e servem como uma sugestão ao sistema de janela do SO.

glutInit

void glutInit(int *argcp, char **argv);
Inicializa a biblioteca GLUT e negocia a sessão junto com o sistema de janela. Neste processo, glutInit pode provocar a finalização da aplicação GLUT, enviando uma mensagem de erro ao usuário indicando que não pode ser inicializada apropriadamente.

glutCreateWindow

int glutCreateWindow(char *name);
Cria uma janela e associa o nome passado como parâmetro ao nome da janela. O valor retornado é um identificador (número inteiro) da janela.

glutDisplayFunc(Desenho)

void glutDisplayFunc(void (*func)(void));
func é chamada de função display callback. Quando o GLUT determina que o conteúdo da janela precisa ser redesenhado, display callback é chamada.

Desenho()

Abaixo segue um exemplo de uma função de callback para executar um desenho na tela:

void Desenho(void)
{
 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

 glLoadIdentity();
 gluLookAt(0,0,5,0,0,0,0,1,0);

 glBegin(GL_LINES);
  glVertex3f(-0.5,-0.5,0.0);
  glVertex3f(0.5,0.0,0.0);
  glVertex3f(0.0,0.5,0.0);

   glutSwapBuffers();
 glEnd();
 glFlush();
}

glutReshapeFunc(AlteraTamanhoJanela)

estabelece a função “AlteraTamanhoJanela” previamente definida como a função callback de alteração do tamanho da janela. Isto é, sempre que a janela é maximizada, minimizada, etc., a função “AlteraTamanhoJanela” é executada para reinicializar o sistema de coordenadas.

void AlteraTamanhoJanela

Abaixo segue um exemplo da função AlteraTamanhoJanela:

void AlteraTamanhoJanela(GLsizei w, GLsizei h)
{
                   // Evita a divisao por zero
                   if(h == 0) h = 1;

                   // Especifica as dimensões da Viewport
                   glViewport(0, 0, w, h);

                   // Inicializa o sistema de coordenadas
                   glMatrixMode(GL_PROJECTION);
                   glLoadIdentity();

                   // Estabelece a janela de seleção (left, right, bottom, top)
                   if (w <= h)
                           gluOrtho2D (0.0f, 250.0f, 0.0f, 250.0f*h/w);
                   else
                           gluOrtho2D (0.0f, 250.0f*w/h, 0.0f, 250.0f);
}

Explicação sobre as funções Desenho() e AlteraTamanhoJanela()

Com apenas algumas primitivas simples, tais como pontos, linhas e polígonos, é possível criar estruturas complexas. Em outras palavras, objetos e cenas criadas com OpenGL consistem em simples primitivas gráficas que podem ser combinadas de várias maneiras. Portanto, OpenGL fornece ferramentas para desenhar pontos, linhas e polígonos, que são formados por um ou mais vértices. Neste caso, é necessário passar uma lista de vértices, o que pode ser feito entre duas chamadas de funções OpenGL:


glBegin()
glEnd()


O argumento passado para glBegin() determina qual objeto será desenhado. No exemplo abaixo, para desenhar três pontos pretos foi usada a seguinte sequência de comandos:

glBegin(GL_POINTS);
	glColor3f(0.0f, 0.0f, 0.0f);
	glVertex2i(100, 50);
	glVertex2i(100, 130);
	glVertex2i(150, 130);
glEnd();

Para desenhar outras primitivas, basta trocar GL_POINTS, que exibe um ponto para cada chamada ao comando glVertex, por:

  • GL_LINES: exibe uma linha a cada dois comandos glVertex;

  • GL_LINE_STRIP: exibe uma seqüência de linhas conectando os pontos definidos por glVertex;

  • GL_LINE_LOOP: exibe uma seqüência de linhas conectando os pontos definidos por glVertex e ao final liga o primeiro como último ponto;

  • GL_POLYGON: exibe um polígono convexo preenchido, definido por uma seqüência de chamadas a glVertex;

  • GL_TRIANGLES: exibe um triângulo preenchido a cada três pontos definidos por glVertex;

  • GL_TRIANGLE_STRIP: exibe uma seqüência de triângulos baseados no trio de vértices v0, v1, v2, depois, v2, v1, v3, depois, v2, v3, v4 e assim por diante;

  • GL_TRIANGLE_FAN: exibe uma seqüência de triângulos conectados baseados no trio de vértices v0, v1, v2, depois, v0, v2, v3, depois, v0, v3, v4 e assim por diante;

  • GL_QUADS: exibe um quadrado preenchido conectando cada quatro pontos definidos por glVertex;

  • GL_QUAD_STRIP: exibe uma seqüência de quadriláteros conectados a cada quatro vértices; primeiro v0, v1, v3, v2, depois, v2, v3, v5, v4, depois, v4, v5, v7, v6, e assim por diante.

A função glVertex2i pertence à biblioteca GL e possui dois argumentos inteiros. De maneira análoga, também é possível passar valores de ponto flutuante no lugar de inteiros, e três coordenadas (x,y,z) no lugar de duas usando, por exemplo, as seguintes chamadas às funções OpenGL:


glVertex2d(100.0, 50.0);
glVertex3f(50.0, 50.0, 50.0);

Além disso, para cada vértice é possível definir uma cor diferente. Neste caso, no desenho final é feita uma “interpolação” das cores.

Antes de descrever os parâmetros e comandos da função “AlteraTamanhoJanela”, é necessário revisar alguns conceitos e especificações. Em quase todos ambientes de janelas, o usuário pode alterar o tamanho e dimensões da janela em qualquer momento. Quando isto ocorre, o conteúdo da janela é redesenhado levando em conta as novas dimensões. Normalmente, o esperado é que a escala do desenho seja alterada de maneira que ele fique dentro da janela, independente do tamanho da janela de visualização ou do desenho. Assim, uma janela pequena terá o desenho completo, mas pequeno, e uma janela grande terá o desenho completo e maior.

Apesar do exemplo mostrar um quadrado 2D, o desenho é feito em um espaço de coordenadas 3D. A função glBegin(GL_QUADS);… glEnd(); desenha o quadrado no plano xy em z=0. Portanto, é necessário determinar o tamanho da viewport (janela onde será feito o desenho) e do volume de visualização (parte do universo da aplicação que será mapeada para viewport), pois estes parâmetros influenciam o espaço de coordenadas e a escala do desenhos 2D e 3D na janela.

Sempre que o tamanho da janela é alterado, a viewport e o volume de visualização devem ser redefinidos de acordo com as novas dimensões da janela. Assim, a aparência do desenho não é alterada (por exemplo, um quadrado não vira um retângulo). Como a alteração do tamanho da janela é detectada e gerenciada de maneira diferente em cada ambiente, a biblioteca GLUT fornece a função glutReshapeFunc, descrita anteriormente, que registra a função callback que a GLUT irá chamar sempre que houver esta alteração. A função passada para a glutReshapeFunc deve ter o seguinte protótipo: void AlteraTamanhoJanela(GLsizei w, GLsizei h);. O nome “AlteraTamanhoJanela” foi escolhido porque descreve o que a função faz. Os parâmetros recebidos sempre que o tamanho da janela é alterado são a sua nova largura e a sua nova altura, respectivamente. Esta informação é usada para modificar o mapeamento do sistema de coordenadas desejado para o sistema de coordenadas da tela com a ajuda de duas funções uma OpenGL, glViewport, e uma da biblioteca GLU, gluOrtho2D. Estas e outras funções chamadas na “AlteraTamanhoJanela”, que definem como a viewport é especificada, são descritas a seguir.

  • glViewport(0, 0, w, h); recebe como parâmetro a nova largura e altura da janela. O protótipo desta função é: void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);. Seus parâmetros especificam o canto inferior esquerdo da viewport (x,y) dentro da janela, e a sua largura e altura em pixels (width e height). Geralmente x e y são zero, mas é possível usar a viewport para visualizar mais de uma cena em diferentes áreas da janela. Em outras palavras, a viewport define a área dentro janela, em coordenadas de tela, que OpenGL pode usar para fazer o desenho. O volume de visualização é, então, mapeado para a nova viewport.

  • gluOrtho2D (0.0f, 250.0f*w/h, 0.0f, 250.0f); é usada para determinar que a projeção ortográfica (2D) será utilizada para exibir na tela a imagem 2D que está na janela de seleção definida através dos parâmetros passados para esta função. O protótipo desta função é: void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);. No sistema de coordenadas cartesianas, os valores left e right especificam os limites mínimo e máximo no eixo X; analogamente, bottom e top especificam os limites mínimo e máximo no eixo Y.

  • glMatrixMode(GL_PROJECTION); e glLoadIdentity(); servem, respectivamente, para avisar a OpenGL que todas as futuras alterações, tais como operações de escala, rotação e translação, irão afetar a “câmera” (ou observador), e para inicializar o sistema de coordenadas antes da execução de qualquer operação de manipulação de matrizes. Sem este comando, cada chamada sucessiva de gluOrtho2D poderia resultar em uma corrupção do volume de visualização. Em outras palavras, a matriz de projeção é onde o volume de visualização, que neste caso é um plano, é definido; a função gluOrtho2D não estabelece realmente o volume de visualização utilizado para fazer o recorte, apenas modifica o volume existente; ela multiplica a matriz que descreve o volume de visualização corrente pela matriz que descreve o novo volume de visualização, cujas coordenadas são recebidas por parâmetro.

  • glMatrixMode(GL_MODELVIEW); avisa a OpenGL que todas as futuras alterações, tais como operações de escala, rotação e translação, irão afetar os modelos da cena, ou em outras palavras, o que é desenhado. A função glLoadIdentity(); chamada em seguida, faz com que a matriz corrente seja inicializada com a matriz identidade (nenhuma transformação é acumulada).

glutMouseFunc

glutMouseFunc
determina a função de controle para comandos do mouse da janela atual.

Uso

void glutMouseFunc(void (*func)(int button, int state,int x, int y));
func
A função de controle de comandos do mouse.
void GerenciaMouse(int button, int state, int x, int y) {
if(button==GLUT_LEFT_BUTTON)
if(state == GLUT_DOWN) {
if(angle >= 10) angle -= 5;
}
if(button==GLUT_RIGHT_BUTTON)
if(state==GLUT_DOWN) {
if(angle <= 130) angle += 5;
}
EspecificaParametrosVisualizacao();
glutPostRedisplay();
}

glutKeyboardFunc

glutKeyboardFunc
Determina a função de manipulação de comandos do teclado da janela atual.

Uso

void glutKeyboardFunc(void (*func)(unsigned char key, int x, int y));
func
A função de manipulação de comandos do teclado.
void GerenciaTeclado(unsigned char key, int x, int y) {
switch(key) {
case 'R':
case 'r':
glColor3f(1.0f,0.0f,0.0f);
break;
case 'G':
case 'g':
glColor3f(0.0f,1.0f,0.0f);
break;
case 'B':
case 'b':
glColor3f(0.0f,0.0f,1.0f);
break;
}
glutPostRedisplay();
}

glutSpecialFunc

glutSpecialFunc
Determina a função de manipula comandos das teclas especiais do teclado na janela atual.

Uso

void glutSpecialFunc(void (*func)(int key, int x, int y));
func
A função que manipulada os comandos.
GLUT_KEY_F1
F1 function key.
GLUT_KEY_F2
F2 function key.
GLUT_KEY_F3
F3 function key.
GLUT_KEY_F4
F4 function key.
GLUT_KEY_F5
F5 function key.
GLUT_KEY_F6
F6 function key.
GLUT_KEY_F7
F7 function key.
GLUT_KEY_F8
F8 function key.
GLUT_KEY_F9
F9 function key.
GLUT_KEY_F10
F10 function key.
GLUT_KEY_F11
F11 function key.
GLUT_KEY_F12
F12 function key.
GLUT_KEY_LEFT
Left directional key.
GLUT_KEY_UP
Up directional key.
GLUT_KEY_RIGHT
Right directional key.
GLUT_KEY_DOWN
Down directional key.
GLUT_KEY_PAGE_UP
Page up directional key.
GLUT_KEY_PAGE_DOWN
Page down directional key.
GLUT_KEY_HOME
Home directional key.
GLUT_KEY_END
End directional key.
GLUT_KEY_INSERT
Inset directional key.

Note que as teclas ESC, Backspace e Delete são geradas como um caracter ASCII.

void TeclasEspeciais(int key, int x, int y) {
if(key == GLUT_KEY_UP) {
win -= 20;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-win,win,-win,win);
}
if(key == GLUT_KEY_DOWN) {
win += 20;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-win,win,-win,win);
}
glutPostRedisplay();
}

Inicializa()

Nesta função são feitas as inicializações OpenGL que devem ser executadas antes do rendering . Muitos estados OpenGL devem ser determinados somente uma vez e não a cada vez que a função “Desenha” é chamada.

glutMainLoop()

é a função que faz com que comece a execução da “máquina de estados” e processa todas as mensagens específicas do sistema operacional, tais como teclas e botões do mouse pressionados, até que o programa termine.