房间内的光源表现异常

6
我已经编写了几个Android应用程序,但这是我第一次接触3D编程。
我创建了一个房间(4面墙、天花板和地板),里面放置了一些物体,并且可以像步行一样移动摄像机。我使用各种图像对所有表面进行了纹理处理,一切都按预期工作。
为了情境说明,房间宽14个单位,深16个单位(以原点为中心),高3个单位(离原点1个单位以上,2个单位以下)。房间中间有两个物体,一个立方体和一个倒置的金字塔。
接着我想添加一个光源来给立方体和金字塔上色。我阅读了一些NeHe的端口并遵循了其中几个,所以我将在照明课程中工作的内容应用到我的新代码中。
gl.glEnable(GL10.GL_LIGHTING);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[] { 1f, 1f, 1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[] { -4f, 0.9f, 6f, 1f }, 0);
gl.glEnable(GL10.GL_LIGHT0);

结果是立方体和金字塔没有受到阴影的影响。它们在对面光线的侧面上看起来与朝向光源的侧面相同。当摄像机直接远离光源时,房间看起来就像我添加灯光代码之前一样。当我旋转摄像机面向光源时,整个房间(包括物体)变暗,直到摄像机直接面对光源时完全变黑。
这是怎么回事?我读了很多关于照明和其工作原理的文章,但我没有看到任何迹象表明为什么这不会照亮房间的所有侧面,并根据光源位置使立方体和金字塔受到阴影的影响。因为光源在“内部”,所以是否有一些预期的光行为?或者我只是因为新手而错过了一些简单的东西?
2个回答

9
每个三维世界中的物体都有一个normal,它帮助OpenGL确定物体需要反射多少光。您可能忘记为表面指定法线。如果没有指定它们,OpenGL将以相同的方式照亮您世界中的所有物体。
为了在三维中获取表面的法线,您需要至少三个顶点,这意味着它至少是一个三角形。
示例内容:
为了计算表面的法线,您需要两个向量。由于在三维空间中有三个顶点,这意味着这些样本点可以包含一个三角形:
// Top triangle, three points in 3D space.
vertices = new float[] {
   -1.0f, 1.0f, -1.0f,
   1.0f, 1.0f, -1.0f,
   0.0f, 1.0f, -1.0f,
}

有了这三点,现在你可以用以下方式定义两个向量:

// Simple vector class, created by you.
Vector3f vector1 = new Vector3f();
Vector3f vector2 = new Vector3f();

vector1.x = vertices[0] - vertices[3];
vector1.y = vertices[1] - vertices[4];
vector1.z = vertices[2] - vertices[5];

vector2.x = vertices[3] - vertices[6];
vector2.y = vertices[4] - vertices[7];
vector2.z = vertices[5] - vertices[8];

现在当你有两个向量时,你最终可以通过使用叉积来得到表面的法线。简单来说,叉积是一种操作,它会生成一个包含与输入向量垂直的角度的新向量。这就是我们需要的法线
要在代码中获得叉积,你必须编写自己的方法来计算它。理论上,你可以根据以下公式计算叉积:
A X B = (A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x)
在代码中(使用上面的向量):
public Vector3f crossProduct(Vector3f vector1, Vector3f vector2) {
    Vector3f normalVector = new Vector3f();

    // Cross product. The normalVector contains the normal for the
    // surface, which is perpendicular both to vector1 and vector2.
    normalVector.x = vector1.y * vector2.z - vector1.z * vector2.y;
    normalVector.y = vector1.z * vector2.x - vector1.x * vector2.z;
    normalVector.z = vector1.x * vector2.y - vector1.y * vector2.x;

    return normalVector;
}

在进一步讨论之前,你可以将法线指定为数组,并在需要时将其放入OpenGL中,但如果你深入研究此主题,你对此的理解会更好,你的代码也会更加灵活。
现在我们有了一个法线,你可以循环遍历它,将向量值分配给法线数组(类似于NeHe的端口,但是动态的),并设置OpenGL使用GL_NORMAL_ARRAY以便正确地反射光线到物体上。
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

// I'm assuming you know how to put it into a FloatBuffer.
gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalsBuffer);

// Draw your surface...

另外一个最后的评论; 如果你正在使用其他顶点值(如5.0f、10.0f或更大),你可能需要标准化crossProduct()方法返回的向量,以获得一些性能。否则OpenGL必须计算新向量以获取单位向量,这可能是一个性能问题。
此外,你的GL_POSITIONnew float[] {-4f, 0.9f, 6f, 1f}不太正确。当第四个值设置为1.0f时,这意味着光源位置是0, 0, 0,无论前三个值是什么。为了指定光源位置的向量,将第四个值更改为0.0f

啊!好的,我一直错过了NeHe示例中调用glNormal3f的行,因为他使用的都是简单的表面,不需要像你展示的那样计算它们。由于我已经完全使用条带和风扇制作了房间,所以在测试之前我需要实现一些规范化操作。感谢您详细解释如何计算法线。 - atheaos

1

每帧都需要重新加载光源位置,否则光源会随着相机移动,这可能不是您想要的。此外,您所描述的着色与顶点插值照明完全一致。如果您想要更好的效果,您将需要按像素进行处理(这意味着实现自己的着色器),或者对几何图形进行细分。


有趣的是,我在阅读关于光线的文章时没有涉及到移动相机的信息,因此他们没有提到位置需要随着移动进行更新。 - atheaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接