No artigo anterior, vimos como obter e alterar informações da máquina de estados do OpenGL. Vimos também como ler informações sobre o OpenGL instalado na máquina e como verificar os códigos de erro.
Formas básicas: Pontos, linhas e polígonos
Todas as formas básicas estão em apenas 3 categoriais: pontos, linhas e polígonos. Você já deve ter uma boa idéia do que cada figura dessas representa matematicamente. Entretanto, esses conceitos não são idênticos na matemática e no computador. Primeiro, devido a imprecisão dos números de ponto flutuante e os consequentes erros de arredondamento, o que afeta todo o sistema de coordenadas do OpenGL. Segundo, devido a limitação física do monitor, que tem como sua menor unidade o pixel.
Vamos, então, as definições dos conceitos de ponto, linha e polígono adaptadas a essa realidade:
Um ponto é constituído por três variáveis de ponto flutuante. O OpenGL sempre trabalha com coordenadas tridimensionais e comandos onde apenas duas dimensões aparecem assumem os valores de Z como sendo zero. Um ponto é também chamado de vértice, quando representa uma extremidade de qualquer outra primitiva.
Quando falamos em linhas nos referimos à segmentos de retas, isso é, um traço delimitado em suas duas extremidades por dois vértices.
Finalmente, quando falamos em polígonos, estamos falando de um conjunto fechado de segmentos de reta (o OpenGL chama esse conjunto fechado de loop). Por ser uma figura fechada, o OpenGL pode preencher o interior de polígonos com cores e texturas. Quando falamos em formas básicas, não estamos falando de qualquer polígono. Os polígonos se restringem apenas a polígonos covexos simples. Ou seja, um polígono em que nenhum dos lados ficará no interior da figura original e onde dois de seus vértices nunca se encontram. Assim, todos os seguimentos de reta que sejam possíveis traçar com dois pontos quaisquer dentro do polígono terão, necessariamente, todos os seus pontos integralmente dentro do polígono. Abaixo, alguns exemplos de polígonos que podem e que não podem ser desenhados:

Essa restrição tem uma razão técnica. É mais fácil construir hardware que desenha esse tipo de polígono de forma acelerada. E o que acontece se você não cumprir essas restrições? O comportamento é indefinido, ou seja, vai variar de hardware para hardware. Exploraremos algumas formas sutis de quebrar essa regra no artigo sobre quadrados e polígonos.
Primitivas do OpenGL
O que são primitivas? Primitivas são comandos que desenham as formas básicas do OpenGL. Essas formas servem como peças, para montar qualquer desenho desejado. O comando básico para desenho de primitivas é:
Ele permite à você dizer ao OpenGL que você está pronto para começar um desenho e o que vai ser desenhado. Você pode usar um dos 10 modos abaixo para desenhar qualquer uma das três formas básicas. Observe que o OpenGL trata de maneira especial dois tipos de polígonos: os triângulos e os quadriláteros.
- GL_POINTS: Representando pontos individuais;
- GL_LINES: Linhas composta por pares de vértices;
- GL_LINE_STRIP: Uma série de linhas conectadas;
- GL_LINE_LOOP: Uma série de linhas conectadas. Também conecta o primeiro e o último vértice;
- GL_TRIANGLES: Desenha triângulos a cada 3 vértices fornecidos;
- GL_TRIANGLE_STRIP: Uma série de triângulos conectados. Após o desenho do primeiro triângulo, cada vértice adicional forma um novo triângulo com dois últimos pontos fornecidos;
- GL_TRIANGLE_FAN: Uma série de triângulos com um único vértice em comum. O vértice comum é o primeiro vértice fornecido;
- GL_QUADS: Desenha um quadrilátero a cada 4 vértices fornecidos;
- GL_QUAD_STRIP: Desenha uma série de quadriláteros. Após o primeiro, apenas mais 2 vértices precisam ser fornecidos para o desenho do segundo quadrilátero;
- GL_POLYGON: Desenha polígonos convexos simples com um número arbitrário de vértices.
A figura abaixo ilustra um exemplo para cada um dos modos de desenho. Observe atentamento o número de cada vértice e como o OpenGL conectou os seguimentos de reta para formar cada figura:

Você diz ao OpenGL que terminou de desenhar através da função glEnd(). A função é void e não aceita qualquer tipo de parâmetro. Geralmente, faz-se a identação do que aparece dentro dos blocos glBegin() e glEnd().
Cadê as curvas?
Você notou que todas as primitivas só desenham formas retas? O OpenGL não fornece uma forma primitiva de traçar curvas. Entretanto, isso não significa que essa seja uma tarefa impossível. Qualquer figura subdividida nos pontos certos e ligadas à seguimentos de retas parece curva. Quanto maior o número de pontos, menor o serrilhado e mais suave a curva parecerá. Veja um exemplo:

Mesmo as curvas não sendo primitivas, o OpenGL também fornece alguns comandos para dar suporte ao desenho de curvas ou superfícies curvas usando a técnica acima, através dos comandos avaliadores (evaluator commands) e dos NURBS, fornecidos pela biblioteca Glut. Esse tópico está muito adiante dos artigos de primitivas e será tratado numa ocasião futura.
Desenhando vértices
Vértices são um conceito particularmente importante no OpenGL, afinal, é através deles descrevemos absolutamente todas as formas primitivas. Mais do que isso, o OpenGL também fará seu mapeamento de texturas, luzes, neblina e cores baseado nos vértices. Para especificar um vértice, recorremos à uma família de funções, chamadas glVertex. Infelizmente, os projetistas do OpenGL optaram por não usar sobrecarga de nomes de função, e, portanto, as diferentes funções da família seguem a seguinte convenção:
- Inicia-se o nome com o prefixo gl;
- Coloca-se o nome da família (nesse caso Vertex);
- Indica-se o número de coordenadas a serem usadas;
- Indica-se o tipo de dados das coordenadas, através da primeira letra de seu tipo de dado. Usa-se também a letra v para vetores;
No caso de glVertex:
void glVertex(234)(dfis)[v](…)
Essa convenção de nomes de função é usada em todo lugar, não só na função glVertex, portanto, acostume-se com ela.
Uma das assinaturas mais usadas da família glVertex é certamente void glVertex3f(float, float, float), para o desenho de pontos em três dimensões, com cada float representando a coordenada do vértice nos eixos x, y e z.
Revendo o desenho do triângulo
Agora que já entendemos as primitivas e os vértices, que tal revisitarmos o bloco glBegin() e glEnd() da função dos artigos anteriores?
glBegin(GL_TRIANGLES); //desenho de triângulos glColor3f(1,0,0); glVertex2f(0.0f,0.5f); //Vértice 1 glColor3f(0,1,0); glVertex2f(-0.5f, -0.5f); //Vértice 2 glColor3f(0,0,1); glVertex2f(0.5f, -0.5f); //Vértice 3 glEnd();
Como você pode notar, usamos o modo de desenho GL_TRIANGLES para desenhar o triângulo. Então, dentro da função, desenhamos cada um dos três vértices, usando o sistema de coordenadas padrão (que varia de -1 até 1 em cada eixo). Lembre-se que o OpenGL trabalha com o sistema de coordenadas igual ao que vemos nos livros de matemática, então, (-1, -1) em y significa o ponto inferior esquerdo da tela, enquanto (1,1) o superior direito.
Associado a cada vértice, existe uma informação de cor, descrita pela função glColor. Cores não serão explicadas nesse momento, mas as mantive no exemplo para demonstrar na prática que os vértices podem carregar mais informações do que simplesmente a sua coordenada. Isso reforça o quão importante é o conceito de vértice para o OpenGL.
Outro detalhe interessante: Note que os vértices foram fornecidos no sentido anti-horário. Eles não podem ser fornecidos de qualquer jeito, pois é através do sentido que o OpenGL sabe não só como ligar as retas mas qual é o lado que a figura está virada. Algumas otimizações envolvem não desenhar um dos lados da figura, como veremos em artigos futuros.
Faça você mesmo:
1. Experimente alterar o código do triângulo girando criado nos artigos anteriores para usar as outras primitivas do OpenGL. Restrinja-se ao que está entre ao bloco glBegin() e glEnd(). Tente usar cada um dos 10 modos da enumeração do comando, como descrito acima;
2. Tente desenhar um círculo usando diversos seguimentos de reta. Você pode usar os comandos for e variáveis dentro dos blocos glBegin e glEnd normalmente. Você precisará usar a função as funções seno e cosseno (sin e cos);
3. Crie mais 2 blocos glBegin e glEnd embaixo do primeiro para criar a bandeira do Brasil girando (não precisa desenhar as estrelinhas e nem a faixa, a menos que você esteja MUITO determinado). As funções abaixo deixam os vertices nas cores verde, amarela e azul. Ela afeta todos os vértices declarados após a função:
glColor3f(0.0f, 0.7f, 0.0f); //Verde
glColor3f(1.0f, 1.0f, 0.0f); //Amarelo
glColor3f(0.0f, 0.0f, 1.0f); //Azul
Feliz 2008 !
Você está realmente determinado a fornecer material de apoio à nós iniciantes ! Sacrificou o 1º dia de 2008, isso me impressiona !
Escrever sobre o que a gente gosta não é um sacrifício tão grande assim.
Além disso, esse artigo já estava na minha cabeça há algum tempo. Foi um alívio materializar ele aqui. No próximo, acho que vou aprofundar um pouco mais o tratamento de cada uma das primitivas, para só depois entrar em cores.
Como agora tudo vai voltar: trabalho, pós-graduação, etc, as postagens voltam a ser semanais.
Obrigado novamente pelo comentário e um feliz 2008 para você também!
Muito Bom Vini!
Este blog está se tornando uma ótima referência de GameDev com SDL e OpenGL, Parabéns e Obrigado!
=D
Muito legal, pena que vou precisar de um livro de geometria para desenhar circulos.
Viny, “arregaçou”!Muito bom mesmo, e simples, o que é melhor ainda.
Valeu pessoal!
Vini,
Inicialmente, parabéns pelo artigo! Muito legal! Eu tentei montar uma função que desenha um círculo e até que funcionou (não sei se existe uma solução mais simples), porém eu não estou conseguindo que o círculo comece, por exemplo no ponto X 40 e no Y 20. Pelo o que eu vi ele sempre começa no centro. Tem alguma idéia?
Segue o código:
void DesenhaCirculo(GLfloat raio, int num_linhas) { int i; GLfloat angulo; angulo = 2 * M_PI / num_linhas; glBegin(GL_LINE_LOOP); for(i = 1; i <= num_linhas; i++) { glVertex2f(cos(i * angulo) * raio, sin(i * angulo) * raio); } glEnd(); }Basta somar os números que você quer em x e y. Ou então, usar a dica do artigo sobre transformações.
Exemplo:
for(i = 1; i <= num_linhas; i++) { glVertex2f(40 + cos(i * angulo) * raio, 20 + sin(i * angulo) * raio); }Só lembrando que inicialmente, o seu sistema de coordenadas vai de -1 até +1, tanto em x quanto em y. Para alterar isso, é necessário usar a função glOrtho() e mapear as coodenadas para o tamanho da tela. Há explicações sobre isso nos artigos sobre transformações.
Como é o codigo da bandeira (parece que está balançando).
Desculpe Eduardo, mas não entendi seu comentário. Era uma pergunta?
Desculpe se não fui claro, mas minha pergunta é: como é o código da bnadeira tremulando?
Não é trivial fazer diretamente por código e existem várias formas. Uma delas seria desenhar a bandeira com vários triângulos e distorcer a posição final dos pontos com um noise, gerado pela função seno modificada.
Outra seria gravar um modelo MD2 da bandeira tremulando (e deixar que o 3D Studio ou o Blender faça os cáculos para vc) e simplesmente reproduzi-lo na sua aplicação.
Olá Vinícius, estou estudando OpenGL de forma auto-didata, e pesquisando, encontrei neste artigo algo que estava procurando, que é desenhar com primitivas, formando ângulos.
Vi um exemplo que discutiram, ( Formando um círculo ), achei interessante, gostaria de saber onde posso conseguir um exemplo com maiores detalhes para fazer testes e entender melhor a função.
Esta é a função:
void DesenhaCirculo(GLfloat raio, int num_linhas) { int i; GLfloat angulo; angulo = 2 * M_PI / num_linhas; glBegin(GL_LINE_LOOP); for(i = 1; i <= num_linhas; i++) { glVertex2f(cos(i * angulo) * raio, sin(i * angulo) * raio); } glEnd(); }Esta foi a sugestão:
for(i = 1; i <= num_linhas; i++) { glVertex2f(40 + cos(i * angulo) * raio, 20 + sin(i * angulo) * raio); }O que preciso seria um projeto modelo para entender melhor como utilizar a funções cos e sin, para analisar melhor seu resultado.
Obrigado,
José Aparecido
Acho melhor você voltar a estudar trigonometria.
Saber o que as funções seno e cosseno fazem é extremamente importante em computação gráfica. E pelo que você deu a entender, o seu problema não é a programação, mas sim, a matemática.
OK, obrigado pelo retorno, você tem razão, e eu já estava providenciando isto.
De qualquer forma, consegui fazer o teste que tinha sugerido, e obrigado por contribuir com meus estudos, entendi a função ( pelo menos em parte ) mudando o numero de linhas, muda-se a forma da figura, até chegar num círculo mais perfeito possível para esta tecnologia…
Obrigado
José Aparecido
Ótimo! =D
No que tange a matemática, também dê aquela boa revisada em vetores matemáticos e matrizes.
E obrigado por prestigiar o blog! As atualizações estão meio lentas, mas não estão paradas!