Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links

Flat Shading usando OpenGL

En el ejemplo anterior, la geometria del Cubo es decir los vertices, caras y normales estaban en el codigo fuente. Las normales ya estaban precalculadas algo facil de hacerlo en un cubo, ya que tienen la direccion de los ejes de cordenada pero en objetos de geometria arbitraria esto no es tan facil, recuerden que las normales me indican la cantidad de luz que recibe cada uno de los poligonos, por lo tanto cada poligono tiene un vector normal asociado.

En este ejemplo, la informacion de los vertices y poligonos que forman el objeto se encuentran en un archivo de texto, el cual es leido por el programa y los vectores normales de cada uno de los poligonos es calculado a partir de los vertices que lo forman, los poligonos estan compuestos por 3 vertices en este ejemplo.

Vectores Normales a una Cara

A partir de los vértices V1, V2, V3 creo dos vectores P, Q que tras su producto vectorial me generan el vector normal, una vez calculada la normal tenemos que normalizar, es decir, dividir ese vector por su propio módulo para que sea unitario, de esta forma tenemos un vector normal de módulo igual a la unidad.

Vertices del poligono

(V1, V2, V3)

V1 = (x1,y1,z1)
V2 = (x2,y2,z2)
V3 = (x3,y3,z3)
V4 = (x4,y4,z4)
P  = (Px,Py,Pz)
Q  = (Qx,Qy,Qz)

Vector Normal

N  = (Nx,Ny,Nz)

Vertices de los poligonos V1, V2, V3, V4.

V1 = (x1,y1,z1)
V2 = (x2,y2,z2)
V3 = (x3,y3,z3)
V4 = (x4,y4,z4)

Obtengo los vectores P y Q, apartir de los vertices V1, V2, V3. diferencia de vectores.

P = V2-V1 = (x2,y2,z2)-(x1,y1,z1) = (x2-x1, y2-y1, z2-z1)
Px = x2-x1
Py = y2-y1
Pz = z2-z1

Q = V3-V1 = (x3,y3,z3)-(x1,y1,z1) = (x3-x1, y3-y1, z3-z1)
Qx = x3-x1
Qy = y3-y1
Qz = z3-z1

Realizo el producto vectorial de los vectores P y Q, me da como resultado un vector N, que es perpendicular al plano que forman los vectores P y Q.

Nx = Py*Qz - Pz*Qy
Ny = Pz*Qx - Px*Qz
Nz = Px*Qy - Py*Qx

Modulo del vector N

Modulo = Sqrt(Nx*Nx + Ny*Ny + Nz*Nz)

Normalizo el vector N, es decir va a tener modulo 1, este es el vector que utilizo para determinar el nivel de luz que recibe el poligono.

Nx = Nx/Modulo
Ny = Ny/Modulo
Nz = Nz/Modulo

La implementacion de estas rutinas en C, es la siguiente:

GLfloat Modulo(GLfloat x, GLfloat y, GLfloat z)
{
 GLfloat len;

 len = x*x + y*y + z*z;
 return (sqrt(len));
}

GLvoid Normaliza(GLfloat *x, GLfloat *y, GLfloat *z)
{
 GLfloat len;

 len = Modulo(*x, *y, *z);
 len = 1.0/len;
 (*x) *= len;
 (*y) *= len;
 (*z) *= len;
}

GLvoid ProductoVectorial(GLfloat V1[], GLfloat V2[], GLfloat V3[],
                         GLfloat *NormalX, 
                         GLfloat *NormalY, 
                         GLfloat *NormalZ)
{
 GLfloat Qx, Qy, Qz, Px, Py, Pz;

 Px = V2[0]-V1[0];
 Py = V2[1]-V1[1];
 Pz = V2[2]-V1[2];
 Qx = V3[0]-V1[0];
 Qy = V3[1]-V1[1];
 Qz = V3[2]-V1[2];
 *NormalX = Py*Qz - Pz*Qy;
 *NormalY = Pz*Qx - Px*Qz;
 *NormalZ = Px*Qy - Py*Qx;
}

OpenGL utiliza el siguiente comando para dibujar los poligonos, utilizando la informacion del vector normal.

glBegin(GL_TRIANGLES);  //le indico que son poligonos con 3 vertices
 for (i=0; i<NumPoligonos; i++)
        {
        glNormal3fv(Tri[i].N);  //este es el vector normal que calcule
        glVertex3fv(Tri[i].V1); //primer vertice
        glVertex3fv(Tri[i].V2); //segundo vertice
        glVertex3fv(Tri[i].V3); //tercer vertice
        }
glEnd();

Codigo Fuente

La esfera esfera.txt esta compuesta por 266 vertices y 528 poligonos, y la cabeza head.txt por 2566 vertices y 4972 poligonos.

Para cargar y visualizar la esfera las modificaciones en el codigo son:

#define MaxVertices     266
#define MaxPoligonos    528

void init(void)
{
...
GargarPoligonos("esfera.txt");
...
}

void display(void)
{
...
gluLookAt(1.75, 1.75, 1.75, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
...
}

y para la cabeza:

#define MaxVertices     2566
#define MaxPoligonos    4972

void init(void)
{
...
GargarPoligonos("head.txt");
...
}

void display(void)
{
...
gluLookAt(22.0, 20, 22.0, 0.0, 12.5, 0.0, 0.0, 1.0, 0.0);
...
}

En el archivo normales.zip se encuentra todo lo necesario para la compilacion del codigo normales.c, Makefile, glutwin32.mak y esfera.txt, este ultimo es el archivo de texto que contiene los vertices y poligonos que forman el objeto.
Este archivo contiene los datos de la cabeza Head.zip

/* librerias  incluidas */
#include <GL/glut.h>
#include <math.h>
#include <stdio.h>

#define MaxVertices     300
#define MaxPoligonos    600

struct Faces{
        GLfloat V1[3];
        GLfloat V2[3];
        GLfloat V3[3];
        GLfloat N[3];
       };

struct  Faces Tri[MaxPoligonos];
GLfloat Vertice[MaxVertices][3];
GLint   Poli[MaxPoligonos][3];
GLint   NumPoligonos;
GLint   NumVertices;

float LightPos[] = { 1.0f, 0.5f, 1.0f, 0.0f};   // Light Position
float LightAmb[] = { 0.2f, 0.2f, 0.2f, 1.0f};   // Ambient Light Values
float LightDif[] = { 1.0f, 1.0f, 1.0f, 1.0f};   // Diffuse Light Values
float LightSpc[] = { 1.0f, 1.0f, 1.0f, 1.0f};   // Specular Light Values

void GargarPoligonos(char *nombre)
{
 int i;

 FILE *in = fopen(nombre, "r");
 if (!in)
	return;
 fscanf(in, "%d\n", &NumVertices);
 for (i = 0; i<NumVertices; i++)
    fscanf(in, "%f %f %f\n", &Vertice[i][0], &Vertice[i][1], &Vertice[i][2]);
 fscanf(in, "%d\n", &NumPoligonos);
 for (i = 0; i<NumPoligonos; i++)
    fscanf(in, "%d %d %d\n", &Poli[i][0], &Poli[i][1], &Poli[i][2]);
 fclose(in);
}

//Calcula el modulo de un vector  (longitud)
GLfloat Modulo(GLfloat x, GLfloat y, GLfloat z)
{
 GLfloat len;

 len = x*x + y*y + z*z;
 return (sqrt(len));
}

//Normaliza el vector a mudulo 1
GLvoid Normaliza(GLfloat *x, GLfloat *y, GLfloat *z)
{
 GLfloat len;

 len = Modulo(*x, *y, *z);
 len = 1.0/len;
 (*x) *= len;
 (*y) *= len;
 (*z) *= len;
}

//Calcula el producto vectorial de dos vectores
GLvoid CalculateVectorNormal(GLfloat V1[], GLfloat V2[], GLfloat V3[],
                             GLfloat *NormalX, GLfloat *NormalY, GLfloat *NormalZ)
{
 GLfloat Qx, Qy, Qz, Px, Py, Pz;

 Px = V2[0]-V1[0];
 Py = V2[1]-V1[1];
 Pz = V2[2]-V1[2];
 Qx = V3[0]-V1[0];
 Qy = V3[1]-V1[1];
 Qz = V3[2]-V1[2];
 *NormalX = Py*Qz - Pz*Qy;
 *NormalY = Pz*Qx - Px*Qz;
 *NormalZ = Px*Qy - Py*Qx;
}

//Calculo el vector normal a cada uno de los poligonos
void CalcularNormales(void)
{
 GLfloat NormalX, NormalY, NormalZ;
 GLint i,j;

 for (i=0; i<NumPoligonos; i++)
     {
      //Vertice 1
      j=Poli[i][0];
      Tri[i].V1[0] = Vertice[j][0];
      Tri[i].V1[1] = Vertice[j][1];
      Tri[i].V1[2] = Vertice[j][2];
      //Verice 2
      j=Poli[i][1];
      Tri[i].V2[0] = Vertice[j][0];
      Tri[i].V2[1] = Vertice[j][1];
      Tri[i].V2[2] = Vertice[j][2];
      //Vertice 3
      j=Poli[i][2];
      Tri[i].V3[0] = Vertice[j][0];
      Tri[i].V3[1] = Vertice[j][1];
      Tri[i].V3[2] = Vertice[j][2];
      //Calcula el vector Normal
      CalculateVectorNormal(Tri[i].V1, Tri[i].V2, Tri[i].V3, &NormalX, &NormalY, &NormalZ);
      //nos retorna un vector unitario, es decir de modulo 1
      Normaliza(&NormalX, &NormalY, &NormalZ);
      //almacena los vectores normales, para cada poligono
      Tri[i].N[0] = NormalX;
      Tri[i].N[1] = NormalY;
      Tri[i].N[2] = NormalZ;
    };
}

void reshape(int w, int h)
{
 if (!h)
	return;
 glViewport(0, 0,  (GLsizei) w, (GLsizei) h);
 /* Activamos la matriz de proyeccion. */
 glMatrixMode(GL_PROJECTION);
 /* "limpiamos" esta con la matriz identidad.*/
 glLoadIdentity();
 /* Campo de vision   45.0
  * radio de aspecto   1.0
  * Z near   1.0
  * Z far   10.0
  */
 gluPerspective( 45.0, 1.0, 1.0,  20.0);
 /* Activamos la matriz de modelado/visionado. */
 glMatrixMode(GL_MODELVIEW);
 /* "Limpiamos" la matriz  */
 glLoadIdentity();
}

void display(void)
{
 /* Propiedades del material del Objeto*/
 GLfloat mat_ambient[] = { 0.04f, 0.28f, 0.36f, 1.0f };
 GLfloat mat_diffuse[] = { 0.05f, 0.5f, 0.9f, 1.0f };
 GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 GLfloat mat_shininess[] = { 128.0f };
 int i;

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glPushMatrix();
 //Coordenadas del "ojo", es la posicion donde se encuentra la camara  1.75,  1.75,  1.75
 //lugar a donde se mira 0, 0, 0
 gluLookAt(1.75, 1.75, 1.75, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
 //Dibujo el objeto
 glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
 glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
 glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
 glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

 glBegin(GL_TRIANGLES);
 for (i=0; i<NumPoligonos; i++)
        {
        glNormal3fv(Tri[i].N);
        glVertex3fv(Tri[i].V1);
        glVertex3fv(Tri[i].V2);
        glVertex3fv(Tri[i].V3);
        }
 glEnd();
 glPopMatrix();
 glFlush();
}

void init(void)
{
 glClearColor(0.0, 0.0, 0.0, 0.0);
 glShadeModel(GL_SMOOTH);
 glCullFace(GL_BACK);
 glEnable(GL_DEPTH_TEST);
 glEnable(GL_CULL_FACE);

 /* Activo la fuente de luz */
 glLightfv(GL_LIGHT0, GL_POSITION, LightPos);        // Set Light1 Position
 glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);         // Set Light1 Ambience
 glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);         // Set Light1 Diffuse
 glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpc);        // Set Light1 Specular
 glEnable(GL_LIGHT0);                                // Enable Light1
 glEnable(GL_LIGHTING);
 /* Uso depth buffering para la eliminacion de superficies ocultas */
 glEnable(GL_DEPTH_TEST);
 GargarPoligonos("esfera.txt");
 CalcularNormales();
}

 /* Termina la ejecucion del programa cuando se presiona ESC */
void keyboard(unsigned char key, int x, int y)
{
 switch (key)
   {
   case 27: exit(0);
             break;
   }
}

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitWindowSize(300, 300);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
  glutCreateWindow("Normales");
  init();
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  /* ANSI C requiere que main retorne un valor entero. */
  return 0;
 }


valcoey@hotmail.com

Ramiro Alcocer, 2001

Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links