Aula Prática: Modelagem em OpenGL

Preliminares

O objetivo desta prática é entender como desenhar objetos simples em OpenGL. Para tanto, a biblioteca oferece um conjunto de primitivas de desenho, que podem ser utilizadas tanto em 2D como em 3D.

O código apresentado nesta aula foi escrito em Java, mas pode ser facilmente utilizado em outras linguagens, como C/C++.

O primeiro passo consiste em criar um projeto no Eclipse para implementação da aplicação. Caso você não lembre como configurar o projeto, olhe as instruções disponíveis na outra aula prática. Depois faça o download do arquivo praticaModelagem.zip. Este arquivo contém as classes que devem ser inseridas no projeto.

Compile e execute o programa: será apresentada uma tela onde aparecem os eixos cartesianos e uma grade, para auxiliar na orientação. A área de desenho compreende coordenadas entre -10 e 10, tanto para x como para y:

Primitivas de Desenho em OpenGL

OpenGL possui diversas primitivas de desenho, ou seja, formas de interpretar informação geométrica. Como visto em aula, um objeto 2D é definido por seus vértices (geometria) e arestas ou faces (topologia). Em OpenGL, a partir de um conjunto de vértices é possível desenhar as seguintes primitivas:


Figura 1: Primitivas de desenho em OpenGL

Como pode-se ver na figura, a biblioteca suporta primitivas baseadas em pontos e linhas (GL_POINTS, GL_LINES, etc) e primitivas baseadas em faces (GL_QUADS, GL_TRIANGLES, etc). A diferença é que por definição, as primitivas baseadas em linhas apenas geram um traçado, enquanto as baseadas em faces apenas PINTAM o conteúdo (não traçam).

Utilizando as primitivas de desenho

Em OpenGL, ao invés de haver funções específicas para cada primitiva, são utilizadas constantes que definem o tipo de primitiva, e sempre as mesmas funções/métodos para realizar o desenho:

Onde PRIMITIVA é uma das constantes da figura 1. É possível trocar gl.glVertex2f por gl.glVertex2d, caso seja necessário ou desejável utilizar valores double ao invés de float.

Observe que não existe a noção de estrutura de dados: de onde as coordenadas vêm depende puramente da aplicação, ou seja, é responsabilidade da aplicação fornecer os dados corretamente.

O desenho em si só pode ser realizado no método display, definido na interface GLEventListener e implementado pelo programa.

public void display(GLAutoDrawable drawable)
{
    gl.glClear(GL.GL_COLOR_BUFFER_BIT);
    gl.glLoadIdentity();    

    // Preto: RGB especificado como valores de 0 a 1 (float)
    gl.glColor3f(0.0f, 0.0f, 1.0f);

    ... comandos de desenho ...
}

Observe que as cores devem ser especificadas com uma chamada a glColor.... No exemplo acima, utilizamos glColor3f, que interpreta valores entre 0 e 1 como componentes RGB (0=0%, 1=100%). Veja agora exemplos de como utilizar cada primitiva:

GL_POINTS: Pontos isolados

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glPointSize(10.0f); // aumenta o tamanho dos pontos
    gl.glBegin(GL.GL_POINTS);
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

É possível usar o método glPointSize para aumentar o tamanho dos pontos desenhados (raio em pixels).


GL_LINES: Linhas isoladas entre cada par de vértices

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glLineWidth(3.0f);  // aumenta a espessura das linhas
    gl.glBegin(GL.GL_LINES);
        gl.glVertex2f(-8,-5); // linha 1
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0); // linha 2
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5); // linha 3
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

É possível usar o método glLineWidth para aumentar a espessura das linhas desenhadas (em pixels).


GL_LINE_STRIP: Sequência de linhas

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glLineWidth(3.0f);  // aumenta a espessura das linhas
    gl.glBegin(GL.GL_LINE_STRIP);
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

Essa primitiva traça linhas entre os pontos, na sequência especificada. Na figura, a primeira linha desenhada está na parte de baixo da tela.


GL_LINE_LOOP: Sequência de linhas, une último ponto com primeiro

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glLineWidth(3.0f);  // aumenta a espessura das linhas
    gl.glBegin(GL.GL_LINE_LOOP);
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

O efeito é igual a GL_LINE_STRIP, mas desenha uma linha a mais entre o último e o primeiro ponto.


GL_TRIANGLES: Sequência de triângulos

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glBegin(GL.GL_TRIANGLES);
        gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glColor3f(0.0f, 1.0f, 0.0f); // verde
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

Essa primitiva pinta triângulos entre cada sequência de 3 pontos especificados. Observe que não é desenhado o contorno. Para facilitar o entendimento, utilizamos cores diferentes para cada triângulo.


GL_QUADS: Sequência de quadriláteros

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glBegin(GL.GL_QUADS);
        gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 0);
        gl.glColor3f(0.0f, 1.0f, 0.0f); // verde
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
        gl.glVertex2f(-8, 8);
        gl.glVertex2f( 8, 8);
    gl.glEnd();
}

Essa primitiva pinta quadriláteros entre cada sequência de 4 pontos especificados. Note que tivemos que incluir mais dois vértices, para termos dois conjuntos de 4. Como nos triângulos, não é desenhado o contorno.


GL_POLYGON: Polígono

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glBegin(GL.GL_POLYGON);
        gl.glVertex2f(-6,-5);
        gl.glVertex2f(-8,-3);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f(-8, 3);
        gl.glVertex2f(-6, 5);
        gl.glVertex2f( 6, 5);
        gl.glVertex2f( 8, 3);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f( 8,-3);
        gl.glVertex2f( 6,-5);
    gl.glEnd();
}

Essa primitiva pinta polígonos convexos, considerando todos os vértices informados. A cor de cada vértice pode ser especificada individualmente, e a biblioteca normalmente mistura (interpola) as cores no interior do polígono.


É importante entender o conceito de polígono convexo. Se considerarmos um polígono qualquer, diz-se que ele é convexo quando é possível traçarmos linhas de cada vértice para todos os demais, que nunca sairão para fora do polígono. Se essa regra não for satisfeita, OpenGL pintará o polígono de forma incorreta (veja a figura, onde aparece também o contorno desejado).

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glBegin(GL.GL_POLYGON);
    gl.glVertex2f(-6,-5);
        gl.glVertex2f(-3, 0);
        gl.glVertex2f(-6, 5);
        gl.glVertex2f( 6, 5);
        gl.glVertex2f( 3, 0);
        gl.glVertex2f( 6,-5);
    gl.glEnd();
}

GL_TRIANGLE_STRIP: Sequência de triângulos conectados

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(0.0f, 1.0f, 0.0f); // verde
    gl.glBegin(GL.GL_TRIANGLE_STRIP);
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
    gl.glEnd();
}

Essa primitiva pinta um triângulo considerando os 3 primeiros pontos, depois cada triângulo considerando os 3 últimos pontos. A figura mostra também o contorno dos triângulos.


GL_TRIANGLE_FAN: Sequência de triângulos com um ponto em comum

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(0.0f, 0.0f, 1.0f); // azul
    gl.glBegin(GL.GL_TRIANGLE_FAN);
        gl.glVertex2f( 0, 0); // primeiro ponto, comum a todos
        gl.glVertex2f(-4,-5);
        gl.glVertex2f(-6,-3);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f(-6, 3);
        gl.glVertex2f(-4, 5);
        gl.glVertex2f( 4, 5);
        gl.glVertex2f( 6, 3);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f( 6,-3);
        gl.glVertex2f( 4,-5);
    gl.glEnd();

Essa primitiva pinta triângulos, onde o primeiro vértice especificado é comum a todos. A figura mostra também o contorno dos triângulos.


GL_QUAD_STRIP: Sequência de quadriláteros conectados

public void display(GLAutoDrawable drawable)
{
    ...
    gl.glColor3f(1.0f, 0.0f, 0.0f); // vermelho
    gl.glBegin(GL.GL_QUAD_STRIP);
        gl.glVertex2f(-8,-5);
        gl.glVertex2f( 8,-5);
        gl.glVertex2f(-8, 0);
        gl.glVertex2f( 8, 0);
        gl.glVertex2f(-8, 5);
        gl.glVertex2f( 8, 5);
        gl.glVertex2f(-8, 8);
        gl.glVertex2f( 8, 8);
    gl.glEnd();
}

Essa primitiva pinta quadriláteros considerando sequências de pares de pontos. Os quadriláteros são pintados entre as linhas formadas por esses pontos.


Exercícios

1. Com base no que foi apresentado, tente escrever o código para desenhar a seguinte figura:

2. Tente imaginar uma forma de desenhar ou pintar um círculo. Dica: use uma das primitivas descritas anteriormente, e um pouco de trigonometria (senos e cossenos...).

3. Em programas mais complexos, é muito comum obtermos a descrição geométrica de objetos a partir de arquivos em formatos específicos. A exemplo de arquivos de imagem (JPEG, PNG, etc), há diversos formatos para descrição de arquivos de objetos. Um formato relativamente simples de ler é denominado OBJ. Veja um exemplo abaixo:

# Blender3D v248 OBJ File: casa.blend
# www.blender3d.org
mtllib casa.mtl
o casa
v -0.400000 -0.400000 0.000000
v 0.400000 -0.400000 0.000000
v 0.400000 0.400000 -0.000000
v -0.400000 0.400000 -0.000000
v -0.400000 0.400000 -0.000000
v 0.000000 0.750000 -0.000000
v 0.400000 0.400000 -0.000000
usemtl verde
s 1
f 1 2 3 4
usemtl telhado
s 1
f 5 6 7

Em linhas gerais, o arquivo descreve primeiramente uma sequência de vértices (as linhas começando com "v..."), depois uma sequência de faces (as linhas começando com "f..."). Os vértices são numerados a partir de um, e o terceiro valor pode ser ignorado (é a coordenada z).
Cada descrição de face contém a sequência de vértices que a compõem. Exemplo: o telhado é composto pelos vértices 5, 6 e 7, nessa ordem (ou seja, é um triângulo). No exemplo, há 5 vértices e 2 faces.

A linha "o Plane" apenas dá um nome ao modelo (pode ser ignorada), bem como a linha "s 1".

As linhas que começam com "usemtl" apenas denotam o tipo de material (ex: cor) das faces que seguem - essa informação é lida de outro arquivo. Veja abaixo um exemplo (arquivo casa.mtl):

# Blender3D MTL File: casa.blend
# Material Count: 2
newmtl verde
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.169558 0.569934 0.129521
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2

newmtl telhado
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.489859 0.239624 0.129521
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2

Se você quiser ler esse arquivo e extrair a cor de cada material, esta é especificada nas linhas que começam com "Ks...". A cor é especificada como 3 valores float (da mesma forma que é usada na chamada a gl.glColor3f).

Implemente uma classe capaz de ler e armazenar a lista de vértices e faces a partir de um arquivo no formato OBJ. Depois, na classe Renderer faça um método que permita desenhar o modelo carregado na tela.

Outros links interessantes

Os dois links abaixo contém vídeos que apresentam os mesmos conceitos, mas com exemplos em C.