web-dev-qa-db-fra.com

Calcul des normales dans un maillage triangulaire

J'ai dessiné un triangle maillé avec 10000 sommets (100x100) et ce sera un terrain gazonné. J'ai utilisé gldrawelements () pour cela. J'ai regardé toute la journée et je ne comprends toujours pas comment calculer les normales pour cela. Chaque sommet a-t-il ses propres normales ou chaque triangle a-t-il ses propres normales? Quelqu'un peut-il m'orienter dans la bonne direction sur la façon de modifier mon code pour incorporer des normales?

struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[60000];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=0;
            vertices[count].z=z;
            count++;
        }
    }
    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glEnableClientState(GL_VERTEX_ARRAY);
    glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glPopMatrix();
}

EDIT 1 Voici le code que j'ai écrit. J'ai juste utilisé des tableaux au lieu de vecteurs et j'ai stocké toutes les normales dans la structure appelée normales. Cela ne fonctionne toujours pas cependant. Je reçois une exception non gérée aux indices *.

struct Normals {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}normals[20000];
Normals* normal = normals;
//***************************************ENVIRONMENT*************************************************************************
struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[59403];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=Rand()%2-2;;
            vertices[count].z=z;
            count++;
        }
    }
    //calculate normals 
    GLfloat vector1[3];//XYZ
    GLfloat vector2[3];//XYZ
    count=0;
    for (int x=0;x<9900;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }
    count=10000;
    for (int x=100;x<10000;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }

    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer( GL_FLOAT, 0, normal);
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glPopMatrix();
}
//***************************************************************************************************************************
43
Vince

Chaque sommet a-t-il ses propres normales ou chaque triangle a-t-il ses propres normales?

Comme souvent, la réponse est: "Cela dépend". Puisqu'un normal est défini comme étant le vecteur perpendiculaire à tous les vecteurs dans un plan donné (en N dimensions), vous avez besoin d'un plan pour calculer une normale. Une position de sommet n'est qu'un point et donc singulière, vous avez donc réellement besoin d'un visage pour calculer la normale. Ainsi, naïvement, on pourrait supposer que les normales sont par face car la première étape du calcul normal consiste à déterminer les normales de face, en évaluant le produit croisé de la fait face aux bords.

Supposons que vous ayez un triangle avec des points A , B , C , alors ces points ont des vecteurs de position ↑ A, ↑ B, ↑ C et les arêtes ont des vecteurs ↑ B - ↑ A et ↑ C - ↑ A donc le vecteur normal de face est ↑ Nf = (↑ B - ↑ A) × (↑ C - ↑ A)

Notez que l'amplitude de ↑ Nf comme indiqué ci-dessus est directement proportionnel à la zone du visage.

Dans les surfaces lisses, les sommets sont partagés entre les faces (ou vous pourriez dire que ces faces partagent un sommet). Dans ce cas, la normale au sommet n'est pas l'une des normales des faces dont elle fait partie, mais une combinaison linéaire d'entre elles:

↑ Nv = ∑ p ↑ Nf; où p est une pondération pour chaque visage.

On pourrait soit supposer une pondération égale entre les normales de visage participantes. Mais il est plus logique de supposer que plus un visage est grand, plus il contribue à la normale.

Rappelez-vous maintenant que vous normalisez par un vecteur ↑ v en le mettant à l'échelle avec sa longueur récipocale: ↑ vje = ↑ v/| ↑ v. Mais comme déjà dit, la longueur des normales du visage dépend déjà de la zone du visage. Ainsi, le facteur de pondération p donné ci-dessus est déjà contenu dans le vecteur lui-même: sa longueur, c'est-à-dire sa magnitude. Nous pouvons donc obtenir le vecteur normal du sommet en additionnant simplement toutes les normales de face.

Dans les calculs d'éclairage, le vecteur normal doit être une longueur unitaire, c'est-à-dire normalisé pour être utilisable. Donc, après avoir résumé, nous normalisons le sommet nouvellement trouvé normal et nous l'utilisons.

Le lecteur attentif a peut-être remarqué que j'ai spécifiquement dit que les surfaces lisses partagent des sommets. Et en fait, si vous avez des plis/bords durs dans votre géométrie, les faces de chaque côté ne partagent pas de sommets. En OpenGL, un sommet est la combinaison complète de

  • position
  • ordinaire
  • (Couleur)
  • Coordonnées de texture N
  • M autres attributs

Vous changez l'un d'entre eux et vous obtenez un sommet complètement différent. Maintenant, certains modélisateurs 3D voient un sommet uniquement comme la position d'un point et stockent le reste de ces attributs par face (Blender est un tel modélisateur). Cela économise de la mémoire (ou une mémoire considérable, selon le nombre d'attributs). Mais OpenGL a besoin de tout cela, donc si vous travaillez avec un tel fichier de paradigme mixte, vous devrez d'abord le décomposer en données compatibles OpenGL. Jetez un œil à l'un des scripts d'exportation de Blender, comme l'exportateur PLY pour voir comment cela se fait.


Maintenant pour couvrir autre chose. Dans votre code, vous avez ceci:

 glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );

Le pointeur d'index a rien à voir avec les indices de tableau de sommets! C'est un anachronisme du temps où les graphiques utilisaient toujours des palettes au lieu de la vraie couleur. Une couleur de pixels n'a pas été définie en donnant ses valeurs RVB, mais par un seul décalage de nombre dans une palette de couleurs limitée. Les couleurs de palette peuvent toujours être trouvées dans plusieurs formats de fichiers graphiques, mais aucun matériel décent ne les utilise plus.

Veuillez effacer glIndexPointer (et glIndex) de votre mémoire et de votre code, ils ne font pas ce que vous pensez qu'ils font Le mode de couleur indexé entier est obscur à utilisé, et franchement, je ne connais aucun matériel construit après 1998 qui le supportait encore.

111
datenwolf

Par sommet.

Utilisez des produits croisés pour calculer les normales de face pour les triangles entourant un sommet donné, additionnez-les et normalisez.

24
genpfault

Bravo pour Datenwolf! Je suis entièrement d'accord avec son approche. Ajouter les vecteurs normaux des triangles adjacents pour chaque sommet puis normaliser est la voie à suivre. Je veux juste pousser la réponse un peu et regarder de plus près le cas particulier mais assez courant d'un maillage rectangulaire, lisse qui a un constant pas x/y. En d'autres termes, une grille rectangulaire x/y avec une hauteur variable à chaque point.

Un tel maillage est créé en bouclant sur x et y et en définissant une valeur pour z et peut représenter des choses comme la surface d'une colline. Donc, chaque point du maillage est représenté par un vecteur

P = (x, y, f(x,y)) 

où f (x, y) est une fonction donnant le z de chaque point de la grille.

Habituellement, pour dessiner un tel maillage, nous utilisons un TriangleStrip ou un TriangleFan mais toute technique doit donner une topographie similaire pour les triangles résultants.

     |/   |/   |/   |/
...--+----U----UR---+--...
    /|   /| 2 /|   /|           Y
   / |  / |  / |  / |           ^
     | /  | /  | /  | /         |
     |/ 1 |/ 3 |/   |/          |
...--L----P----R----+--...      +-----> X
    /| 6 /| 4 /|   /|          
   / |  / |  / |  / |         
     | /5 | /  | /  | /      
     |/   |/   |/   |/
...--DL---D----+----+--...
    /|   /|   /|   /|

Pour un triangleStrip, chaque sommet P = (x0, y0, z0) a 6 sommets adjacents notés

up       = (x0     , y0 + ay, Zup)
upright  = (x0 + ax, y0 + ay, Zupright) 
right    = (x0 + ax, y0     , Zright) 
down     = (x0     , y0 - ay, Zdown)
downleft = (x0 - ax, y0 - ay, Zdownleft) 
left     = (x0 - ax, y0     , Zleft)

où ax/ay est le pas de grille constant sur l'axe x/y respectivement. Sur une grille carrée ax = ay.

ax = width / (nColumns - 1)
ay = height / (nRows - 1)

Ainsi, chaque sommet a 6 triangles adjacents chacun avec son propre vecteur normal (noté N1 à N6). Ceux-ci peuvent être calculés en utilisant le produit croisé des deux vecteurs définissant le côté du triangle et en faisant attention à l'ordre dans lequel nous faisons le produit croisé. Si le vecteur normal pointe dans la direction Z vers vous:

N1 = up x left =
   = (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup) 

   =( (y0 + ay)*Zleft - y0*Zup, 
      (x0 - ax)*Zup   - x0*Zleft, 
      x0*y0 - (y0 + ay)*(x0 - ax) ) 

N2 = upright  x up
N3 = right    x upright
N4 = down     x right
N5 = downleft x down
N6 = left     x downleft

Et le vecteur normal résultant pour chaque point P est la somme de N1 à N6. Nous normalisons après sommation. Il est très facile de créer une boucle, de calculer les valeurs de chaque vecteur normal, de les ajouter puis de normaliser. Cependant, comme l'a souligné M. Shickadance, cela peut prendre un certain temps, en particulier pour les grands maillages et/ou sur les appareils intégrés.

Si nous regardons de plus près et effectuons les calculs à la main, nous découvrirons que la plupart des termes s'annulent, nous laissant une solution finale très élégante et facile à calculer pour le vecteur résultant N. Le point ici est de accélérer les calculs en évitant de calculer les coordonnées de N1 à N6, en faisant 6 produits croisés et 6 ajouts pour chaque point. L'algèbre nous aide à passer directement à la solution, à utiliser moins de mémoire et moins de temps CPU.

Je ne montrerai pas les détails des calculs car ils sont longs mais simples et je passerai à l'expression finale du vecteur Normal pour n'importe quel point de la grille. Seul N1 est décomposé par souci de clarté, les autres vecteurs se ressemblent. Après sommation on obtient N qui n'est pas encore normalisé:

N = N1 + N2 + ... + N6

  = .... (long but easy algebra) ...

  = ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax,
      (2*(Zdown - Zup)    + Zupright + Zdownleft - Zup - Zleft) / ay,
       6 )

Voilà! Normalisez simplement ce vecteur et vous avez le vecteur normal pour n'importe quel point de la grille, à condition de connaître les valeurs Z de ses points environnants et le pas horizontal/vertical de votre grille.

Notez qu'il s'agit de la moyenne pondérée des vecteurs normaux des triangles environnants. Le poids est l'aire des triangles et est déjà inclus dans le produit croisé.

Vous pouvez même le simplifier davantage en ne prenant en compte que les valeurs Z des quatre points environnants (haut, bas, gauche et droite). Dans ce cas, vous obtenez:

                                             |   \|/   |
N = N1 + N2 + N3 + N4                    ..--+----U----+--..
  = ( (Zleft - Zright) / ax,                 |   /|\   |
      (Zdown -  Zup  ) / ay,                 |  / | \  |
       2 )                                 \ | / 1|2 \ | /
                                            \|/   |   \|/
                                         ..--L----P----R--...
                                            /|\   |   /|\
                                           / | \ 4|3 / | \
                                             |  \ | /  |
                                             |   \|/   |
                                         ..--+----D----+--..
                                             |   /|\   |

ce qui est encore plus élégant et encore plus rapide à calculer.

J'espère que cela rendra les maillages plus rapides. À votre santé

22

Aussi simple que cela puisse paraître, le calcul de la normale d'un triangle n'est qu'une partie du problème. Le produit croisé de 2 côtés du polygone est suffisant dans les cas triangulaires, à moins que le triangle ne s'effondre sur lui-même et ne dégénère; dans ce cas, il n'y a personne de normal valide, vous pouvez donc en sélectionner un à votre convenance.

Alors pourquoi le produit croisé normalisé n'est-il qu'une partie du problème? ordre d'enroulement des sommets de ce polygone définit la direction de la normale, c'est-à-dire que si une paire de sommets est permutée, la normale pointera la direction opposée. Donc, en fait, cela peut être problématique si le maillage lui-même contient des incohérences à cet égard, c'est-à-dire que certaines parties supposent un ordre, tandis que d'autres parties supposent des ordres différents. Un exemple célèbre est le modèle original Stanford Bunny , où certaines parties de la surface pointent vers l'intérieur, tandis que d'autres pointent vers l'extérieur. La raison en est que le modèle a été construit à l'aide d'un scanner, et aucun soin n'a été pris pour produire des triangles avec des motifs d'enroulement réguliers. (évidemment, des versions propres du lapin existent aussi)

Le problème d'enroulement est encore plus important si les polygones peuvent avoir plusieurs sommets, car dans ce cas, vous calculez la moyenne des normales partielles de la semi-triangulation de ce polygone. Considérons le cas où les normales partielles pointent dans des directions opposées, résultant en des vecteurs normaux de longueur 0 lors de la prise de la moyenne!

Dans le même sens, les soupes polygonales et les nuages ​​de points déconnectés présentent des défis pour une reconstruction précise en raison du nombre d'enroulement mal défini.

Une stratégie potentielle qui est souvent utilisée pour résoudre ce problème est de tirer des rayons aléatoires de l'extérieur vers le centre de chaque semi-triangulation (c'est-à-dire ray-stabbing). Mais on ne peut pas supposer que la triangulation est valide si les polygones peuvent contenir plusieurs sommets, donc les rayons peuvent manquer ce sous-triangle particulier. Si un rayon frappe, alors la normale opposée à la direction du rayon, c'est-à-dire avec point (rayon, n) <.5 satisfait, peut être utilisée comme normale pour tout le polygone. Évidemment, c'est assez cher et évolue avec le nombre de sommets par polygone.

Heureusement, il y a une grande nouveau travail qui décrit une méthode alternative qui est non seulement plus rapide (pour les maillages grands et complexes) mais aussi généralise le 'ordre d'enroulement' concept pour les constructions au-delà des maillages polygonaux , telles que les nuages ​​de points et les soupes polygonales, les iso-surfaces et les surfaces de points, où la connectivité peut même ne pas être défini!

Comme indiqué dans l'article, la méthode construit une représentation de l'arbre de séparation hiérarchique qui est affinée progressivement, en tenant compte de l'orientation "dipôle" parent à chaque opération de division . Un polygone normal serait alors simplement une intégration (moyenne) sur tous les dipôles (c'est-à-dire point + paires normales) du polygone.

Pour les personnes confrontées à des données de maillage/pcl impures provenant de scanners Lidar ou d'autres sources, cela pourrait être défectueux. être un changeur de jeu.

2
StarShine

Pour ceux comme moi qui sont tombés sur cette question, votre réponse pourrait être la suivante:

// Compute Vertex Normals
std::vector<sf::Glsl::Vec3> verticesNormal;
verticesNormal.resize(verticesCount);

for (i = 0; i < indices.size(); i += 3)
{
    // Get the face normal
    auto vector1 = verticesPos[indices[(size_t)i + 1]] - verticesPos[indices[i]];
    auto vector2 = verticesPos[indices[(size_t)i + 2]] - verticesPos[indices[i]];
    auto faceNormal = sf::VectorCross(vector1, vector2);
    sf::Normalize(faceNormal);

    // Add the face normal to the 3 vertices normal touching this face
    verticesNormal[indices[i]] += faceNormal;
    verticesNormal[indices[(size_t)i + 1]] += faceNormal;
    verticesNormal[indices[(size_t)i + 2]] += faceNormal;
}

// Normalize vertices normal
for (i = 0; i < verticesNormal.size(); i++)
    sf::Normalize(verticesNormal[i]);
0
Maghin