glNormal
минимальный пример
glNormal
является устаревшим методом OpenGL 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 2 каждая вершина имеет свой собственный связанный вектор нормали.
Нормальный вектор определяет яркость вершины, которая затем используется для определения яркости треугольника.
В OpenGL 2 использовалась модель отражения Фонга , в которой свет разделяется на три компонента: рассеянный, рассеянный и зеркальный. Из них на диффузные и зеркальные компоненты влияет нормаль:
- Если рассеянный свет перпендикулярен поверхности, он становится ярче, независимо от того, где находится наблюдатель
- если зеркальный свет попадает на поверхность и отражается прямо в глаз наблюдателя, эта точка становится более быстрой
glNormal
устанавливает текущий вектор нормали, который используется для всех следующих вершин.
Начальное значение для нормали, прежде чем мы все glNormal
будет 0,0,1
.
Нормальные векторы должны иметь норму 1, иначе цвета меняются! glScale
также изменяет длину нормалей! glEnable(GL_NORMALIZE);
заставляет OpenGL автоматически устанавливать для них норму 1. Этот GIF прекрасно иллюстрирует это.
Почему полезно иметь нормали для вершин, а не для граней
Обе сферы ниже имеют одинаковое количество полигонов. Тот, у которого нормали на вершинах, выглядит намного плавнее.
Фрагментные шейдеры OpenGL 4
В более новых API OpenGL вы передаете данные о нормальном направлении в графический процессор в виде произвольного фрагмента данных: графический процессор не знает, что он представляет нормали.
Затем вы пишете рукописный фрагментный шейдер, который представляет собой произвольную программу, запускаемую в графическом процессоре, которая считывает обычные данные, которые вы передаете ему, и реализует любой алгоритм молнии, который вы хотите. Вы можете эффективно реализовать Phong, если захотите, вручную вычисляя некоторые точечные продукты.
Это дает вам полную гибкость при изменении структуры алгоритма, которая является основной особенностью современных графических процессоров. Смотри: https://stackoverflow.com/a/36211337/895245
Примеры этого можно найти в любом из "современных" учебных пособий по OpenGL 4, например, https://github.com/opengl-tutorials/ogl/blob/a9fe43fedef827240ce17c1c0f07e83e2680909a/tutorial08_basic_shading/StandardShading.fragmentshader#L42
Библиография