我已经写了几个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);
结果是立方体和金字塔没有阴影。它们在与光相对的两侧看起来和在面对它时一样。当摄像机直接指向远离光源时,房间看起来就像我添加照明代码之前一样。当我将相机旋转到面向光源时,整个房间(包括物体)会变暗,直到相机直接面向光源时全黑。
这里发生了什么?我阅读了很多关于灯光及其工作原理的文章,但我没有看到任何迹象表明为什么这不会照亮房间的各个方面,立方体和金字塔根据灯光位置着色。是否有一些预期的光线行为,因为它是"内部"房间?我只是错过了一些简单的东西,因为我是新手?
答案 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或更大),你可能想要normalize从crossProduct()
方法返回的向量,以获得一些性能。否则,OpenGL必须计算新向量以获取单位向量,这可能是性能问题。
另外,new float[] {-4f, 0.9f, 6f, 1f}
的{{1}}不太正确。当第四个值设置为GL_POSITION
时,无论前三个值是什么,它都表示灯光位置为1.0f
。要为灯光位置指定矢量,请将第四个值更改为0, 0, 0
。
答案 1 :(得分:1)
您需要每帧重新加载灯光位置,否则光源会随着相机一起移动,这可能不是您想要的。您描述的阴影也与顶点插值照明完全一致。如果你想要更好的东西,你必须按像素(这意味着实现你自己的着色器),或者细分你的几何。