Parte II - Sombreamento/Shading
Como já 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.
2. Atualizem suas classes Mesh adicionando o seguinte:
Em mesh.h antes da declaração da classe:
struct Normal // define o vetor normal
{
float x,y,z;
};
struct Neighbors // define uma lista de triangulos vizinhos de um triangulo
{
Neighbors(){tri=0;nbr=0;}
~Neighbors(){delete [] tri;}
void addNeighbor(int triangle) // adiciona um vizinho na lista
{
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; // lista de vizinhos
int nbr; // tamanho da lista
};
A lista de vizinhos serve para acelerar
o cálculo das normais dos vértices, serve para evitar que
a lista de triangulos seja percorrida n*n vezes (sendo n o
número de triângulos) a cada vez que se deseja recalcular
as normais da malha.
Em mesh.h dentro do corpo da classe Mesh:
// declaracao das novas funcoes e variaveis necessarias ao calculo das normais
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:
// definicao das novas funcoes necessarias ao calculo das normais
inline float Mesh::invSqrt (float x) // raiz inversa para normalizacao
{
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) // dados 3 vertices, calcula a normal do triangulo formado por eles
{
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) // normaliza qualquer vetor n dado
{
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() //carrega a estrutura de dados de vizinhanca da malha
{
for (int t=0;t<nbrTris;t++)
{
for (int v=0;v<3;v++)
{
vertNeigh[tris[t].vert[v]].addNeighbor(t);
}
}
}
void Mesh::calcNormals() // calcula todas as normais da malha, carregando a estrutura de dados
{
// 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();
}
}
// habilita o vetor de normais para o rendering
glEnableClientState(GL_NORMAL_ARRAY);
smooth=false;
if (smooth)
{
glShadeModel(GL_SMOOTH);
}
else
{
glShadeModel(GL_FLAT); // Enables Smooth Color Shading (default)
}
// cria as estruturas com o tamanho certo
dolphin.initVerts(NUM_VERTICES); //NUM_VERTICES é uma constante definida em dolphin.h
dolphin.initTris(NUM_POLYGONS); //NUM_POLYGONS é uma constante definida em dolphin.h
// Inicializa os vertices
for (int v=0;v<dolphin.nbrVerts;v++)
{
dolphin.verts[v].x=mesh_vertices[0+v*3]; //mesh_vertices é um array definido em dolphin.h
dolphin.verts[v].y=mesh_vertices[1+v*3];
dolphin.verts[v].z=mesh_vertices[2+v*3];
}
// Inicializa os triangulos
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]; //mesh_polygons é um array definido em dolphin.h
}
}
// Agora podemos calcular as normais
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 resultado deve ser algo parecido com isso
(Esquerda: flat; Direita: smooth).