OpenGL: perché devo impostare un valore normale con glNormal?


19

Sto imparando alcune basi di OpenGL ma mi chiedo perché ci sia una chiamata glNormalper impostare il normale dei vertici.

Se creo un triangolo semplice come questo:

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

Le normali non dovrebbero essere definite implicitamente dal tipo di primitiva geometrica? Se non imposto un normale OpenGL lo calcolerà?


7
Vorrei solo sottolineare che questo codice utilizza una pipeline deprecata a funzione fissa, e nel moderno OpenGL sia normali che vertici sono solo attributi di vertici generici.
Bartek Banachewicz,

4
Non utilizzare la modalità immediata. È deprecato. Consiglio vivamente di imparare la moderna programmazione grafica 3D .
destra

3
Penso che il commento sull'uso della modalità immediata sia completamente irrilevante qui. Sì, non si dovrebbe usarlo, ma il punto della domanda rimane lo stesso indipendentemente dal fatto che si tratti di modalità immediata, array di vertici, VBO, attributi generici, attributi fissi o toast.
Maximus Minimus

1
Il motivo per cui non viene determinato automaticamente è perché renderebbe l'illuminazione molto poligonale (per mancanza di una parola migliore). Ciò che intendo con questo è che tutti i vertici su un triangolo avrebbero lo stesso valore normale. Per le superfici piane questo è ciò che ci aspetteremmo, tuttavia se avessi un modello di una faccia di una persona, ogni triangolo sulla faccia avrebbe lo stesso valore di illuminazione (rendendo molto facile vedere i poligoni). Specificando i propri valori normali è possibile rendere le cose molto più fluide (normalmente si trova un valore medio calcolando la media dei valori normali di tutti i triangoli contenenti il ​​vertice corrente).
Benjamin Danger Johnson,

Risposte:


30

Chiamare glNormalpiù volte tra glBegin/Endconsente di impostare un normale per vertice anziché per primitivo.

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

Per una mesh più complessa composta da più triangoli, la normale in un vertice dipende dagli altri triangoli della geometria circostante e non solo dalla normale del triangolo a cui appartiene.

Ecco un esempio della stessa geometria con:

  • sulle normali per vertice di sinistra - quindi ogni vertice di una faccia ha una normale diversa (per questa sfera significa che tutte le normali puntano verso l'esterno dal suo centro), e
  • sulle normali faccia a faccia - ogni vertice ottiene lo stesso valore normale (poiché lo specifichi una sola volta o perché utilizza il valore normale predefinito di (0,0,1)):

normali per vertice a sinistra, normali per faccia a destra

Il modello di tonalità predefinito ( GL_SMOOTH) fa sì che le normali dei vertici della faccia vengano interpolate sulla faccia. Per le normali per vertice questo si traduce in una sfera ombreggiata liscia, ma per le normali per faccia si finisce con il caratteristico aspetto sfaccettato.


4

No, GL non calcola una norma per te. Non può sapere quale dovrebbe essere il normale. I tuoi primitivi non significano nulla al di fuori della rasterizzazione (e in pochi altri luoghi). GL non sa quali facce (triangoli) condividano un vertice (il calcolo di questo in fase di esecuzione è molto costoso senza l'utilizzo di strutture dati diverse).

È necessario preelaborare la "zuppa triangolare" di una mesh per trovare vertici comuni e calcolare normali. Questo dovrebbe essere fatto prima che il gioco corra per la velocità.

Ci sono anche casi come bordi duri in cui geometricamente c'è un vertice condiviso all'angolo, ma vuoi normali separate in modo che si illumini correttamente. Nel modello di rendering GL / D3D, sono necessari due vertici separati (nella stessa posizione) per questo, ognuno con una normale separata.

Avrai bisogno di tutto questo anche per la mappatura UV e texture.


3

La risposta breve è che in realtà non dispone di impostare un normale a tutti. I vettori normali vengono utilizzati solo se stai eseguendo l'illuminazione OpenGL (o forse qualche altro calcolo che può dipendere da loro), ma se ciò non ti riguarda (o se stai usando qualche altro metodo per fare l'illuminazione, ad esempio mappe luminose ) allora puoi tranquillamente omettere tutte le chiamate glNormal.

Quindi supponiamo che tu stia facendo qualcosa in cui specificare glNormal ha senso e guardiamo ancora un po '.

glNormal può essere specificato per primitivo o per vertice. Con le normali per primitive tutto ciò che significa è che tutte le normali per ciascun vertice nella faccia primitiva nella stessa direzione. A volte questo è quello che vuoi, ma a volte no - dove le normali per vertice sono utili è che permettono a ciascun vertice di avere il proprio fronte.

Prendi il classico esempio di una sfera. Se ogni triangolo in una sfera avesse la stessa normalità, il risultato finale sarebbe piuttosto sfaccettato. Probabilmente non è quello che vuoi, vuoi invece un'ombreggiatura regolare. Quindi quello che fai è fare una media delle normali per ogni triangolo che condivide un vertice comune e ottenere un risultato sfumato uniforme.


2

Esempio minimo che illustra alcuni dettagli su come glNormalfunziona con un fulmine diffuso.

I commenti della displayfunzione spiegano cosa significa ogni triangolo.

inserisci qui la descrizione dell'immagine

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

Teoria

In OpenGL ogni vertice ha il proprio vettore normale associato.

Il vettore normale determina la luminosità del vertice, che viene quindi utilizzata per determinare la luminosità del triangolo.

Quando una superficie è perpendicolare alla luce, è più luminosa di una superficie parallela.

glNormal imposta il vettore normale corrente, che viene utilizzato per tutti i seguenti vertici.

Il valore iniziale per il normale prima di tutti noi glNormalè 0,0,1.

I vettori normali devono avere la norma 1, altrimenti i colori cambiano! glScalealtera anche la lunghezza delle normali! glEnable(GL_NORMALIZE);fa in modo che OpenGL imposti automaticamente la loro norma su 1 per noi. Questa GIF lo illustra magnificamente.

Bibliografia:


Bella risposta, ma perché concentrarsi su una vecchia domanda e una vecchia tecnologia?
Vaillancourt

@AlexandreVaillancourt grazie! Vecchia domanda: perché no :-) Vecchia tecnologia: qual è il modo migliore oggi?
Ciro Santilli 18 改造 中心 法轮功 六四 事件

Penso che il moderno OpenGL usi oggetti buffer invece di specificare glNormal.
Vaillancourt

1
In effetti, glNormal e glVertex e simili sono scomparsi nel moderno OpenGL e sono stati deprecati qualche tempo prima ... in realtà erano deprecati anche quando questa domanda è stata originariamente posta.

0

È necessario glNormal per implementare alcune funzionalità dipendenti dal programmatore. Ad esempio, potresti voler creare dati normali per preservare i bordi rigidi.


4
Forse vorrai migliorare un po 'la tua risposta.
Bartek Banachewicz,

1
Se vuoi che io migliori la mia risposta, dovresti sottolineare i punti deboli. Posso elaborarlo, però. Come pensava xblax, ogni normale "dovrebbe" essere calcolato allo stesso modo. Anche nonostante la differenza tra normali interpolate per ciascun pixel rispetto a ciascuna faccia. Dodici nell'argomento un po 'di più.
AB.

1
A volte potresti voler mantenere il limite e renderlo più distintivo. Ecco alcune immagini di esempio: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Ci scusiamo per il doppio invio. Sono nuovo dell'interfaccia SE
AB.

c'è un pulsante "modifica".
Bartek Banachewicz,

1
Non è possibile modificare dopo 5 minuti
AB.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.