OpenGL: почему я должен установить нормаль с помощью glNormal?

19

Я изучаю некоторые основы OpenGL, но мне интересно, почему есть вызов, glNormalчтобы установить нормаль вершин.

Если я создам простой треугольник, как это:

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

Не должны ли нормали определяться неявно по типу геометрического примитива? Если я не установлю нормальный, OpenGL вычислит это?

xblax
источник
7
Я просто хотел бы отметить, что этот код использует устаревший конвейер с фиксированными функциями, а в современном OpenGL и нормали, и вершины являются просто общими атрибутами вершин.
Бартек Банахевич
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 вам для этого нужны две отдельные вершины (в одной позиции), каждая с отдельной нормалью.

Все это вам понадобится и для UV, и для наложения текстур.

Шон Миддледич
источник
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 прекрасно это иллюстрирует.

Список используемой литературы:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
источник
Хороший ответ, но зачем концентрироваться на старом вопросе и старой технологии?
Vaillancourt
@AlexandreVaillancourt спасибо! Старый вопрос: почему бы и нет :-) Старые технологии: как лучше сегодня?
Сиро Сантилли 新疆 15: 中心 法轮功 六四 事件
Я думаю, что современный OpenGL использует буферные объекты вместо указания glNormal.
Vaillancourt
1
Действительно, glNormal, glVertex и тому подобное ушли в современный OpenGL, и за некоторое время до этого они устарели ... на самом деле они устарели, даже когда первоначально задавался этот вопрос.
0

Вам нужен glNormal для реализации некоторых функций, зависящих от кодера. Например, вы можете захотеть создать нормали, сохраняющие твердые края.

AB.
источник
4
Возможно, вы захотите немного улучшить свой ответ.
Бартек Банахевич
1
Если вы хотите, чтобы я улучшил свой ответ, вам нужно указать на слабые места. Я могу остановиться на этом, хотя. Как думал xblax, каждое нормальное «должно» рассчитываться одинаково. Даже несмотря на разницу между интерполированными нормалями для каждого пикселя и каждой грани. Давайте рассмотрим тему немного подробнее.
AB.
1
Иногда вы можете захотеть сохранить острый край и сделать его более выразительным. Вот некоторые примеры изображений: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Извините за двойной пост. Я новичок в интерфейсе SE
AB.
есть кнопка «редактировать».
Бартек Банахевич
1
Вы не можете редактировать через 5 минут
AB.