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:
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:
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).
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:
gl.glBegin(GL.PRIMITIVA); - inicia uma ou mais primitivas
gl.glVertex2f(x,y) ou gl.glVertex3f(x,y,z) - especifica um vértice em 2D ou 3D
gl.glEnd(); - finaliza a definição
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:
É possível usar o método glPointSize para aumentar o tamanho dos pontos desenhados (raio em pixels). |
![]() |
É possível usar o método glLineWidth para aumentar a espessura das linhas desenhadas (em pixels). |
![]() |
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. |
![]() |
O efeito é igual a GL_LINE_STRIP, mas desenha uma linha a mais entre o último e o primeiro ponto. |
![]() |
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. |
![]() |
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. |
![]() |
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).
|
![]() |
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. |
![]() |
Essa primitiva pinta triângulos, onde o primeiro vértice especificado é comum a todos. A figura mostra também o contorno dos triângulos. |
![]() |
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. |
![]() |
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.
Os dois links abaixo contém vídeos que apresentam os mesmos conceitos, mas com exemplos em C.