2

更新:似乎大部分这些东西现在都不见了。我认为在 OpenGL ES 2.0 中正确进行光照的方法是编写自己的着色器,以任何你希望的方式传递法线,然后在着色器中进行光照。特别是根据OpenGL ES 2.0 docs,不再有glMaterialFv,glLightFv等(更不用说glNormalPointer和朋友了。)我认为这是为我编译的原因是因为这些东西是由GLKit在iOS中提供的,尽管显然它们并不能作为直接替代品开箱即用。但是,GLKit 提供了一些标准着色器来模拟 OpenGL ES 1.1 中的光照功能。

如果有人有任何进一步的信息,我很感激,但我认为这就是答案。


我是 OpenGL 的新手,所以对任何混淆提前表示歉意。我在 iOS 上使用 OpenGL ES 2.0。我在 Internet 上找到的许多示例似乎都已过时。例如,glBegin(GL_POLYGON) 方法似乎已被弃用,所以我认为我不能像大多数较旧的示例那样使用 glNormal3f。

我从 Ray Wenderlich 的精彩教程开始,并将其与我正在构建的游戏集成,成功渲染了一些房间的基本几何图形。现在我只想添加照明。我不确定什么不起作用,但我怀疑它与法线有关。

我做了一个令人困惑的尝试来启用 GL_LIGHT0,主要取自 OpenGL文档

//* Some attempts at lighting
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light0_position[] = { 0.0, 0.0, 7.99, 0.0 };
GLfloat light0_ambience[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat light0_specular[] = { 1.0, 1.0, 1.0, 1.0 };

glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambience);
glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
glShadeModel(GL_SMOOTH);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
//*/
glEnable(GL_DEPTH_TEST);

我正在使用 VBOS 将顶点位置和颜色传递给一对简单的着色器。以下内容来自教程:

_positionSlot = glGetAttribLocation(programHandle, "Position");
_colorSlot = glGetAttribLocation(programHandle, "SourceColor");

然后在 render: 方法中(我已经修改了 Vertex 结构以包括位置和颜色位置之间的法线)。

glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE,
                      sizeof(Vertex), 0);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE,
                      sizeof(Vertex), (GLvoid*) (sizeof(float) * 6));

除了照明,所有这些都非常有效。我已经计算并重新计算了我的法线。对于平行于 X 和 Y 轴的垂直矩形墙,这是微不足道的。它们是绑定到 GL_ARRAY_BUFFER 的缓冲区的一部分,以及位置和颜色信息。

我曾多次尝试使用 glNormalPointer:

glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, sizeof(Vertex), (GLvoid*)(sizeof(float)*3));

教程中的顶点结构现在是:

typedef struct {
    float Position[3];
    float Normal[3];
    float Color[4];
} Vertex;

上面调用成功,但是没有出现灯光效果。一些消息来源建议我也应该将 glVertexAttribPointer 用于法线。为此,我修改了教程的顶点着色器:

attribute vec4 Position;
attribute vec4 SourceColor;
attribute vec3 Normal;

varying vec4 DestinationColor;

uniform mat4 Projection;
uniform mat4 Modelview;

void main(void) {
    DestinationColor = SourceColor;
    gl_Position = Projection * Modelview * Position;
}

并添加了我认为允许我使用该 Normal 变量的调用(尽管我不确定 OpenGL 如何知道如何处理它):

// just before glLinkProgram()
glBindAttribLocation(programHandle, 1, "Normal");

GLuint _normalSlot = glGetAttribLocation(programHandle, "Normal");

glVertexAttribPointer(_normalSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(sizeof(float)*3));

我注意到,无论我将 Normal 变量放在 GLSL 文件中的哪个位置,glBindAttribLocation 总是对该属性失败,而 glGetAttribLocation 总是为该属性返回 -1。(我可以使用 glBindAttribLocation 成功更改 SourceColor 属性的位置,但它不会接受 Normal。)在旧版本的 OpenGL/GLSL 中曾经有一个 gl_Normal 变量,但显然现在没有了。

我不知所措,继续尝试发生在我身上的任何事情。我的下一步将是 glInterleavedArrays。有这么多活动部件,很难找到问题所在。可能是我的法线一直很好,而是灯有问题。除了照明,还有什么方法可以确认您的法线是否正确?或者如果法线错误,我应该能够从灯光中看到任何东西吗?

因此,对于这篇文章的篇幅和混乱深表歉意,但任何关于在这种情况下如何指定法线的详细解释都将受到欢迎。我反复提到,现在 OpenGL ES 2.0 中的情况有所不同,但从来没有任何详细的例子。提前致谢。

4

1 回答 1

7

自从发布它以来,我学到了很多东西,并意识到它从来没有真正得到正确的回答。更新是正确的,但值得多解释一下,因为我仍然从未在任何地方看到过很好的解释。这很简短,可以从一个详细的具体示例中受益,但希望它能够触及要点。

您通常在 VBO 中将法线向量作为顶点属性的三元组传输。如果您使用纹理,您将在代码中定义一个顶点结构,其中至少包含十二个浮点属性:顶点的三个空间坐标 (xyz)、顶点法线向量的三个分量、四个颜色分量(rgba)和纹理坐标的两个分量在那里。根据您的照明模型,您可以使用更多自己定义的属性,具体取决于硬件限制,最高可达 GL_MAX_VERTEX_ATTRIBS。

如何布局结构完全取决于您,因为您将执行所有照明计算;这些东西对 OpenGL ES 2.0 没有任何意义。您必须构建 GLSL 顶点和片段着色器程序来计算每个像素的颜色。您为定义的每个属性向量调用一次 glVertexAttribPointer,这会导致属性向量以您指定的名称传递给顶点着色器。您可以使用变量将其从顶点着色器传递给片段着色器。片段着色器中的属性值将由线性插值确定。

您通常还会传入一些带有更多照明参数的制服。对从一个顶点到下一个顶点没有变化的任何事物使用制服,例如光的位置或强度,或诸如光泽度之类的材料属性。对模型中变化的任何内容使用顶点属性。

大多数魔法应该发生在片段着色器中。使用法线向量的最简单的光照效果是漫反射光照,您可以使用这样的片段着色器生成它:

varying lowp vec4 FragmentPosition;
varying lowp vec3 FragmentNormal;
varying lowp vec4 FragmentColor;

uniform lowp vec3 LightPosition;
uniform lowp float LightIntensity;

void main() {
  // .xyz makes a vec3 out of a vec4
  lowp vec3 inLightFrame = FragmentPosition.xyz - LightPosition;
  lowp float distance = length(inLightFrame);
  // inverse square dependence of intensity
  gl_FragColor = max(0.0, -dot(inLightFrame, normalize(FragmentNormal))) 
    / distance / distance * LightIntensity * FragmentColor;
}

这假设 LightPosition 统一向量在软件中设置,并且顶点着色器如下:

attribute vec4 Position;
attribute vec3 Normal;
attribute vec4 Color;

varying vec4 FragmentPosition;
varying vec4 FragmentNormal;
varying vec4 FragmentColor;

void main() {
    FragmentPosition = Position;
    FragmentNormal = normalize(Normal);
    FragmentColor = Color;
}

另外,还会有一些调用glVertexAttribPointer来定义Position、Normal和Color,比如:

glVertexAttribPointer(glGetAttribLocation(programHandle, "Position"), 3, GL_FLOAT, 
  GL_FALSE, sizeof(Vertex), (GLvoid*(sizeof(float)*0));
glVertexAttribPointer(glGetAttribLocation(programHandle, "Normal"), 3, GL_FLOAT, 
  GL_FALSE, sizeof(Vertex), (GLvoid*(sizeof(float)*3));
glVertexAttribPointer(glGetAttribLocation(programHandle, "Color"), 4, GL_FLOAT, 
  GL_FALSE, sizeof(Vertex), (GLvoid*(sizeof(float)*6));

这假设一个结构像

typedef struct {
  float Position[3];
  float Normal[3];
  GLubyte Color[4];
} Vertex;

这个简单的例子假设单位强度的白光。您可能需要更多灯,并且您可能希望为每个灯指定红色、绿色和蓝色强度。此示例忽略了材质属性、环境光和镜面光以及阴影。但这就是您在 OpenGL ES 2.0 中处理法线向量的方式。

于 2014-02-26T15:03:00.877 回答