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);

结果是立方体和金字塔没有阴影。它们在与光线相对的侧面看起来与在面对光线的侧面看起来相同。当相机直接远离光源时,房间看起来就像我添加照明代码之前一样。当我旋转相机以面对光源时,整个房间(包括物体)变得更暗,直到相机直接面对光源时完全变黑。

这里发生了什么?我阅读了许多关于照明及其工作原理的文章,但我没有看到任何东西表明为什么它不会照亮房间的所有侧面,立方体和金字塔会根据光线的位置进行着色。是否有一些预期的灯光行为,因为它在房间“内部”?我只是因为我是新手而错过了一些简单的事情吗?

4

2 回答 2

8

3D 世界中的每个对象都有一个法线,它可以帮助 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];

现在,当您有两个向量时,您终于可以通过使用叉积来获得曲面的法线。简而言之,叉积是一种产生一个新向量的操作,该向量包含一个垂直于输入向量的角度。这是我们需要的常态。

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

A X B = (Ay * Bz - Az * By, Az * Bx - Ax * Bz, Ax * By - Ay * Bx)

在代码中(通过使用上面的向量):

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

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

于 2011-06-22T17:37:32.200 回答
1

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

于 2011-06-22T17:31:32.970 回答