OpenGL: warum muss ich mit glNormal ein normal setzen?


19

Ich lerne einige Grundlagen von OpenGL, frage mich aber, warum es einen Aufruf gibt glNormal, um die Normalen von Scheitelpunkten festzulegen.

Wenn ich ein einfaches Dreieck wie folgt erstelle:

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

Sollten die Normalen nicht implizit durch den Typ des geometrischen Primitivs definiert werden? Wenn ich keinen Normalwert einstelle, berechnet OpenGL diesen?


7
Ich möchte nur darauf hinweisen, dass dieser Code eine veraltete Pipeline mit festen Funktionen verwendet und in modernem OpenGL sowohl Normalen als auch Scheitelpunkte nur allgemeine Scheitelpunktattribute sind.
Bartek Banachewicz

4
Verwenden Sie nicht den Sofortmodus. Es ist veraltet. Ich kann das Erlernen der modernen 3D-Grafikprogrammierung nur empfehlen .
rechts

3
Ich denke, eine Bemerkung zur Verwendung des Sofortmodus ist hier völlig irrelevant. Ja, man sollte es nicht verwenden, aber der Punkt der Frage bleibt derselbe, unabhängig davon, ob es sich um den unmittelbaren Modus, Vertex-Arrays, VBO, generische Attribute, feste Attribute oder Toastbits handelt.
Maximus Minimus

1
Der Grund, warum es nicht automatisch bestimmt wird, ist, dass es die Beleuchtung sehr vielschichtig machen würde (mangels eines besseren Wortes). Damit meine ich, dass alle Eckpunkte in einem Dreieck den gleichen Normalwert haben würden. Für ebene Flächen ist dies das, was wir erwarten würden. Wenn Sie jedoch ein Modell eines Personengesichts hätten, hätte jedes Dreieck auf dem Gesicht den gleichen Beleuchtungswert (was das Erkennen der Polygone sehr einfach macht). Indem Sie Ihre eigenen Normalwerte angeben, können Sie die Darstellung verbessern (normalerweise finden Sie eine Norm, indem Sie den Durchschnitt der Normalwerte aller Dreiecke bilden, die den aktuellen Scheitelpunkt enthalten).
Benjamin Danger Johnson

Antworten:


30

Durch glNormalmehrmaliges Aufrufen zwischen glBegin/Endkönnen Sie eine Norm pro Scheitelpunkt anstatt pro Grundelement festlegen.

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();

Für ein komplexeres Netz, das aus mehreren Dreiecken besteht, möchten Sie, dass die Normalen an einem Scheitelpunkt von den anderen Dreiecken der umgebenden Geometrie und nicht nur von der Normalen des Dreiecks abhängen, zu dem es gehört.

Hier ist ein Beispiel der gleichen Geometrie mit:

  • Auf der linken Seite pro Scheitelpunkt Normalen - so hat jeder Scheitelpunkt einer Fläche eine andere Normale (für diese Kugel bedeutet dies, dass alle Normalen von ihrer Mitte nach außen zeigen), und
  • auf der rechten Seite Normalen - jeder Scheitelpunkt erhält den gleichen Normalwert (da Sie ihn nur einmal angeben oder weil er den Standardnormalwert von verwendet (0,0,1)):

Normalen pro Scheitelpunkt links, Normalen pro Fläche rechts

Das Standardschattenmodell ( GL_SMOOTH) bewirkt, dass die Normalen der Eckpunkte des Gesichts über das Gesicht interpoliert werden. Für die Per-Vertex-Normalen ergibt sich eine glatte schattierte Kugel, für die Per-Face-Normalen jedoch Sie das charakteristische facettierte Aussehen.


4

Nein, der GL berechnet keine Norm für Sie. Es kann nicht wissen, was das normale sein sollte. Ihre Primitiven haben außerhalb der Rasterung (und an einigen anderen Stellen) keine Bedeutung. Der GL weiß nicht, welche Flächen (Dreiecke) einen Scheitelpunkt gemeinsam haben (dies zur Laufzeit zu berechnen ist sehr teuer, ohne unterschiedliche Datenstrukturen zu verwenden).

Sie müssen die "Dreiecksuppe" eines Netzes vorverarbeiten, um gemeinsame Eckpunkte zu finden und Normalen zu berechnen. Dies sollte erfolgen, bevor das Spiel aus Geschwindigkeitsgründen ausgeführt wird.

Es gibt auch Fälle wie harte Kanten, in denen sich geometrisch ein gemeinsamer Scheitelpunkt an der Ecke befindet. Sie möchten jedoch separate Normalen, damit diese korrekt leuchten. Im GL / D3D-Rendering-Modell benötigen Sie dazu zwei separate Eckpunkte (an derselben Position) mit jeweils einer eigenen Normalen.

All dies benötigen Sie auch für UVs und Textur-Mappings.


3

Die kurze Antwort ist, dass Sie tatsächlich nicht haben keinen Normalwert einstellen müssen. Normale Vektoren werden nur verwendet, wenn Sie OpenGL-Beleuchtung durchführen (oder vielleicht eine andere davon abhängige Berechnung), aber wenn dies für Sie nicht zutrifft (oder wenn Sie eine andere Methode für die Beleuchtung verwenden, z. B. Lightmaps) ) dann kannst du ganz gerne alle glNormal Anrufe weglassen.

Nehmen wir also an, dass Sie etwas tun, bei dem die Angabe von glNormal Sinn macht, und schauen Sie es sich genauer an.

glNormal kann pro Primitiv oder pro Vertex angegeben werden. Bei pro-primitiven Normalen bedeutet dies, dass alle Normalen für jeden Scheitelpunkt in der primitiven Fläche in die gleiche Richtung weisen. Manchmal ist es das, was Sie wollen, aber manchmal ist es nicht - wo Per-Vertex-Normalen nützlich sind, ist, dass sie jedem Vertex eine eigene Ausrichtung geben.

Nehmen Sie das klassische Beispiel einer Kugel. Wenn jedes Dreieck in einer Kugel die gleiche Norm hätte, wäre das Endergebnis ziemlich facettenreich. Das ist wahrscheinlich nicht das, was Sie wollen, Sie wollen stattdessen eine glatte Schattierung. Sie mitteln also die Normalen für jedes Dreieck, das einen gemeinsamen Scheitelpunkt hat, und erhalten ein glattes, schattiertes Ergebnis.


2

Minimales Beispiel , das einige Details der Funktionsweise glNormalvon diffusem Blitz veranschaulicht .

Die Kommentare der displayFunktion erklären, was jedes Dreieck bedeutet.

Bildbeschreibung hier eingeben

#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;
}

Theorie

In OpenGL ist jedem Vertex ein normaler Vektor zugeordnet.

Der Normalvektor bestimmt, wie hell der Scheitelpunkt ist. Mit diesem Vektor wird dann bestimmt, wie hell das Dreieck ist.

Wenn eine Oberfläche senkrecht zum Licht steht, ist sie heller als eine parallele Oberfläche.

glNormal Legt den aktuellen Normalenvektor fest, der für alle folgenden Scheitelpunkte verwendet wird.

Der anfängliche Wert für das Normal vor uns allen glNormalist 0,0,1.

Normale Vektoren müssen Norm 1 haben, sonst ändern sich die Farben! glScaleändert auch die Länge der Normalen! glEnable(GL_NORMALIZE);bringt OpenGL dazu, ihre Norm für uns automatisch auf 1 zu setzen. Dieses GIF illustriert das sehr schön.

Literaturverzeichnis:


Gute Antwort, aber warum sich auf eine alte Frage und eine alte Technologie konzentrieren?
Vaillancourt

@AlexandreVaillancourt danke! Alte Frage: Warum nicht :-) Alte Technologie: Was ist heute der bessere Weg?
Ciro Santilli am

Ich denke, dass das moderne OpenGL Pufferobjekte verwendet, anstatt glNormal anzugeben.
Vaillancourt

1
In der Tat sind glNormal und glVertex und ähnliches in der modernen OpenGL-Version nicht mehr enthalten und wurden einige Zeit zuvor abgelehnt ... Sie wurden sogar abgelehnt, als diese Frage ursprünglich gestellt wurde.

0

Sie benötigen glNormal, um einige Funktionen coderabhängig zu implementieren. Zum Beispiel möchten Sie vielleicht Normalen erstellen, bei denen harte Kanten erhalten bleiben.


4
Vielleicht möchten Sie Ihre Antwort ein wenig verbessern.
Bartek Banachewicz

1
Wenn Sie möchten, dass ich meine Antwort verbessere, müssen Sie auf die Schwachstellen hinweisen. Ich kann es jedoch näher erläutern. Wie xblax zu denken pflegte, sollte jeder Normalfall auf die gleiche Weise berechnet werden. Auch trotz des Unterschieds zwischen interpolierten Normalen für jedes Pixel und jedes Gesicht. Kommen wir noch etwas näher zum Thema.
AB.

1
Manchmal möchten Sie vielleicht die harte Kante beibehalten und sie unverwechselbarer machen. Hier sind einige Beispielbilder: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Sorry für das Doppelposting. Ich bin neu in der SE-Schnittstelle
AB.

Es gibt eine Schaltfläche "Bearbeiten".
Bartek Banachewicz

1
Sie können nicht nach 5 Minuten bearbeiten
AB.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.