OpenGL: pourquoi dois-je définir un normal avec glNormal?

19

J'apprends quelques bases d'OpenGL mais je me demande pourquoi il y a un appel glNormalà définir la normale des sommets.

Si je crée un simple triangle comme celui-ci:

glBegin(GL_TRIANGLES);
    glVertex3f(0,0,0);
    glVertex3f(1,0,0);
    glVertex3f(0,1,0);
glEnd();

Les normales ne devraient-elles pas être définies implicitement par le type de la primitive géométrique? Si je ne mets pas un normal, OpenGL le calculera?

xblax
la source
7
Je voudrais simplement souligner que ce code utilise un pipeline de fonction fixe obsolète, et dans OpenGL moderne, les normales et les sommets ne sont que des attributs de sommet génériques.
Bartek Banachewicz
4
N'utilisez pas le mode immédiat. C'est obsolète. Je recommande fortement l' apprentissage de la programmation graphique 3D moderne .
droite
3
Je pense que le commentaire sur l'utilisation du mode immédiat est complètement hors de propos ici. Oui, il ne faut pas l'utiliser, mais le point de la question reste le même, que ce soit le mode immédiat, les tableaux de vertex, le VBO, les attributs génériques, les attributs fixes ou les morceaux de pain grillé.
Maximus Minimus
1
La raison pour laquelle il n'est pas déterminé automatiquement est qu'il rendrait l'éclairage très polygonal (faute d'un meilleur mot). Ce que je veux dire par là, c'est que tous les sommets d'un triangle auraient la même valeur normale. Pour les surfaces planes, c'est ce à quoi nous nous attendrions, mais si vous aviez un modèle de visage de personne, chaque triangle sur le visage aurait la même valeur d'éclairage (ce qui rend très facile de voir les polygones). En spécifiant vos propres valeurs normales, vous pouvez rendre les choses beaucoup plus fluides (normalement vous trouvez une normale en faisant la moyenne des valeurs normales de tous les triangles contenant le sommet actuel).
Benjamin Danger Johnson

Réponses:

30

Appeler glNormalplusieurs fois entre glBegin/Endvous permet de définir une normale par sommet au lieu de par primitive.

glBegin(GL_TRIANGLES);
    glNormal3f(0,0,1);
    glVertex3f(0,0,0);
    glNormal3f(0,0,1);
    glVertex3f(1,0,0);
    glNormal3f(0,0,1);
    glVertex3f(0,1,0);
glEnd();

Pour un maillage plus complexe composé de plusieurs triangles, vous voudriez que la normale à un sommet dépende des autres triangles de la géométrie environnante et pas seulement de la normale du triangle auquel elle appartient.

Voici un exemple de la même géométrie avec:

  • sur les normales par vertex gauche - de sorte que chaque sommet d'une face a une normale différente (pour cette sphère, cela signifie que toutes les normales pointent vers l'extérieur à partir de son centre), et
  • sur les normales par face droite - chaque sommet obtient la même valeur normale (puisque vous ne la spécifiez qu'une seule fois, ou parce qu'il utilise la valeur normale par défaut de (0,0,1)):

normales par sommet à gauche, normales par face à droite

Le modèle d'ombre par défaut ( GL_SMOOTH) entraîne l'interpolation des normales des sommets de la face sur la face. Pour les normales par sommet, cela donne une sphère lisse et ombragée, mais pour les normales par face, vous vous retrouvez avec l'aspect caractéristique à facettes.

Eric
la source
4

Non, GL ne calcule pas de norme pour vous. Il ne sait pas ce que devrait être la normale. Vos primitives ne signifient rien en dehors de la pixellisation (et quelques autres endroits). GL ne sait pas quelles faces (triangles) partagent un sommet (le calculer à l'exécution est très coûteux sans utiliser de structures de données différentes).

Vous devez prétraiter la "soupe triangulaire" d'un maillage pour trouver des sommets communs et calculer des normales. Cela devrait être fait avant que le jeu ne tourne pour la vitesse.

Il y a aussi des cas comme les bords durs où géométriquement il y a un sommet partagé au coin, mais vous voulez des normales séparées pour qu'il s'allume correctement. Dans le modèle de rendu GL / D3D, vous avez besoin pour cela de deux sommets séparés (à la même position), chacun avec une normale distincte.

Vous aurez également besoin de tout cela pour les UV et la cartographie des textures.

Sean Middleditch
la source
3

La réponse courte est que vous n'avez pas du tout besoin de définir une normale. Les vecteurs normaux ne sont utilisés que si vous effectuez un éclairage OpenGL (ou peut-être un autre calcul qui peut en dépendre), mais si cela ne s'applique pas à vous (ou si vous utilisez une autre méthode pour faire de l'éclairage, par exemple des lightmaps ), alors vous pouvez très volontiers omettre tous les appels glNormal.

Supposons donc que vous faites quelque chose où la spécification de glNormal est logique et examinez-la un peu plus.

glNormal peut être spécifié par primitive ou par sommet. Avec des normales par primitives, cela signifie simplement que toutes les normales pour chaque sommet de la face primitive dans la même direction. Parfois, c'est ce que vous voulez, mais parfois ce n'est pas le cas - là où les normales par sommet sont utiles, c'est qu'elles permettent à chaque sommet d'avoir sa propre face.

Prenons l'exemple classique d'une sphère. Si chaque triangle dans une sphère avait la même normale, le résultat final serait tout à fait à facettes. Ce n'est probablement pas ce que vous voulez, vous voulez plutôt un ombrage lisse. Donc, ce que vous faites, c'est la moyenne des normales pour chaque triangle qui partage un sommet commun, et obtenez un résultat ombré lisse.

Maximus Minimus
la source
2

Exemple minimal qui illustre certains détails du glNormalfonctionnement de la foudre diffuse.

Les commentaires de la displayfonction expliquent ce que signifie chaque triangle.

entrez la description de l'image ici

#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

/* Triangle on the x-y plane. */
static void draw_triangle() {
    glBegin(GL_TRIANGLES);
    glVertex3f( 0.0f,  1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 0.0f);
    glVertex3f( 1.0f, -1.0f, 0.0f);
    glEnd();
}

/* A triangle tilted 45 degrees manually. */
static void draw_triangle_45() {
    glBegin(GL_TRIANGLES);
    glVertex3f( 0.0f,  1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f,  0.0f);
    glVertex3f( 1.0f, -1.0f,  0.0f);
    glEnd();
}

static void display(void) {
    glColor3f(1.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glPushMatrix();

    /*
    Triangle perpendicular to the light.
    0,0,1 also happens to be the default normal if we hadn't specified one.
    */
    glNormal3f(0.0f, 0.0f, 1.0f);
    draw_triangle();

    /*
    This triangle is as bright as the previous one.
    This is not photorealistic, where it should be less bright.
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    draw_triangle_45();

    /*
    Same as previous triangle, but with the normal set
    to the photorealistic value of 45, making it less bright.

    Note that the norm of this normal vector is not 1,
    but we are fine since we are using `glEnable(GL_NORMALIZE)`.
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    glNormal3f(0.0f, 1.0f, 1.0f);
    draw_triangle_45();

    /*
    This triangle is rotated 45 degrees with a glRotate.
    It should be as bright as the previous one,
    even though we set the normal to 0,0,1.
    So glRotate also affects the normal!
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    glNormal3f(0.0, 0.0, 1.0);
    glRotatef(45.0, -1.0, 0.0, 0.0);
    draw_triangle();

    glPopMatrix();
    glFlush();
}

static void init(void) {
    GLfloat light0_diffuse[] = {1.0, 1.0, 1.0, 1.0};
    /* Plane wave coming from +z infinity. */
    GLfloat light0_position[] = {0.0, 0.0, 1.0, 0.0};
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);
    glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glColorMaterial(GL_FRONT, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
}

static void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 7.0, -1.0, 1.0, -1.5, 1.5);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(800, 200);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return EXIT_SUCCESS;
}

Théorie

Dans OpenGL, chaque sommet a son propre vecteur normal associé.

Le vecteur normal détermine la luminosité du sommet, qui est ensuite utilisé pour déterminer la luminosité du triangle.

Lorsqu'une surface est perpendiculaire à la lumière, elle est plus lumineuse qu'une surface parallèle.

glNormal définit le vecteur normal actuel, qui est utilisé pour tous les sommets suivants.

La valeur initiale pour la normale avant nous tous glNormalest 0,0,1.

Les vecteurs normaux doivent avoir la norme 1, sinon les couleurs changent! glScalemodifie également la longueur des normales! glEnable(GL_NORMALIZE);fait qu'OpenGL règle automatiquement sa norme à 1 pour nous. Ce GIF illustre cela magnifiquement.

Bibliographie:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
la source
Belle réponse, mais pourquoi se concentrer sur une vieille question et une vieille technologie?
Vaillancourt
@AlexandreVaillancourt merci! Vieille question: pourquoi pas :-) Ancienne technologie: quelle est la meilleure façon aujourd'hui?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Je pense que l'OpenGL moderne utilise des objets tampons au lieu de spécifier glNormal.
Vaillancourt
1
En effet, glNormal et glVertex et similaires ont disparu dans OpenGL moderne, et ont été dépréciés un certain temps avant cela ... ils étaient en fait dépréciés même lorsque cette question a été initialement posée.
0

Vous avez besoin de glNormal pour implémenter certaines fonctionnalités dépendant du codeur. Par exemple, vous souhaiterez peut-être créer des normales pour conserver les bords durs.

UN B.
la source
4
Vous voudrez peut-être améliorer un peu votre réponse.
Bartek Banachewicz
1
Si vous voulez que j'améliore ma réponse, vous devrez signaler les points faibles. Je peux cependant en dire plus. Comme le pensait xblax, chaque "devrait" être calculé de la même manière. Même malgré la différence entre les normales interpolées pour chaque pixel et chaque face. Intéressons-nous un peu plus au sujet.
AB.
1
Parfois, vous souhaiterez peut-être maintenir le bord dur et le rendre plus distinctif. Voici quelques exemples d'images: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Désolé pour la double publication. Je suis nouveau dans l'interface SE
AB.
il y a un bouton "modifier".
Bartek Banachewicz
1
Vous ne pouvez pas modifier après 5 minutes
AB.