Desenhando polígonos – Parte 1

27 01 2008

Nos artigos anteriores, vimos pontos e linhas. Embora sejam interessantes, ainda estão longe de mostrar todo o poder que o OpenGL tem. A partir de agora, vemos como desenhar polígonos. Desenhar polígonos é um campo ainda mais vasto, pois é sobre polígonos que o OpenGL aplicará recursos como iluminação, mistura de cores, texturas, materiais, entre outras. Nesse artigo, e nos próximos da série “Desenhando polígonos”, veremos ainda o básico sobre o desenho de polígonos, ainda de maneira muito similar ao que vimos até agora com linhas e pontos. Nos próximos, aprofundaremos em cada um dos outros recursos do OpenGL.

Polígonos e seus lados

Qualquer triângulo pode ser descrito por três pontos, que não estejam alinhados numa reta (não-colineares). Por se tratar apenas de três pontos, é praticamente impossível traçar um triângulo de maneira errada. Eles também definem um plano, sempre são polígonos convexos, são rapidamente desenhados pelo hardware, por isso, constituem a mais importante forma do OpenGL. Não se impressione se nos sites e nos materiais sobre OpenGL por aí, você encontrar verdadeiros fanáticos por triângulos. Por isso, iniciaremos a explicação sobre polígonos utilizando-os como exemplo.

O comando mais simples para se desenhar um triângulo é o GL_TRIANGLES. Você deve passar os três vértices para o triângulo recém formado. Veja no exemplo (as setas apenas ilustram a ordem de desenho dos pontos, não serão desenhadas):

Os dois comandos geram exatamente o mesmo triângulo, certo? Errado. Uma das maiores diferenças entre os polígonos e as outras formas vistas até aqui é que polígonos tem lado. E o OpenGL define esse lado pela ordem em que os comandos de desenho são dados. Por padrão, se os pontos são desenhados em sentido anti-horário, a figura está virada de frente, se os pontos são desenhados em sentido horário, a figura está de costas. Lados diferentes podem ter cores diferentes, materiais diferentes, ou mesmo, pode-se pedir para o OpenGL não desenhar em absoluto um dos lados da figura! Impedir um dos lados de ser desenhado pode reduzir o tempo de desenho, e é útil especialmente em figuras tridimensionais fechadas, como um cubo, para que o OpenGL não desenhe os lados internos, por exemplo. Na figura acima, o primeiro desenho está de frente, e o segundo de costas.

Esse sentido é conhecido como “winding“. Você pode altera-lo usando o comando glFrontFace e passando para ele a constante GL_CW para desenhar o lado da frente fornecendo comandos no sentido do relógio (CW se refere a clockwise, que é o sentido do relógio), ou então voltar ao padrão usando a constante GL_CCW (Counterclockwise). Raramente é necessário usar essa função, já que normalmente não desenhamos figuras “no braço”, no futuro, veremos como carregar modelos prontos de programas como Blender.

Modos de desenho e culling

Nem sempre queremos desenhar os polígonos de maneira sólida. As vezes, é bom desenhar apenas as linhas dos polígonos para ganhar performance (quando estamos editando um cenário complexo, por exemplo), ou mesmo somente os seus pontos. Algumas vezes, como já comentado acima, podemos desejar não desenhar um dos lados totalmente.

A função glPolygonMode define exatamente isso. Ela recebe dois parâmetros, o primeiro refere-se a que lado da figura será afetado – que pode ser GL_FRONT, GL_BACK ou o prático GL_FRONT_AND_BACK. E o segundo, o modo de desenho em si, que pode ser um dos três:

  • GL_POINT: Produz o mesmo efeito de desenharmos usando a primitiva GL_POINTS. Apenas os vértices são desenhados na forma de pequenos pontinhos;
  • GL_LINE: Produz o mesmo efeito se tivessemos desenhado usando a primitiva GL_LINE_LOOP. Apenas as linhas são desenhadas, sem qualquer preenchimento interno;
  • GL_FILL: É o estado padrão, desenha a figura com o interior preenchido. Somente através nesse modo é que poderemos aplicar suavização e, obviamente, cores, texturas e padrões.

E para eliminar totalmente o desenho de um dos lados do polígono? Para isso, habilitamos a capacidade GL_CULL_FACE usando a função glEnable. Automaticamente, o OpenGl iniciará a eliminação de um dos lados de tudo que for desenhado em seguida, até que um glDisable() dessa capacidade seja encontrado. E qual lado será eliminado? Para definir isso, usamos a a função glCullFace. A função aceita dois parâmetros GL_BACK (para eliminar o fundo) e GL_FRONT para eliminar a frente. A palavra”Cull” se refere a culling, que é justamente o processo de eliminação de partes desnecessárias do desenho.

Suavização de serrilhado (anti-aliasing)

Assim como os pontos e as linhas, também é possível aplicar suavização aos polígonos. Para fazer isso apenas habilitamos a capacidade GL_POLYGON_SMOOTH usando glEnable, como você provavelmente já havia deduzido. Como nos demais casos, podemos questionar se esse valor está ou não ligado usando essa mesma constante nas funções glGet e glIsEnabled.

É importante ressaltar que suavização em polígonos é um recurso caro. Use-o com cuidado e nunca deixe de avaliar bem o seu impacto na performance do seu código. Não é a toa que muitos jogos profissionais fornecem opções para que o usuário escolha se deseja ou não liga-lo.

Cores

Exceto se estivermos produzindo um jogo sobre o primeiro volume de Sin City, precisaremos de cores além do preto e do branco. Vamos aprofundar um pouquinho mais no básico sobre cores, para poder desenhar nossos polígonos com alguma graça. No futuro, farei um artigo inteiro sobre o assunto.

Como já vimos brevemente no passado, um dos comandos para colorir no OpenGL é o glColor. Esse comando tem uma infinidade de assinaturas, que aceitam praticamente qualquer tipo de dado conhecido. O que é importante saber, é que o comando aceita os três tradicionais parâmetros de cor, verde, vermelho, azul, mas que cada canal é representado por um valor de ponto flutuante, que varia entre 0 (sem a cor) e 1 (totalmente ligada). Mesmo as funções que utilizam números inteiros e unsigned, mapeiam o menor valor aceito pela variável em questão para 0, e o maior valor para 1, aplicando a proporção nos demais valores.

Existe um motivo: a notação é diferente da maior parte das linguagens e ferramentas, porque as cores referem-se apenas a cor real do objeto. Essa cor ainda sofrerará transformações de acordo com as luzes (que também podem ser coloridas) e o material do objeto, com a possível presença de materiais transparentes em frente dele e de acordo com sua distância relativa do objeto com a câmera. Portanto, para a decepção do seu designer gráfico, não basta apenas conhecer a tabela Pantone para definir exatamente o que vai ou não aparecer na tela.

Os principais comandos de cor usados são os seguintes:

  • glColor3f – Define a cor em seus 3 componentes RGB, sendo 0 para ausência de cor e 1 para presença;
  • glColor3fv – Variação do comando acima, que aceita um array com os 3 componentes;
  • glColor4f e glColor4fv – Inclui além dos três componentes de cor, um componente de transparência (canal alfa);
  • glColor3ubv e glColor4ubv – Aceita vetores com os componentes em unsigned bytes. Assim, os componentes podem assumir valores 0 para o menor componente de cor, e 255 para o maior.

As cores são aplicadas por vértice. Se os vértices forem coloridos, o OpenGL fará a aproximação das cores. É por isso que com apenas três comandos de cor, geramos o triângulo super colorido dos primeiros exemplos. Lembram-se dele?

Triângulo colorido

Podemos desabilitar esse efeito através do comando glShadeModel() usando o parâmetro GL_FLAT. Assim o polígono assumirá apenas a primeira cor que lhe for atribuída e desprezando as outras. Ou, podemos retornar ao padrão através do parâmetro GL_SMOOTH.

Esses comandos de cor também podem ser usados para linhas e pontos.

Mais detalhes sobre as primitivas

Você deve estar lembrado que ao falarmos das primitivas do OpenGL, citamos também algumas que desenhavam múltiplos polígonos como GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP e GL_QUAD_STRIP. Você pode estar se perguntando, como fica a questão do winding nesse caso? Como era de se esperar, o OpenGL manterá a face de todos os polígonos voltadas para o mesmo lado.

Além dessa garantia, é importante saber que esses comandos também promovem otimização do hardware do vídeo, e que triângulos são preferíveis a qualquer outra forma geométrica. De fato, muitos softwares para composição e formatos de modelos procuram inserir apenas triângulos em suas formas. Como é o caso do formato MD2, do Quake 2.

Próximos passos

Nos próximo artigo, veremos como aplicar padrões de desenho aos polígonos e como evitar o desenho de determinados vértices. Usando o conhecimento de hoje, procure fazer o seguinte exercício:

1. Construa um cubo, em três dimensões usando a primitiva GL_QUADS.
2. Deixe cada face do cubo de uma cor diferente, em um suave degradée (basta colocar 2 vértices num tom mais claro, e 2 num tom mais escuro);
3. Substitua a primitiva GL_QUADS por GL_TRIANGLES e tente construir o mesmo cubo;
4. Aplique culling para eliminar os lados não desenhados do cubo: Você pode ter surpresas aqui se o winding de seu cubo estiver errado.
5. Suavize o desenho do cubo;

Trabalharemos muito com esse cubo nos artigos futuros. Portanto, é bom você conhecê-lo bem.