OpenGL: dlaczego muszę ustawiać normalność za pomocą glNormal?

19

Uczę się podstaw OpenGL, ale zastanawiam się, dlaczego istnieje wezwanie glNormaldo ustawienia normalności wierzchołków.

Jeśli utworzę prosty trójkąt taki jak ten:

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

Czy normalne nie powinny być definiowane pośrednio przez rodzaj prymitywu geometrycznego? Czy jeśli nie ustawię normalnej wartości, OpenGL ją obliczy?

xblax
źródło
7
Chciałbym tylko zauważyć, że ten kod używa przestarzałego potoku o stałej funkcji, aw nowoczesnym OpenGL zarówno normalne, jak i wierzchołki są tylko ogólnymi atrybutami wierzchołków.
Bartek Banachewicz
4
Nie używaj trybu natychmiastowego. To jest przestarzałe. Bardzo polecam nauczenie się programowania nowoczesnej grafiki 3D .
prawej strony
3
Myślę, że komentarz na temat użycia trybu natychmiastowego jest tutaj zupełnie nieistotny. Tak, nie należy go używać, ale sedno pytania pozostaje takie samo, niezależnie od tego, czy jest to tryb bezpośredni, tablice wierzchołków, VBO, ogólne atrybuty, stałe atrybuty lub kawałki tostów.
Maximus Minimus
1
Powodem, dla którego nie jest ono określane automatycznie, jest to, że spowodowałoby to, że oświetlenie byłoby bardzo wielokątne (z braku lepszego słowa). Rozumiem przez to, że wszystkie wierzchołki trójkąta miałyby tę samą wartość normalną. W przypadku płaskich powierzchni tego byśmy się spodziewali, jednak gdybyś miał model twarzy osoby, każdy trójkąt na twarzy miałby tę samą wartość oświetlenia (dzięki czemu bardzo łatwo jest zobaczyć wielokąty). Określając własne wartości normalne, możesz sprawić, że wszystko będzie wyglądać płynniej (normalnie znajdziesz normę, uśredniając wartości normalne wszystkich trójkątów zawierających bieżący wierzchołek).
Benjamin Danger Johnson

Odpowiedzi:

30

Wywoływanie glNormalwielokrotnie pomiędzy glBegin/Endpozwala na ustawienie normalnej wartości dla wierzchołka zamiast dla operacji podstawowej.

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

Aby uzyskać bardziej złożoną siatkę złożoną z wielu trójkątów, norma na jednym wierzchołku zależałaby od innych trójkątów otaczającej geometrii, a nie tylko normalnej trójkąta, do którego należy.

Oto przykład tej samej geometrii z:

  • po lewej stronie normalnych na wierzchołek - więc każdy wierzchołek twarzy ma inną normalną (dla tej kuli oznacza to, że wszystkie normalne są skierowane na zewnątrz od środka), i
  • po prawej stronie normalnych na twarz - każdy wierzchołek otrzymuje tę samą wartość normalną (ponieważ określasz ją tylko raz lub ponieważ używa domyślnej wartości normalnej (0,0,1)):

normalne na wierzchołek po lewej, normalne na twarz po prawej

Domyślny model cienia ( GL_SMOOTH) powoduje interpolację normalnych wierzchołków twarzy w poprzek twarzy. W przypadku normalnych na wierzchołek daje to gładką cieniowaną kulę, ale dla normalnych na twarz uzyskuje się charakterystyczny wygląd fasetowany.

Eric
źródło
4

Nie, GL nie oblicza dla Ciebie normy. Nie może wiedzieć, co powinno być normalne. Twoje prymitywne nic nie znaczą poza rasteryzacją (i kilkoma innymi miejscami). GL nie wie, które ściany (trójkąty) dzielą wierzchołek (obliczanie tego w czasie wykonywania jest bardzo drogie bez użycia różnych struktur danych).

Musisz wstępnie przetworzyć „trójkątną zupę” siatki, aby znaleźć wspólne wierzchołki i obliczyć normalne. Należy to zrobić, zanim gra uruchomi się na szybkość.

Istnieją również przypadki, takie jak twarde krawędzie, w których geometrycznie jest wspólny wierzchołek w rogu, ale chcesz osobne normalne, aby świeciło poprawnie. W modelu renderowania GL / D3D potrzebujesz do tego dwóch osobnych wierzchołków (w tej samej pozycji), każdy z osobną normalną.

Będziesz tego potrzebować także do UV i mapowania tekstur.

Sean Middleditch
źródło
3

Krótka odpowiedź jest taka, że ​​tak naprawdę wcale nie musisz ustawiać normalności. Zwykłe wektory są używane tylko wtedy, gdy wykonujesz oświetlenie OpenGL (lub może jakieś inne obliczenia, które mogą od nich zależeć), ale jeśli to nie dotyczy ciebie (lub jeśli używasz innej metody do oświetlenia, np. Lightmaps ), możesz całkiem szczęśliwie pominąć wszystkie połączenia glNormal.

Załóżmy więc, że robisz coś tam, gdzie podanie glNormal ma sens, i spójrz na to jeszcze trochę.

glNormal można określić dla operacji pierwotnej lub dla wierzchołka. W przypadku normalnych dla prymitywnych wszystko to oznacza, że ​​wszystkie normalne dla każdego wierzchołka w pierwotnej powierzchni w tym samym kierunku. Czasami jest to, czego chcesz, ale czasem nie jest - przydają się normalne na wierzchołek, ponieważ pozwalają każdemu wierzchołkowi mieć swoją własną stronę.

Weź klasyczny przykład kuli. Gdyby każdy trójkąt w kuli miał taką samą normę, wynik byłby dość fasetowany. Prawdopodobnie nie tego chcesz, zamiast tego potrzebujesz płynnego cieniowania. To, co robisz, to uśrednianie normalnych dla każdego trójkąta, który ma wspólny wierzchołek, i otrzymujesz gładki cieniowany wynik.

Maximus Minimus
źródło
2

Minimalny przykład ilustrujący niektóre szczegóły glNormaldziałania rozproszonego błyskawicy.

Komentarze displayfunkcji wyjaśniają, co oznacza każdy trójkąt.

wprowadź opis zdjęcia tutaj

#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

W OpenGL każdy wierzchołek ma swój własny normalny wektor.

Wektor normalny określa, jak jasny jest wierzchołek, który jest następnie używany do określenia, jak jasny jest trójkąt.

Kiedy powierzchnia jest prostopadła do światła, jest jaśniejsza niż powierzchnia równoległa.

glNormal ustawia bieżący wektor normalny, który jest używany dla wszystkich kolejnych wierzchołków.

Wartość początkowa dla normalnego zanim wszystko glNormaljest 0,0,1.

Normalne wektory muszą mieć normę 1, inaczej kolory się zmienią! glScalezmienia również długość normalnych! glEnable(GL_NORMALIZE);sprawia, że ​​OpenGL automatycznie ustawia dla nas swoją normę na 1. Ten GIF pięknie to ilustruje.

Bibliografia:

Ciro Santilli
źródło
Dobra odpowiedź, ale dlaczego skupiać się na starym pytaniu i starej technologii?
Vaillancourt
@AlexandreVaillancourt dzięki! Stare pytanie: dlaczego nie :-) Stara technologia: jaki jest dziś lepszy sposób?
Ciro Santilli 事件 改造 中心 法轮功 六四 事件
Myślę, że współczesny OpenGL używa obiektów buforowych zamiast określania glNormal.
Vaillancourt
1
Rzeczywiście, glNormal, glVertex i tym podobne zniknęły we współczesnym OpenGL i były przestarzałe jakiś czas wcześniej ... w rzeczywistości były przestarzałe, nawet gdy pierwotnie zadano to pytanie.
0

Potrzebujesz glNormal, aby zaimplementować niektóre funkcje zależne od kodera. Na przykład możesz chcieć utworzyć normalne dane zachowujące twarde krawędzie.

AB.
źródło
4
Być może będziesz chciał nieco poprawić swoją odpowiedź.
Bartek Banachewicz
1
Jeśli chcesz, żebym poprawił moją odpowiedź, musisz wskazać słabe punkty. Mogę jednak rozwinąć tę kwestię. Jak mawiał Xblax, każde normalne „powinno” być obliczane w ten sam sposób. Nawet pomimo różnicy między interpolowanymi normalnymi dla każdego piksela a każdą twarzą. Rozważmy trochę więcej w tym temacie.
AB.
1
Czasami możesz chcieć zachować twardą krawędź i uczynić ją bardziej charakterystyczną. Oto kilka przykładowych zdjęć: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Przepraszamy za podwójne publikowanie. Jestem nowy w interfejsie SE
AB.
jest przycisk „edytuj”.
Bartek Banachewicz
1
Nie można edytować po 5 minutach
AB.