Para desenhar qualquer objeto no espaço, é necessário descreve-lo. E, para descrever um objeto, você necessita de referências, onde medidas se encontram numa escala válida. Normalmente, fora do OpenGL, descrevemos a posicionamento de pontos através da notação de linha/coluna. Embora essa notação seja adequada para o desenho de textos, ela está muito afastada da notação matemática, e acaba se tornando pouco prática para o desenho de figuras.
No OpenGL, assim como em praticamente todas as APIs 3D, você pode definir o seu próprio sistema de coordenadas. O sistema padrão, utilizado até agora, envolvia coordenadas de -1 até 1, tanto em x, y e z. O que fazia que as imagens ficassem lateralmente esticadas, já que a tela não é um quadrado e, logo, -1 até +1 em x é uma distância menor que -1 até +1 em y.
Nesse artigo, vamos aprender sobre como o OpenGL define o sistema de coordenadas, como definir o nosso próprio sistema e as diferentes formas de mapeamento de coordenadas.
Coordenadas cartesianas 2D
Além de diversos trabalhos de filosofia (e da criação da frase “Penso, logo existo.”), René Descartes (ou, Renatus Cartesius, em latim) foi também um matemático importante, e criou duas técnicas que conhecemos hoje como sistema cartesiano e geometria analítica. Se tivermos que atribuir um pai a computação gráfica, Descartes seria realmente um ótimo candidato à vaga.
A localização de cada ponto cartesiano é formada de um par de números, um no eixo x e outro no eixo y, chamados coordenadas. A coordenada x mede uma distância na direção horizontal e a y uma distância na posição vertical. A notação comumente utilizada para descrever um ponto é colocar ambas as coordenadas entre parênteses, separadas por vírgulas. Para evitar confusão e por ser mais próxima do que programaremos, usaremos a notação de números reais americana, com um “ponto” no lugar da vírgula. Assim, exemplos de coordenadas cartesianas seriam: (14.17, 50.23) ou (3, 4).
A figura abaixo, mostra o sistema cartesiano bidimensional, tal como vemos durante todo segundo grau. Ele é formado por dois eixos perpendiculares, definidos por duas retas marcadas numa unidade qualquer. O ponto (0,0), onde os dois eixos se cruzam em um ângulo de 90 graus, é chamado de origem. Quaisquer duas retas não colineares formam um plano (na verdade, basta uma reta e um ponto não colinear para definir um plano) e o plano formado pelo eixo cartesiano é o plano de desenho 2D.

Clipping de coordenadas
O sistema de coordenadas pode ser infinito, mas nossos monitores não são. Eles têm uma medida fixa, geralmente descrita em termos de pixels. Antes de iniciar o desenho, podemos dizer ao OpenGL como mapear os pares coordenados em unidades da tela, os pixels. Fazemos isso através da função void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top). Essa função aceita quatro parâmetros, que definem a coordenada máxima e mínima que cada eixo ocupará na área de desenho.
A figura abaixo mostra alguns exemplos dessa função na prática:

Também é possível fazer um eixo em que a menor coordenada esteja no topo e a maior na base, como é padrão em muitos aplicativos. Essa prática, entretanto, não é muito utilizada pela comunidade OpenGL.
A área onde o mapeamento é feito é chamada de clipping area, na tradução literal “area de recorte”. Isso porque mostramos ao OpenGL que parte do eixo cartesiano deve ser “recortada” para caber dentro da área de desenho.
Viewport: Definindo a área de desenho
Os jogos geralmente utilizam toda a área da tela para o desenho. Mas isso nem sempre é desejável para todos os tipos de aplicação. Usamos o comando void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) para definir exatamente o local e a área onde queremos desenhar. Por estramos tratando aqui de coordenas da janela, dentro do monitor, todas as medidas são dadas em pixels.
Você também pode usar o Viewport para aumentar ou diminuir o tamanho da imagem.
Por padrão, o viewport do OpenGL ocupa toda a janela. Entretanto, é sempre recomendável executar o comando glViewport pois algumas implementações do OpenGL mais antigas simplesmente ignoram esse padrão e, sem o comando, não desenham nada.
Coordenadas cartesianas 3D
Não é muito difícil imaginar um eixo cartesiano com mais uma dimensão. Crie um eixo perpendicular tanto a y, quanto a x e chame-o de eixo z. Definimos os pontos a partir de 3 coordenadas e, voilá. Temos um novo eixo cartesiano, tridimensional.
Embora matemáticamente perfeito, ao pensar no monitor alguns alarmes devem soar na sua cabeça. Afinal, TVs e monitores estão previstos para trabalhar em 3D só daqui ha alguns anos, e seu preço inicial será extremamente caro. Portanto, como podemos “expremer” três dimensões em apenas duas? Através de projeções, calculadas com base em trinometria e matrizes. O OpenGL trabalha com duas formas diferentes de projeção.
Projeção ortográfica
O primeiro tipo de projeção é o mais utilizado em CADs, programas de arquitetura, e jogos isométricos. É chamada de projeção ortográfica, ou projeção paralela. Para criar essa projeção você desenha uma forma retangular. Tudo que está fora dessa forma, não é desenhado. Por isso volume definido por essa forma, é chamado volume de visualização (viewing volume). Todos os objetos parecem ter o mesmo tamanho, independente do quão próximos ou o quão afastados se encontrem. Você especifica a projeção ortográfica através da função void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far). O desenho abaixo, ajuda a entender cada um desses valores:

A funçao gluOrtho2D, vista no tópico anterior, simplesmente fornece os valores -1 e 1 para near e far.
Projeção em perspectiva
Ao falarmos de jogos 3D, a forma de projeção mais comum é a projeção em perspectiva. Essa projeção faz com que objetos mais distantes sejam desenhados em tamanho menor do que se estivessem próximos. Nessa projeção o viewing volume não é definido por um retângulo e sim por uma forma que lembra uma pirâmide que sofreu um corte paralelo a base, chamada de frustum.
A função que permite-nos definir o tamanho do frustum é a seguinte:
Abaixo, uma figura que nos ajuda a entender cada um desses parâmetros:


Combinando projeções
Tipicamente, num jogo 3D, combinamos dois tipos de projeções. Primeiro, desenhamos todo o cenário usando a projeção em perspectiva. Em seguida, utilizamos a projeção ortográfica 2D para desenhar o heads-up display, com as informações de vida, balas, pontuação, etc. Veja como exemplo a tela principal do Bola Gelada:

A título de curiosidade, veja como foram definidas as funções no bola gelada:
- Viewport: glViewport (0, 0, GAME_WINDOW.getWidth(), GAME_WINDOW.getHeight());
- Perspectiva: gluPerspective(60.0f, GAME_WINDOW.getRatio(),1 ,15000);
- HUD: gluOrtho2D(0, GAME_WINDOW.getWidth(), GAME_WINDOW.getHeight(), 0);
Usando os comandos
Todos os comandos vistos, exceto glViewport, precisam ser usados na matriz de projeção do OpenGl. Entraremos em mais detalhes sobre as matrizes nos próximos artigos, por hora, apenas decore a sintaxe. Abaixo, um exemplo com glOrtho2D, mas que vale também para glOrtho, glPerspective e glFrustum:
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); //Resetamos a projeção atual
gluOrtho2D(0, GAME_WINDOW.getWidth(),
GAME_WINDOW.getHeight(), 0);
glMatrixMode(GL_MODELVIEW);
Resumindo
- Você viu como definir a clipping area, ou seja, o tamanho do eixo cartesiano na área de desenho, através da função gluOrtho2D;
- Também aprendeu a como definir o tamanho da área de desenho através da função glViewport;
- Viu como o OpenGL projeta coordenadas tridimensionais em bidimensionais;
- Aprendeu a como definir o modo de projeção ortográfica usando a função glOrtho;
- Entendeu que a projeção em perspectiva é mais adequada ao desenho 3D, e aprendeu a defini-la com a função glFrustum;
- Viu que é possível combinar duas projeções, para desenhar o HUD de um jogo 3D.
Exercícios
- Use a função glOrtho para criar um sistema de coordenadas proporcional a sua tela, como por exemplo de -40 até 40 na largura e de -30 até 30 na altura para uma janela em 800×600. Então, altere o seu cubo para ser desenhado nesse novo sistema. Note que o cubo parecerá melhor desenhado, agora que as coordenadas são proporcionais;
- Agora use a função gluPerspective para desenhar o cubo em perspectiva (você terá que incluir o header “GL/GLU.h”). Você notará que o cubo parecerá ainda mais bem desenhado, já que o OpenGL desenhará a parte de trás ligeiramente menor que a parte da frente.
Importante: Por padrão, a câmera está localizada na origem e voltada para -z. Portanto, para você não ficar exatamente no centro do cubo ao aplicar esse comando, desloque-o usando o comando glTranslatef(x,y,z); antes de desenha-lo ou gira-lo, onde x, y e z são distâncias. Uma boa medida para os valores de x, y e z usando-se o glOrtho sugerido no primeiro exercício é (0,0, -10). Veremos detalhes sobre esse comando no próximo artigo.
-
Experimente o modo de perspectiva em outros exercícios, como o das pirâmides e o da mola.
Referências
- OpenGL Superbible;
- OpenGL Red Book;
- Programming Game AI By Example;
- Biografia de René Descartes, na Wikipedia;