房间内的光源意外地起作用

时间:2011-06-22 16:16:54

标签: java android opengl-es lighting

我已经写了几个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 个答案:

答案 0 :(得分:8)

3D世界中的每个对象都有一个 normal ,它可帮助OpenGL确定对象需要反射多少光。您可能忘了为曲面指定法线。如果不指定它们,OpenGL将以相同的方式点亮您世界中的所有对象。

为了在3D中获得曲面的法线,您需要至少三个顶点,这意味着它至少是一个三角形。

示例内容

为了计算表面的法线,你需要两个向量。由于3D空间中有三个顶点,这意味着这些采样点可能包含三角形:

// 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];

现在,当你有两个向量时,你最终可以使用Cross Product来获得表面的法线。不久,叉积是一种操作,其产生包含垂直于输入矢量的角度的新矢量。这是我们需要的普通

要在代码中获取交叉产品,您必须编写自己的方法来计算它。理论上,您可以根据以下公式计算叉积数:

  

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以使OpenGL反映出来正确照亮物体:

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或更大),你可能想要normalizecrossProduct()方法返回的向量,以获得一些性能。否则,OpenGL必须计算新向量以获取单位向量,这可能是性能问题。

另外,new float[] {-4f, 0.9f, 6f, 1f}的{​​{1}}不太正确。当第四个值设置为GL_POSITION时,无论前三个值是什么,它都表示灯光位置为1.0f。要为灯光位置指定矢量,请将第四个值更改为0, 0, 0

答案 1 :(得分:1)

您需要每帧重新加载灯光位置,否则光源会随着相机一起移动,这可能不是您想要的。您描述的阴影也与顶点插值照明完全一致。如果你想要更好的东西,你必须按像素(这意味着实现你自己的着色器),或者细分你的几何。