Luzes na prática – Parte 2

27 07 2008

No artigo anterior, vimos como é possível especificar para o OpenGL as propriedades das luzes e dos materiais, como ligar e desligar luzes e como utilizar a luz direcional e posicional na prática. Neste artigo, aprenderemos a dizer para o OpenGL como a luz reflete em nossos objetos.

Uma observação sobre vetores

Ao falarmos de vetores, não estamos falaremos aqui do tipo de dado array do C++, mas do conceito matemático de vetores. Se você não está familiarizado com esse conceito, é melhor ler primeiro esse artigo, também escrito por mim.

Vetores são provavelmente o conceito matemático mais importante para um programador de jogos. Com eles, modela-se facilmente a física do jogo, posiciona-se a câmera, calcula-se campos de visão e, obviamente, fazemos a iluminação. Portanto, se você não se lembra, ou prefere revisar o conceito, é uma ótima idéia ler aquele artigo antes de prosseguir.

No futuro, eu provavelmente dedicarei artigos inteiros sobre esse conceito, dando exemplos com mais detalhes.

Normais de luz

Normais são vetores perpendiculares a uma determinada superfície. Elas são muito importantes em iluminação, pois podem ser usadas para descrever a orientação de uma determinada superfície. Como vimos, uma fonte de luz está posicionada num determinado local, ou brilha numa determinada direção. Quando se desenha um objeto, os raios de luz que partem dessa origem atingem a superfície desse objeto num determinado ângulo. Ao usar o ângulo entre a luz recebida e a normal, além das propriedades da luz e do material atingido, o OpenGL é capaz de calcular como a luz é refletida e chega ao observador e então definir a cor final em que a superfície deve ser colorida.

Normal de luz A imagem ao lado exibe esse conceito. A luz parte da origem atinge a supercíficie, formando um ângulo alfa. Ela então é refletida e sai formando um ângulo beta, que atinge o olho do observador. Quem define o tamanho do ângulo de alfa e beta é a normal, que nesse caso, forma exatamente 90º com a superfície.

No OpenGL as normais são especificadas por vértice, e não por face do polígono, tal como exibido na figura. A vantagem dessa abordagem é que quando você tem apenas uma normal por face você está assumindo que todas as normais de uma face apontam para a mesma direção. Embora isso seja próximo da física como a estudamos e uma boa para superfícies planas, podemos usar as normais por vértice para definir que um grupo de triângulos tem normais diferentes, e simular com isso uma superfície curva.

O OpenGL mantém um valor de normal na sua grande máquina de estados. Cada vez que um vértice é definido essa mesma normal é utilizada. Para altera-la, basta usar o comando glNormal3[fdbsi](x, y, z). Graças a essa propriedade, caso vários vértices seguidos tenham a mesma normal, podemos especifica-la somente uma vez, dentro da lista de vértices.

Os valores passados para o glNormal representam um vetor tridimensional, que indica para onde a normal aponta. Idealmente esse vetor deve ter comprimento unitário. O código a seguir, define um triângulo com as três normais apontado para a posição y positiva:

glBegin(GL_TRIANGLES);
 glNormal3f(0.0f, 1.0f, 0.0f);
 glVertex3f(-3.0f, 0.0f, 2.0f);
 glVertex3f(2.0f, 0.0f, 0.0f);
 glVertex3f(-1.0f, 0.0f, 3.0f);
glEnd();

Normais unitárias

Para que o OpenGL faça seu trabalho, é necessário que todas as normais de luz sejam convertidas para normais unitárias. Uma normal unitária é um vetor normal de tamanho um. Se você já leu o artigo sugerido no inicio desse tópico você já sabe o porque: é muito mais fácil fazer cálculos de ângulos com vetores unitários.

A operação que transforma um vetor em unitário é chamado de normalização.É possível dizer para o OpenGL fazer essa operação em todas as normais, usando o comando glEnable(GL_NORMALIZE). Entretanto, essa abordagem impõe um prejuízo grave a performance: ela faz com que o OpenGL calcule as normais sempre que o desenho for feito, o que normalmente não é necessário. É muito melhor você mesmo calcular as normais uma única vez, na hora de calcular a posição dos vértices de seu polígono.

Entretanto, chamadas a função glScale também aplicarão escalas a sua normal. Se você usa essa função, então definitivamente você deve usar o GL_NORMALIZE, ou então, você pode acabar obtendo resultados indesejáveis.

Agora, se você criou sua geometria com as normais unitárias, mas está aplicando uma escala exatamente do mesmo tamanho em todo lugar, existe uma alternativa muitíssimo mais barata no OpenGL a partir da versão 1.2. É substituir o GL_NORMALIZE no glEnable por GL_RESCALE_NORMALS. Isso diz ao OpenGL que suas normais não tem um tamanho unitário, mas podem ser submetida a mesma função de escala para se tornarem unitárias. Isso permite bem menos cálculos, já que a função pode ser facilmente obtida através da matriz ModelView.

Isso mostra que de um jeito ou de outro, é melhor que você mesmo calcule as normais unitárias sempre. Por isso, no artigo sugerido no início, também inclui as classes de vetores 2D e 3D tanto para Java quanto para C++ (as classes também estão no exemplo).

As normais de uma figura plana

Nem sempre é fácil adivinhar quais são as normais de uma face, especialmente quando ela não é exatamente alinhada com um plano formado por dois eixos coordenados. Também poderiamos querer criar uma função genética que, dada uma face, obtivesse a normal dessa face.

É possível calcular facilmente a normal de um polígono obtendo apenas 3 pontos que estão no plano desse polígono. A figura abaixo mostra um polígono de onde pode-se obter 2 vetores, um deles do ponto p1 até p2, e outro de p1 até p3. Matematicamente, 2 vetores num espaço tridimensional definem um plano e o seu polígono original está nesse plano.

Se você retirar o produto vetorial (cross product) desses dois vetores, obterá um vetor que é perpendicular a esse plano. Basta agora normaliza-lo e pronto!

No caso desse polígono simples, as normais estariam apontando para fora do monitor, como se ela estivesse de pé sobre a tela. Outra possibilidade (multiplicando-se os vetores na ordem contrária) é obter uma normal apontando para o fundo do monitor, na direção inversa.

As normais de uma figura arredondada

Se tentarmos usar a técnica acima numa figura com várias faces, representando um círculo, obteremos uma figura que parece uma jóia. Isso porque a técnica sempre obtém imagens de aparência facetada, como se a figura tivesse sido entalhada com cortes retos. A técnica acima obtém o que chamamos de “normais poligonais”.

A alternativa a isso é fornecer normais que dariam a direção que a luz refletiria naquele vértice se ele fosse uma figura realmente arredondada. São as chamadas “normais reais”. Note que o termo “real” refere-se a forma como a vemos no mundo real, não no modelo poligonal usado para representa-la.

A figura abaixo, mostra exatamente as mesmas figuras, desenhadas com normais poligonais (esquerda) e normais reais (direita):

Vemos assim que podemos “enganar” o espectador e dar uma aparência arredondada à figura simplesmente mexendo em suas normais.

Uma técnica simples e genérica para obter esse efeito é pegar todas as faces em que o vértice está presente, tirar a média de suas normais (definidas usando o método poligonal) e aplicar esse resultado como a normal. Assim, cada vértice terá uma normal diferente, que aponta na direção contrária de sua face, para “fora” da forma geométrica (como se partisse do centro em direção ao vértice). Quando desenhado, isso dá o efeito de que a figura é arredondada.

Para figuras calculadas através de fórmulas matemáticas, como as esferas, é possível obter uma normal ainda melhor, que realmente represente o vetor que parte de um centro até o vértice em questão.

Mas o método acima dá uma boa aproximação para qualquer figura genérica, tal qual a imagem ao lado.

Exemplo

Hora de voltar ao nosso bom e velho cubo de exemplo e dar uma mostra de como todos esses conceitos complexos podem ser usados juntos.

Tanto as laterais do cubo quanto as normais de luz são desenhadas usando a classe Vector3D. Para o cálculo das normais é usado o método de normais para figura plana, descrito acima. É usada uma luz posicional, para que o efeito fique mais nítido.

Ainda é possível girar o cubo usando as setas. Adicionalmente, é possível usar as letras Q e A para mudar o componente vermelho da luz, W e S para o verde e E e D para o azul. Ou então, pressione L para ligar e delisgar a luz, isso deixará a figura iluminada apenas por uma fraca luz ambiente branca.

O local da luz, bem como sua cor, é exibido por uma pequena esfera.

Baixe o exemplo clicando aqui.

Em resumo

Neste artigo, vimos como definir as normais de luz e o quão importante elas são para um bom efeito na cena. Vimos também que para uma normal funcionar bem, é necessário que ela seja unitária e aprendemos alguns métodos prontos do OpenGL para garantir que as normais atinjam essa condição. Também vimos alguns métodos matemáticos simples para o cálculo de normais.

No próximo artigo, veremos outros detalhes das luzes, como a luz spotlight e atenuação.