我听说我应该使用法线而不是颜色,因为不推荐使用颜色。(这是真的吗?)法线与光的反射有关,但我找不到清晰直观的解释。什么是正常?
3 回答
法线通常是一个单位矢量,其方向在特定点处垂直于表面。因此,它会告诉您表面朝向的方向。法线的主要用例是照明计算,您必须确定给定表面点的法线与朝向光源或相机的方向之间的角度(或实际上通常是其余弦)。
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 使用了Phong 反射模型,其中光被分成三个分量:环境光、漫反射和镜面反射。其中,漫反射和镜面反射分量受法线影响:
- 如果漫射光垂直于表面,无论观察者在哪里,它都会变得更亮
- 如果镜面光照射到表面,并直接反射到观察者的眼睛中,则该点会变得更亮
glNormal
设置当前的法线向量,用于所有后续顶点。
我们所有人面前的法线的初始值glNormal
是0,0,1
。
法线向量必须有范数 1,否则颜色会改变!glScale
也改变了法线的长度!glEnable(GL_NORMALIZE);
使 OpenGL 自动为我们设置它们的标准为 1。这个GIF很好地说明了这一点。
为什么每个顶点而不是每个面都有法线是有用的
下面的两个球体具有相同数量的多边形。顶点上有法线的那个看起来更平滑。
OpenGL 4 片段着色器
在较新的 OpenGL API 中,您将法线方向数据作为任意数据块传递给 GPU:GPU 不知道它代表法线。
然后你编写一个手写片段着色器,这是一个在 GPU 中运行的任意程序,它读取你传递给它的正常数据,并实现你想要的任何闪电算法。如果您愿意,可以通过手动计算一些点积来有效地实现 Phong。
这使您可以完全灵活地更改算法设计,这是现代 GPU 的主要功能。请参阅:https ://stackoverflow.com/a/36211337/895245
这方面的示例可以在任何“现代”OpenGL 4 教程中找到,例如https://github.com/opengl-tutorials/ogl/blob/a9fe43fedef827240ce17c1c0f07e83e2680909a/tutorial08_basic_shading/StandardShading.fragmentsshader#L42
参考书目
现在很多东西都被弃用了,包括法线和颜色。这只是意味着你必须自己实现它们。使用法线,您可以为对象着色。计算由您决定,但有很多关于 Gouraud/Phong 着色的教程。
编辑:有两种类型的法线:面法线和顶点法线。面法线指向远离三角形,顶点法线指向远离顶点。使用顶点法线可以获得更好的质量,但是面法线也有很多用途,例如它们可以用于碰撞检测和阴影体积。