Computação Gráfica II

Trabalho Prático

Exercícios



3. Sombreamento/Shading

Parte II

Agora que sabemos como visualizar malhas, nesta segunda parte, ao invés de usar um objeto primitivo da GLUT como o teapot, aplicaremos sombreamento sobre uma malha modelada com nossa própria classe Mesh. À primeira vista, vocês podem pensar que fazendo o display com as configurações de shading usadas para o teapot bastaria. Tentem!

1. O que acontece quando se aplica sombreamento a uma malha ordinária.

Isto acontece porque os vetores normais da superfície da malha (triangulos ou vértices) não são conhecidos.
Lembrem-se da parte teória da aula de sombreamento. O professor falou várias vezes que o vetor normal era usado para calcular a intensidade de reflexão da luz.

2. Atualizem suas classes Mesh adicionando o seguinte:

Em mesh.h antes da declaração da classe:

struct Normal
{
float x,y,z;
};

struct Neighbors
{
Neighbors(){tri=0;nbr=0;}
~Neighbors(){delete [] tri;}

void addNeighbor(int triangle)
{
for (int i=0;i<nbr;i++) if (tri[i]==triangle) return;
int *temp=new int[nbr+1];
for (int i=0;i<nbr;i++) temp[i]=tri[i];
temp[nbr]=triangle;
delete [] tri;
tri=temp;
nbr++;
}

int *tri;
int nbr;
};



Em mesh.h dentro do corpo da classe Mesh:

  void  initNeighbors();
void calcNormals();
float invSqrt (float x);
void calcNormal(Vertex& p1,Vertex& p2,Vertex& p3,Normal& n);
void normalize(Normal& n);
Neighbors *vertNeigh; // triangles that are adjacent to a vertex
Normal *vertNorm;
Normal *triNorm;

Em mesh.h depois do corpo da classe:

inline float Mesh::invSqrt (float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x;
i = 0x5f3759df - (i >> 1); // This line hides a LOT of math!
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x); // repeat this statement for a better approximation
return x;
}

inline void Mesh::calcNormal(Vertex& p1,Vertex& p2,Vertex& p3,Normal& n)
{
Vertex v1;
Vertex v2;
v1.x = p2.x-p1.x;
v1.y = p2.y-p1.y;
v1.z = p2.z-p1.z;

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

n.x = v1.y*v2.z - v1.z*v2.y;
n.y = v1.z*v2.x - v1.x*v2.z;
n.z = v1.x*v2.y - v1.y*v2.x;

// normalization step
float l=invSqrt(n.x*n.x+n.y*n.y+n.z*n.z);

// Possibly unnecessary
if (l==0.0) l=1.0;

n.x*=l;
n.y*=l;
n.z*=l;
}

inline
void Mesh::normalize(Normal& n)
{
float length=invSqrt(n.x*n.x+n.y*n.y+n.z*n.z);

// Possibly unnecessary
if(length == 0.0) length = 1.0;

n.x *= length;
n.y *= length;
n.z *= length;
}


Em mesh.cpp modifiquem estas duas funções adicionando as linhas abaixo (especificadas entre "ADD THIS" e "END ADD THIS"):

void Mesh::initVerts(int p_nbrVerts)
{
  nbrVerts=p_nbrVerts;
  verts =new Vertex[nbrVerts];
  // ADD THIS
  vertNorm=new Normal[nbrVerts];
  vertNeigh=new Neighbors[nbrVerts];
  // END ADD THIS
}

void Mesh::initTris(int p_nbrTris)
{
  nbrTris=p_nbrTris;
  tris=new Triangle[nbrTris];
  // ADD THIS
  triNorm=new Normal[nbrTris];
  // END ADD THIS
}

Também em mesh.cpp adicione estas funções:

void Mesh::initNeighbors()
{
  for (int t=0;t<nbrTris;t++)
  {
    for (int v=0;v<3;v++)
    {
      vertNeigh[tris[t].vert[v]].addNeighbor(t);
    }
  }
}

void Mesh::calcNormals()
{
  // First calculate the triangle normals
  for (int t=0;t<nbrTris;t++)
  {
    calcNormal(verts[tris[t].vert[0]],
               verts[tris[t].vert[1]],
               verts[tris[t].vert[2]],
               triNorm[t]);
  }
  // Now calculate the vertex normals
  for (int v=0;v<nbrVerts;v++)
  {
    vertNorm[v].x=vertNorm[v].y=vertNorm[v].z=0;
    for (int t=0;t<vertNeigh[v].nbr;t++)
    {
      vertNorm[v].x+=triNorm[vertNeigh[v].tri[t]].x;
      vertNorm[v].y+=triNorm[vertNeigh[v].tri[t]].y;
      vertNorm[v].z+=triNorm[vertNeigh[v].tri[t]].z;
    }
    normalize(vertNorm[v]);
  }
}

Em main.cpp adicionem isto logo após seus outros includes:

#include "dolphin.h"
// Lighting flag
bool smooth;

Mesh dolphin;

Substituam a função drawMesh atual por esta (agora estamos enviando também as normais para a OpenGL):

void drawMesh(Mesh& m)
{
if (smooth)
{
glVertexPointer(3,GL_FLOAT,0,m.verts);
glNormalPointer(GL_FLOAT,0,m.vertNorm);
glDrawElements (GL_TRIANGLES,m.nbrTris*3,GL_UNSIGNED_INT,m.tris);
}
else
{
glBegin(GL_TRIANGLES);
for (int i=0;i<m.nbrTris;i++)
{
glNormal3f(m.triNorm[i].x,m.triNorm[i].y,m.triNorm[i].z);
for (int v=0;v<3;v++)
{
glVertex3f(m.verts[m.tris[i].vert[v]].x,
m.verts[m.tris[i].vert[v]].y,
m.verts[m.tris[i].vert[v]].z);
}
}
glEnd();
}
}
Em main.cpp adicionem isto no final das suas funções init():

glEnableClientState(GL_NORMAL_ARRAY);

smooth=false;

if (smooth)
{
glShadeModel(GL_SMOOTH);
}
else
{
glShadeModel(GL_FLAT); // Enables Smooth Color Shading (default)
}

// don't use the color array command any more
dolphin.initVerts(dolph_nbrVert);
dolphin.initTris(dolph_nbrTri);
// Initialize the vertices
for (int v=0;v<dolphin.nbrVerts;v++)
{
dolphin.verts[v].x=dolph_vert[0+v*3];
dolphin.verts[v].y=dolph_vert[1+v*3];
dolphin.verts[v].z=dolph_vert[2+v*3];
}
for (int t=0;t<dolphin.nbrTris;t++)
{
for (int v=0;v<3;v++)
{
dolphin.tris[t].vert[v]=dolph_tri[v+t*3];
}
}
// Now we can calculate the normals
dolphin.initNeighbors();
dolphin.calcNormals();

Finalmente, não esqueçam de chamar drawMesh(dolphin) na sua função display().

Pra testar, mudem o valor da variável bool smooth para trocar entre sombreamento flat e smooth (Gouraud). Podem usar o teclado ou mouse para fazer isso enquanto o programa está executando. O resultad deve ser algo parecido com isso (Esquerda: flat; Direita: smooth).