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 2013年

4
不要使用立即模式。不推荐使用。我强烈建议学习现代3D图形编程
2013年

3
我认为对使用立即模式的评论在这里完全不相关。是的,不应该使用它,但是问题的重点仍然是相同的,而不管它是立即模式,顶点数组,VBO,通用属性,固定属性还是吐司。
Maximus Minimus 2013年

1
不能自动确定它的原因是因为它会使照明非常多边形(因为缺少更好的词)。我的意思是,三角形上的所有顶点都将具有相同的法线值。对于平坦的表面,这是我们所期望的,但是,如果您有一个人脸模型,则该脸部上的每个三角形都将具有相同的照明值(使多边形的查看变得非常容易)。通过指定自己的法线值,可以使外观看起来更加平滑(通常,通过对包含当前顶点的所有三角形的法线值求平均值,可以找到法线)。
本杰明·丹格·约翰逊

Answers:


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 设置当前法向向量,该向量将用于以下所有顶点。

我们所有人之前的法线初始值glNormal0,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 2013年

1
如果您希望我改善答案,则需要指出薄弱环节。我可以详细说明。正如xblax曾经认为的那样,每个正常的“应该”都以相同的方式计算。即使每个像素与每个脸部的内插法线之间存在差异,也是如此。让我们进一步讨论该主题。
AB。

1
有时您可能希望保留硬边并使其更具特色。以下是一些示例图片:titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/…PS 。抱歉,双重发布。我是SE界面的新手
AB。

有一个“编辑”按钮。
Bartek Banachewicz 2013年

1
5分钟后无法编辑
AB。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.