OpenGL 如何管理负法线?

OpenGL how to manage negative normals?

对于如下代码中定义的立方体,您会看到法线通常在一个轴上为负。 (即使我们计算它们)

OpenGL 使用其固定管道管理它,如果我错了请纠正我,但是使用可编程管道,它会导致像黑脸这样的伪像。 (我之前的提供了代码。)

我设法 运行 我的代码对我的法线进行了操作 (normal = (0.5 + 0.5 * normal); ),但即使结果看起来不错,我想知道我的法线是否仍然有效? (而且这个操作是最好的吗?)

我的意思是,从着色器的角度来看,我仍然可以使用它们来为我的模型着色或增亮吗?你平时怎么样?

提到的法线:

const GLfloat cube_vertices[] = {
  1, 1, 1,  -1, 1, 1,  -1,-1, 1,      // v0-v1-v2 (front)
  -1,-1, 1,   1,-1, 1,   1, 1, 1,      // v2-v3-v0
  1, 1, 1,   1,-1, 1,   1,-1,-1,      // v0-v3-v4 (right)
  1,-1,-1,   1, 1,-1,   1, 1, 1,      // v4-v5-v0
  1, 1, 1,   1, 1,-1,  -1, 1,-1,      // v0-v5-v6 (top)
  -1, 1,-1,  -1, 1, 1,   1, 1, 1,      // v6-v1-v0
  -1, 1, 1,  -1, 1,-1,  -1,-1,-1,      // v1-v6-v7 (left)
  -1,-1,-1,  -1,-1, 1,  -1, 1, 1,      // v7-v2-v1
  -1,-1,-1,   1,-1,-1,   1,-1, 1,      // v7-v4-v3 (bottom)
  1,-1, 1,  -1,-1, 1,  -1,-1,-1,      // v3-v2-v7
  1,-1,-1,  -1,-1,-1,  -1, 1,-1,      // v4-v7-v6 (back)
  -1, 1,-1,   1, 1,-1,   1,-1,-1 };    // v6-v5-v4

const GLfloat cube_normalsI[] = {
  0, 0, 1,   0, 0, 1,   0, 0, 1,      // v0-v1-v2 (front)
  0, 0, 1,   0, 0, 1,   0, 0, 1,      // v2-v3-v0
  1, 0, 0,   1, 0, 0,   1, 0, 0,      // v0-v3-v4 (right)
  1, 0, 0,   1, 0, 0,   1, 0, 0,      // v4-v5-v0
  0, 1, 0,   0, 1, 0,   0, 1, 0,      // v0-v5-v6 (top)
  0, 1, 0,   0, 1, 0,   0, 1, 0,      // v6-v1-v0
  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,      // v1-v6-v7 (left)
  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,      // v7-v2-v1
  0,-1, 0,   0,-1, 0,   0,-1, 0,      // v7-v4-v3 (bottom)
  0,-1, 0,   0,-1, 0,   0,-1, 0,      // v3-v2-v7
  0, 0,-1,   0, 0,-1,   0, 0,-1,      // v4-v7-v6 (back)
  0, 0,-1,   0, 0,-1,   0, 0,-1 };    // v6-v5-v4

不,这根本没有意义。要么你需要更新你的问题,要么你完全错了。

法线可能面向任何方向,法线通常在一个轴上为负值 是完全自然的。他们为什么不呢?根据您的描述,您似乎正在使用照明。一部分照明使用法线来查看光源与表面之间的角度。这里的想法是,当您转动法线时,光线有效地照亮了表面的大部分,从而降低了反射光的密度。通过基础数学,您可以看到相关性为 cos(angle),因此平行矢量将产生最高亮度。由于我们使用的是向量,因此我们最好将 cos 替换为点积。

所以在某些时候你有

float factor = dot(normalize(normal), normalize(lightSource-surfacePoint))

让我们在这里举两个例子:

normal = (0, 1, 0)
lightSource = (0, 1, 0)
surfacePoint = (0, 0, 0)
dot((0, 1, 0), (0, 1, 0)) = 0+1+0 = 1

然后转过来:

normal = (-1, 0, 0)
lightSource = (-3, 1, 0)
surfacePoint = (0, 1, 0)
dot((-1, 0, 0), normalize(-3, 0, 0)) = dot((-1, 0, 0), (1, 0, 0)) = 1+0+0 = 1

所以即使位置完全改变并且法线为负,对于相同的角度我们也会得到相同的结果(在这些情况下向量是垂直的)。

这里唯一的问题是当点积为负时怎么办。当法线背对着光线时,就会发生这种情况。在您的情况下,您有一个立方体,所有法线都指向外。如果你需要在一个立方体中并且仍然有照明怎么办?你会得到

normal = (0, 1, 0)
lightSource = (0, 0, 0)
surfacePoint = (0, 1, 0)
dot((0, 1, 0), (0, -1, 0)) = 0-1+0 = -1

由于这种情况,您需要保留值或使用绝对值。夹紧会使立方体的内部变黑(不亮),而绝对值也会使它们变亮:

fragmentColor += lightColor*dotFactor // Do nothing and your light will darken the area
fragmentColor += lightColor*abs(dotFactor) // Use absolute value to lighten even if facing away
fragmentColor += lightColor*max(0.0, dotFactor) // Clamp minimum so there are no negative values.

但其中none与绝对坐标系中面向任何方向的法线无关。它只是与法线、像素位置和光源之间的相对位置有关。