OpenGL: чому мені потрібно встановити нормальну функцію з glNormal?


19

Я вивчаю основи OpenGL, але мені цікаво, чому є дзвінок glNormal встановити нормальність вершин.

Якщо я створю такий простий трикутник:

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

Чи не слід нормалі визначати неявно за типом геометричного примітиву? Якщо я не встановлю нормальне значення, чи обчислить його OpenGL?


7
Я хотів би лише зазначити, що цей код використовує застарілий конвеєр з фіксованою функцією, а в сучасному OpenGL і нормалі, і вершини є лише загальними атрибутами вершин.
Bartek Banachewicz

4
Не використовуйте негайний режим. Це застаріло. Я настійно рекомендую вивчити сучасне 3D графічне програмування .
праворуч

3
Я думаю, що коментар щодо використання негайного режиму тут абсолютно не має значення. Так, не слід його використовувати, але пункт питання залишається тим самим, незалежно від того, чи це безпосередній режим, вершинні масиви, VBO, загальні атрибути, фіксовані атриби чи біти тостів.
Максим Мінімус

1
Причина, яка не визначається автоматично, полягає в тому, що це зробить освітлення дуже багатоголосним (за відсутності кращого слова). Що я маю на увазі під цим, всі вершини трикутника мали б однакове нормальне значення. Для плоских поверхонь ми цього очікували, але якби у вас була модель обличчя, кожен трикутник на обличчі мав би однакове значення освітлення (що полегшило б побачити багатокутники). Вказавши власні нормальні значення, ви можете зробити речі набагато більш гладкими (зазвичай ви знаходите нормальні, усереднюючи нормальні значення всіх трикутників, що містять поточну вершину).
Бенджамін Дангер Джонсон

Відповіді:


30

Виклик glNormalдекількох разів між собою glBegin/Endдозволяє встановити нормальну для вершини замість первісної.

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

Для більш складної сітки, що складається з декількох трикутників, ви хочете, щоб нормальна в одній вершині залежала від інших трикутників навколишньої геометрії, а не лише від норми трикутника, до якої вона належить.

Ось приклад тієї самої геометрії з:

  • ліворуч за вершиною нормали - тому кожна вершина обличчя має іншу норму (для цієї сфери це означає, що всі нормали вказують назовні від її центру), і
  • праворуч за нормальною нормою - кожна вершина отримує однакове нормальне значення (оскільки ви вказали її лише один раз або тому, що вона використовує стандартне значення за замовчуванням (0,0,1)):

нормальні вершини ліворуч, нормали за лицем праворуч

Модель відтінку за замовчуванням ( GL_SMOOTH) призводить до того, що нормали вершин обличчя будуть інтерпольовані по обличчю. Для нормальних вершин це призводить до гладкої затіненої сфери, але для нормальних для обличчя ви закінчуєтеся характерним обличчям .


4

Ні, GL не розраховує норму для вас. Він не може знати, яким має бути нормальний стан. Ваші примітиви не означають нічого, крім растерізації (та кількох інших місць). GL не знає, на які грані (трикутники) ділиться вершина (обчислювати це під час виконання дуже дорого без використання різних структур даних).

Потрібно попередньо обробити сітчастий «трикутний суп», щоб знайти загальні вершини та обчислити нормалі. Це слід зробити до того, як гра працює на швидкість.

Також є такі випадки, як жорсткі ребра, коли геометрично на куті є спільна вершина, але ви хочете, щоб окремі нормали були правильно. У моделі рендерингу GL / D3D для цього вам потрібні дві окремі вершини (в одному і тому ж положенні), кожна з яких має окрему нормальну.

Вам знадобиться все це для ультрафіолетового випромінювання та текстурування карти.


3

Коротка відповідь полягає в тому, що вам насправді взагалі не потрібно встановлювати норму. Звичайні вектори використовуються лише в тому випадку, якщо ви виконуєте освітлення OpenGL (або, можливо, якийсь інший розрахунок, який може залежати від них), але якщо це не стосується вас (або якщо ви використовуєте інший метод освітлення, наприклад, світлові карти ), то ви можете цілком радісно опустити всі виклики glNormal.

Отже, припустимо, що ви робите щось, де вказувати glNormal має сенс, і перегляньте це ще раз.

glNormal може бути вказаний первістично або за вершиною. Що стосується первісних нормалів, все, що це означає, - це те, що всі норми для кожної вершини в примітивній грані в одному напрямку. Іноді це те, що ви хочете, але іноді це не так - де корисні для вершин норми корисні, це те, що вони дозволяють кожній вершині мати своє обличчя.

Візьмемо класичний приклад сфери. Якби кожен трикутник у кулі мав однакову норму, то кінцевий результат був би досить гранічним. Мабуть, це не те, що ви хочете, ви хочете плавне затінення замість цього. Отже, те, що ви робите, - це середні норми для кожного трикутника, які мають спільну вершину, і отримуєте плавний затінений результат.


2

Мінімальний приклад, який ілюструє деякі деталі того, як glNormalпрацює з розсіяною блискавкою.

У коментарях displayфункції пояснюється, що означає кожен трикутник.

введіть тут опис зображення

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

Теорія

У OpenGL у кожної вершини є власний асоційований нормальний вектор.

Нормальний вектор визначає, наскільки яскравою є вершина, яка потім використовується для визначення того, наскільки яскравим є трикутник.

Коли поверхня перпендикулярна до світла, вона яскравіша за паралельну поверхню.

glNormal встановлює поточний нормальний вектор, який використовується для всіх наступних вершин.

Початкове значення для нормального , перш ніж все glNormalце 0,0,1.

Звичайні вектори повинні мати норму 1, інакше змінюються кольори! glScaleтакож змінює довжину норми!glEnable(GL_NORMALIZE);змушує OpenGL автоматично встановлювати для них свою норму 1. Цей GIF це прекрасно ілюструє.

Бібліографія:


Гарна відповідь, але навіщо зосередитися на старому питанні та старої технології?
Vaillancourt

@AlexandreVaillancourt дякую! Старе питання: чому б і ні :-) Стара технологія: чим краще сьогодні?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Я думаю, що сучасний OpenGL використовує буферні об’єкти замість того, щоб вказувати glNormal.
Vaillancourt

1
Дійсно, glNormal і glVertex тощо, як і раніше, перебувають у сучасному OpenGL, і були застарілими за деякий час до цього ... вони були фактично застарілими навіть тоді, коли це питання було задано спочатку.

0

Вам потрібно glNormal, щоб реалізувати деякі функції, що залежать від кодера. Наприклад, ви можете створити нормали, щоб зберегти жорсткі краї.


4
Можливо, ви захочете трохи покращити свою відповідь.
Bartek Banachewicz

1
Якщо ви хочете, щоб я покращив свою відповідь, вам слід вказати на слабкі місця. Я можу детальніше розглянути це. Як думав xblax, кожен нормальний "повинен" обчислюватися однаково. Навіть незважаючи на різницю між інтерпольованими нормалами для кожного пікселя та кожної грані. Давайте детальніше зупинимось на цій темі.
AB.

1
Іноді, можливо, ви захочете зберегти жорсткий край і зробити його більш виразним. Ось кілька прикладних зображень: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Вибачте за подвійну публікацію. Я новачок в інтерфейсі SE
AB.

є кнопка "редагувати".
Bartek Banachewicz

1
Ви не можете редагувати через 5 хвилин
AB.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.