I heard that I should use normals instead of colors, because colors are deprecated. (Is that true?) Normals have something to do with the reflection of light, but I can't find a clear and intuitive explanation. What is a normal?
A normal is the technical term used in Computer Graphics (and Geometry) to describe the orientation of a surface of a geometric object at a point on that surface.
The normal vector, often simply called the "normal," to a surface is a vector which is perpendicular to the surface at a given point. When normals are considered on closed surfaces, the inward-pointing normal (pointing towards the interior of the surface) and outward-pointing normal are usually distinguished.
The normal to the plane is given by the cross product n=(r−b)×(s−b).
Vertex normals (sometimes called pseudo-normals) are values stored at each vertex that are most commonly used by a renderer to determine the reflection of lighting or shading models, such as phong shading. For example, the normal of a surface is used to calculate which direction light reflects off this surface.
A normal in general is a unit vector whose direction is perpendicular to a surface at a specific point. Therefore it tells you in which direction a surface is facing. The main use case for normals are lighting calculations, where you have to determine the angle (or practically often its cosine) between the normal at a given surface point and the direction towards a lightsource or a camera.
glNormal
minimal example
glNormal
is a deprecated OpenGL 2 method, but it is simple to understand, so let's look into it. The modern shader alternative is discussed below.
This example illustrates some details of how glNormal
works with diffuse lightning.
The comments of the display
function explain what each triangle means.
#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; }
Theory
In OpenGL 2 each vertex has its own associated normal vector.
The normal vector determines how bright the vertex is, which is then used to determine how bright the triangle is.
OpenGL 2 used the Phong reflection model, in which light is separated into three components: ambient, diffuse and specular. Of those, diffuse and specular components are affected by the normal:
glNormal
sets the current normal vector, which is used for all following vertexes.
The initial value for the normal before we all glNormal
is 0,0,1
.
Normal vectors must have norm 1, or else colors change! glScale
also alters the length of normals! glEnable(GL_NORMALIZE);
makes OpenGL automatically set their norm to 1 for us. This GIF illustrates that beautifully.
Why it is useful to have normals per vertexes instead of per faces
Both spheres below have the same number of polygons. The one with normals on the vertexes looks much smoother.
OpenGL 4 fragment shaders
In newer OpenGL API's, you pass the normal direction data to the GPU as an arbitrary chunk of data: the GPU does not know that it represents the normals.
Then you write a hand-written fragment shader, which is an arbitrary program that runs in the GPU, which reads the normal data you pass to it, and implements whatever lightning algorithm you want. You can implement Phong efficiently if you feel like it, by manually calculating some dot products.
This gives you full flexibility to change the algorithm design, which is a major features of modern GPUs. See: https://stackoverflow.com/a/36211337/895245
Examples of this can be found in any of the "modern" OpenGL 4 tutorials, e.g. https://github.com/opengl-tutorials/ogl/blob/a9fe43fedef827240ce17c1c0f07e83e2680909a/tutorial08_basic_shading/StandardShading.fragmentshader#L42
Bibliography
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With