Capítulo 8. Superfícies no espaço

Índice
8.1. Descrição do programa splines3d.c
8.2. Exercícios

O propósito desta lição é mostrar como gerar no espaço as superfícies paramétricas de Bézier e NURBS utilizando OpenGL. De modo semelhante às curvas descritas no Capítulo 7, a forma destas superfícies é controlada pelo posicionamento de vértices, desta vez componentes de um malha característica. Com a resolução dos exercícios propostos no final da lição, o aluno poderá notar a utilidade das superfícies apresentadas, associando-as a uma situação prática, através da modelagem interativa de uma superfície.

Será mostrado também como introduzir iluminação na cena, de modo a tornar a apresentação da superfície mais realística.

A superfície gerada pelo programa utilizado nesta lição é mostrado na Figura 8-1.

Figura 8-1. Traçado de superfícies.

O programa usado para modelar as splines é mostrado no Exemplo 8-1. As teclas b e n definem o tipo de curva que será desenhada: Bézier ou NURBS, respectivamente. As teclas x e X, rotacionam a superfície em torno do eixo x. As teclas y e Y, rotacionam a superfície em torno do eixo y. As teclas z e Z, rotacionam a superfície em torno do eixo z.

Exemplo 8-1. programa splines3d.c

#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

GLint eixox, eixoy, eixoz;

GLint nVertices=4;
GLfloat vertices[4][4][3];

GLint largura, altura;

enum {BEZIER, NURBS};
GLint spline;
GLUnurbsObj *nc;
GLfloat nos[8]={0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
GLint nNos=8;

void gera_superficie(void){
  int i,j;
  for(i=0; i<4; i++){
    for(j=0; j<4; j++){
      vertices[i][j][0] = 2.0*((GLfloat)i - 1.5);
      vertices[i][j][1] = 2.0*((GLfloat)j - 1.5);
      
      if ( (i == 1 || i == 2) && (j == 1 || j == 2))
	vertices[i][j][2] = 7.0;
      else
	vertices[i][j][2] = -3.0;
    }
  }
}
void display(void){
  int i,j;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glPushMatrix();
  glRotatef(eixox, 1 ,0 ,0);
  glRotatef(eixoy, 0 ,1 ,0);
  glRotatef(eixoz, 0 ,0 ,1);
  glScalef(0.25, 0.25, 0.25);

  glDisable(GL_LIGHTING);
  glPushMatrix();
  glTranslatef(-5,-5,-5);
  glColor3f(1,1,1);
  glBegin(GL_LINES);
  glVertex3f(0,0,0);  glVertex3f(1,0,0);
  glEnd();
  glRasterPos3f(1.5,0,0);
  glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, 'x');

  glBegin(GL_LINES);
  glVertex3f(0,0,0);  glVertex3f(0,1,0);
  glEnd();
  glRasterPos3f(0,1.5,0);
  glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, 'y');

  glBegin(GL_LINES);
  glVertex3f(0,0,0);  glVertex3f(0,0,1);
  glEnd();
  glRasterPos3f(0,0,1.5);
  glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, 'z');

  glPopMatrix();
  glEnable(GL_LIGHTING);

  switch(spline){
  case BEZIER:
    glMap2f(GL_MAP2_VERTEX_3, 0.0, 1.0, 3, 4, 0, 1, 3*nVertices, 4, &vertices[0][0][0]);
    glEnable(GL_AUTO_NORMAL);
    glMapGrid2f(20, 0, 1.0, 20, 0, 1.0);
    glEvalMesh2(GL_FILL, 0, 20, 0, 20);
    break;
  case NURBS:
    gluBeginSurface(nc);
    gluNurbsSurface(nc, nNos, nos, nNos, nos, 4*3, 3, &vertices[0][0][0], 4, 4, GL_MAP2_VERTEX_3);
    gluEndSurface(nc);
    break;
  }
  glPointSize(5.0);
  glColor3f(1.0, 0.0, 0.0);
  glDisable(GL_LIGHTING);
  glBegin(GL_POINTS);
  for(i=0; i<4; i++){
    for(j=0; j<4; j++){
      glVertex3fv(&vertices[i][j][0]);
    }
  }
  glEnable(GL_LIGHTING);
  glEnd();
  
  glPopMatrix();
  glFlush();

  glutSwapBuffers();
}

void init(void){
  GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
  GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat mat_shininess[] = { 100.0 };
  
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
  glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
  
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  
  gera_superficie();
  
  nc= gluNewNurbsRenderer();
  gluNurbsProperty(nc, GLU_SAMPLING_TOLERANCE, 5.0);
  gluNurbsProperty(nc, GLU_DISPLAY_MODE, GLU_FILL);
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef (0.0, 0.0, -5.0);

  spline=NURBS;
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_MAP2_VERTEX_3);
  display();
}

void reshape(int w, int h){
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0);
  glMatrixMode(GL_MODELVIEW);
}

/* ARGSUSED1 */
void keyboard(unsigned char key, int x, int y){
  switch (key) {
  case 'x':
    eixox = (eixox + 5) % 360;
    glutPostRedisplay();
    break;
  case 'y':
    eixoy = (eixoy + 5) % 360;
    glutPostRedisplay();
    break;
  case 'z':
    eixoz = (eixoz + 5) % 360;
    glutPostRedisplay();
    break;
  case 'X':
    eixox = (eixox - 5) % 360;
    glutPostRedisplay();
    break;
  case 'Y':
    eixoy = (eixoy - 5) % 360;
    glutPostRedisplay();
    break;
  case 'Z':
    eixoz = (eixoz - 5) % 360;
    glutPostRedisplay();
    break;
  case 'b':
    spline = BEZIER;
    glutPostRedisplay();
    break;
  case 'n':
    spline = NURBS;
    glutPostRedisplay();
    break;
  case 27:
    exit(0);
    break;
  }
}
int main(int argc, char** argv){
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH| GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize(500, 500);
  glutInitWindowPosition(100, 100);
  glutCreateWindow(argv[0]);
  init();
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}

Para compilar e executar o programa splines3d.c, salve-o juntamente com o arquivo Makefile em um diretório e execute a seguinte seqüência de comandos:

$ make splines3d
$ splines3d

8.1. Descrição do programa splines3d.c

GLint nVertices=4;
GLfloat vertices[4][4][3];

Define o número de vértices da malha característica (variável nVertices) e as posições no espaço dos seus respectivos vértices (matriz vertices).

GLint largura, altura;

As variáveis largura e altura armazenam a largura e a altura da janela corrente, em pixels.

GLUnurbsObj *nc;
GLfloat nos[8]={0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
GLint nNos=8;

A variável nc contém a referência para a spline que irá modelar a superfície tipo NURBS. nos e nNos são o vetor de nós e o número de nós neste vetor, respectivamente.

void gera_superficie(void);

Cria as coordenadas x, y e z dos pontos da malha de controle.

  glBegin(GL_LINES);
  glVertex3f(0,0,0);  glVertex3f(1,0,0);
  glEnd();
  glRasterPos3f(1.5,0,0);
  glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, 'x');

Cria um segmento de reta representando o eixo x. A função glRasterPos3f() define a posição do rasterizador para operações com pixels (desenho de bitmaps). Neste caso, o rasterizador é posicionado na ponta do eixo x. Em seguida, a função glutBitmapCharacter() é utilizada para desenhar um mapa de bits do caracter 'x' com a fonte especificada. Outras duas operações semelhantes são realizadas para os eixos y e z.

    glMap2f(GL_MAP2_VERTEX_3, 0.0, 1.0, 3, 4, 0, 1, 3*nVertices, 4, &vertices[0][0][0]);

Caso a superfície seja do tipo Bézier, o seu traçado também se com o uso de avaliadores (semelhante ao caso bidimensional). A função glMap2f() define um avaliador bididimesional.

A função glMap2f() possui o seguinte protótipo:

void glMap1f(GLenum target, GLfloatu1, GLfloatu2, GLint ustride, GLint uorder, GLfloatv1, GLfloatv2, GLint vstride, GLint vorder, const GLfloat *points);

O parâmetro target especifica o tipo de valores gerados pelo evaluator; neste caso, especifica que cada ponto de controle é usado para mapear três valores x, y e z. Comandos internos glVertex*() são gerados quando este mapeamento é realizado. u1 e u2 especificam a faixa de mapeamento linear para u (direção x), ou seja, a faixa de variação do parâmetro que define a curva; neste caso [0,1]. ustride especifica o número de elementos entre o início de um ponto de controle e o início do ponto de controle seguinte; como cada ponto de controle possui três coordenadas, ustride=3 para este exemplo. O parâmetro order especifica o número de pontos de controle para a curva (6 pontos). Estes quatro parâmetros repetem-se para o mapeamento de 'v', relativo à direção y da superfície. Finalmente, o parâmetro *points deve conter a posição do primeiro ponto da malha de controle.

    glEnable(GL_AUTO_NORMAL);
    glMapGrid2f(20, 0, 1.0, 20, 0, 1.0);
    glEvalMesh2(GL_FILL, 0, 20, 0, 20);

As funções glMapGrid2f() e glEvalMesh2() são utilizadas em conjunto para gerar uma série valores de 'u' e 'v' e avaliar as coordenadas x, y e z do pontos da superfície, para cada um dos pares de parâmetros (u,v). Foi definido 20 subdivisões para as faixas de cada parâmetro, [0,1].

Na função glEvalMesh2() foi especificado que a superfície deveria ser preenchida (GL_FILL), mas poderia também ser traçada utilizando linhas (GL_LINES) ou pontos (GL_POINTS). Os parâmetros seguintes passados para essa função indicam os primeiros e últimos valores inteiros utilizados para as posições do grid de desenho.

Foi habilitado, através do parâmetro GL_AUTO_NORMAL, o cálculo automático dos vetores normais aos pontos da superfície, utilizados no processo de iluminação da cena.

    gluBeginSurface(nc);
    gluNurbsSurface(nc, nNos, nos, nNos, nos, 4*3, 3, &vertices[0][0][0], 4, 4, GL_MAP2_VERTEX_3);
    gluEndSurface(nc);

Se a curva a ser desenhada for do tipo NURBS, então as funções gluBeginSurface() e gluEndSurface() serão utilizadas para demarcar o início e o fim do seu traçado. A função usada para desenhar esta classe de superfícies é gluNurbsSurface, cujos parâmetros são semelhantes aos da função gluNurbsCurve(), acrescentando o vetor de nós e o número de nós na direção y (nos e nNos), os deslocamentos entre sucessivos pontos de controle (stride) nas duas direções (4*3 e 3), e as ordens da superfície em ambas as direções (4 e 4). A variável nc é iniciada na função init().

Neste exemplo o valor do deslocamento na direção x é 12 (4*3) porque existem três coordenadas para cada vértice (GL_MAP2_VERTEX_3) e quatro pontos de controle na direção v. O valor do deslocamento na direção v é 3 porque cada vértice tem três coordenadas, e os pontos de controle v são adjacentes uns aos outros na memória.

  GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
  GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat mat_shininess[] = { 100.0 };
  
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
  glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

Define as características do material que serão utilizadas pelo modelo de iluminação, através da função glMaterialfv(). Por enquanto estes recursos são apenas ilustrativos, pois serão abordados em uma lição posterior.

  glShadeModel(GL_SMOOTH);
  glEnable(GL_MAP2_VERTEX_3);

A função glShadeModel() define o modelo de sombreamento usado na superfície. O parâmetro GL_SMOOTH torna a superfície suave, mais realística. Da mesma forma que a função glMaterialfv(), a presença desta também é ilustrativa, pois também será abordada em uma lição posterior.

Se o parâmetro GL_MAP2_VERTEX_3 for hatilitado, chamadas às funções glEvalCoord2(), glEvalMesh2 e glEvalPoint2 irão gerar vértices com coordenadas x, y e z, necessárias ao traçado das superfícies.

  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);

Aqui, a geração automática de vetores normais à superfície é habilitado via GL_AUTO_NORMAL. Após gerados, os vetores normais são escalados de modo a ficarem com módulo igual a 1, melhorando a exibição da superfície.