Lição 3.4: Iluminação e Vetores Normais: Versão GLUT

Muito do código deste tutorial foi escrito originalmente por Jeff Molofee e Fredric Echols e é  uma tradução do OpenGL Tutorial de Jeff Molofee & Neon Helium. Jeff o reescreveu totalmente em Janeiro de 2000. Durante a tradução ao português, a formatação original, com muitas tabelas e figuras mostrando bordas de tabelas para o texto explicativo, foi modificada de forma a facilitar a carga das páginas e a torná-las mais leves.

Muitas partes do código foram adaptadas para utilizarem a API GLUT. Para isso foi muitas vezes utilizado material proveniente do Livro OpenGL SuperBible, que foi programado por Michael Sweet.


O objetivo geral desta lição é ensiná-lo a determinar vetores normais a facetas de objetos, para permitir uma iluminação realista.



Objetivos e Teoria de Iluminação e Reflexos

O objetivo desta lição é ensiná-lo a calcular os efeitos de iluminação causados por ângulos entre a luz incidente sobre um objeto e o que você enxerga.

Quando aluz incide sobre um objeto, como foi visto na aula sobre cor, há dois tipos de luz refletida: a luz reflexa difusa, que dá a cor do objeto e a luz especular, que é refletida na integridade de seu espectro e que dá o "brilho" de um objeto.

Para ambos os tipos de luz, a intensidade com que ela é refletida depende do ângulo com que a luz inside sobre a superfície refletora e também do ângulo realizado com a superfície pelos raios refletidos par aque atinjam o observador.

Isto pode ser mostrado pela figura 1:


Figura 1: Raios de luz que chegam ao observador e provêm a impressão de cor e intensidade luminosa do objeto.

OpenGL não calcula estes ângulos por default. Isto significa, que se você não prover informação para o cálculo destes ângulos, OpenGL vai gerar uma iluminação simplificada da cena, pouco realista. Para que OpenGL possa calcular os ângulos de reflexão de forma eficiente e gerar intensidades de iluminação (luz difusa) e reflexos (luz especular) realistas, é necessário que você o ajude.

Para isso, você vai prover para cada superfície de seu objeto, um vetor normal a esta superfície. Este vetor facilita o cálculo do reflexo, pois reduz a tarefa de OpenGL a calcular em tempo real os ângulos alfa e beta e a partir daí, a intensidade de luz refletida, o que é bem mais simples e rápido do que fazer tudo em tempo real.


Figura 2: Um vetor normal à superfíce.

Para implementar isto, vamos construir duas rotinas que calculam e normalizam um vetor normal a uma superfície representada por três pontos quaisquer que estejam em cima da mesma.

Este enfoque baseado em três pontos vai facilitar a sua vida também no quesito aplicar esta rotina, já que na nossa reconstrução 3D do modelo de cabeça baseado em pontos, você trabalha sempe com triângulos.

Depois disso vamos mostrar a você onde inserir isto no contexto do programa FacetaArame que você já fez na aula passada.



Estrutura da Rotina para Cálculo da Normal para OpenGL

Podemos dividir o cálculo da normal em duas etapas:

/* ===================================================== */
/* Arquivo normal.c                                      */
/*                                                       */
/* Contem um conjunto de funcoes que implementa:         */
/*                                                       */
/* Calculo de um vetor de comprimento unitario a partir  */
/* de um vetor qualquer:                                 */
/*              void reduzParaUnitario()                 */
/*                                                       */
/* Calculo do vetor normal a um plano definido por 3     */
/* pontos quaisquer. Usado para calcular a normal a uma  */
/* faceta de uma superficie qualquer para podermos tirar */
/* o melhor da iluminacao atraves de reflexos realistas. */
/*              void calculaNormal()                     */
/*                                                       */
/* Fonte:                                                */
/*              OpenGL Super Bible, Capitulo 6           */
/*              Program by Richard S. Wright Jr.         */
/* Adaptacao:                                            */
/*              Aldo von Wangenheim, awangenh@inf.ufsc.br*/
/*              Florianopolis, Ilha de Santa Catarina,   */
/*              Maio/Junho de 2001                       */
/*                                                       */
/* ===================================================== */

#include <math.h>       // Nao esqueca de incluir este por
                         // causa da definicao de sqrt()
#include <gl/glut.h>
#include "triangulos.h"

Vamos iniciar a descrição das rotinas com a função de normalização, que poderia ser copiada de um livro de segundo grau:

void
reduzParaUnitario(      TipoPontos      *vector )
        {
        GLfloat length;

        // Calculate the length of the vector
        length = (GLfloat) sqrt((vector->x * vector->x) +
                                (vector->y * vector->y) +
                                (vector->z * vector->z));

        // Keep the program from blowing up by providing an exceptable
        // value for vectors that may calculated too close to zero.
        if (length == 0.0f)
            length =  1.0f;

        // Dividing each element by the length will result in a
        // unit normal vector.
        vector->x = vector->x / length;
        vector->y = vector->y / length;
        vector->z = vector->z / length;
        }

O cálculo da normal a um plano você também já aprendeu em Álgebra Linear no início de seu curso de informática. Temos aqui uma rotina que se utiliza de nosso modelo de pontos para facilitar a passagem de parâmetros:

/* ===================================================== */
void 
calculaNormal(  TipoPontos      p1,
                TipoPontos      p2,
                TipoPontos      p3,
                TipoPontos      *retorno        )

/* Assume que p1, p2, & p3 estão especificados em        */
/* sentido anti-horario. Se voce passar os parametros em */
/* outra ordem, o vetor vai apontar para o lado errado.  */
/* ===================================================== */
        {
        TipoPontos v1,v2;

        // Calcula dois vetores a partir dos tres pontos.
        v1.x = p1.x - p2.x;
        v1.y = p1.y - p2.y;
        v1.z = p1.z - p2.z;

        v2.x = p2.x - p3.x;
        v2.y = p2.y - p3.y;
        v2.z = p2.z - p3.z;

        // Take the cross product of the two vectors to get
        // the normal vector which will be stored in out
        retorno->x = v1.y * v2.z - v1.z * v2.y;
        retorno->y = v1.z * v2.x - v1.x * v2.z;
        retorno->z = v1.x * v2.y - v1.y * v2.x;

        // Normaliza o vector, reduzindo comprimento para um
        reduzParaUnitario(retorno);
        }




Inserindo a geração de normais em seu código

No laço para-faça, entre glBegin() e glEnd(), onde você plota os triângulos, inclua uma chamada ao cálculo da normal e, a seguir, uma chamada à rotina glNormal3F(), que passa essa normal para OpenGL antes de criar o triângulo. A normal passada por glNormal3F() será neste momento a normal válida para OpenGL e vai ser asociada a todos os objetos que forem criado. Como você calcula uma normal para cada faceta, a cada iteração, cada uma das faces do objeto vai ser associada com a sua própria normal.

Assim você consegui um efeito como o mostrado aqui. Observe os rflexos decorrentes da luz especular que havíamos definido.

O código do laço fica então como abaixo:
 

      for (trianguloAtual=1; trianguloAtual <= numTriangulos; trianguloAtual++)
        {
          /* (NOVO) Calcula a normal aos Pontos que vao definir a proxima faceta */
          calculaNormal( meusPontos[meusTriangulos[trianguloAtual].v3],
                         meusPontos[meusTriangulos[trianguloAtual].v2],
                         meusPontos[meusTriangulos[trianguloAtual].v1],
                         &normal );
                                 

          /* (NOVO) Vou setar normal... */
          glNormal3f(normal.x, normal.y, normal.z);

          glVertex3f(     meusPontos[meusTriangulos[trianguloAtual].v3].x, 
                          meusPontos[meusTriangulos[trianguloAtual].v3].y, 
                          meusPontos[meusTriangulos[trianguloAtual].v3].z);
          glVertex3f(     meusPontos[meusTriangulos[trianguloAtual].v2].x, 
                          meusPontos[meusTriangulos[trianguloAtual].v2].y, 
                          meusPontos[meusTriangulos[trianguloAtual].v2].z);                                       
          glVertex3f(     meusPontos[meusTriangulos[trianguloAtual].v1].x, 
                          meusPontos[meusTriangulos[trianguloAtual].v1].y, 
                          meusPontos[meusTriangulos[trianguloAtual].v1].z);       
       }


Links para Código Fonte

Aldo von Wangenheim (UFSC)