在使用VBOS时,如何将法线向量信息传输到OpenGL ES 2.0?

时间:2013-05-06 17:38:17

标签: opengl-es-2.0

更新:现在看来大部分内容都已消失。我认为在OpenGL ES 2.0中正确进行照明的方法是编写自己的着色器,以任何方式传递法线,然后在着色器中进行照明。特别是,根据OpenGL ES 2.0文档,不再有glMaterialFv,glLightFv等。(更不用说glNormalPointer和朋友了。)我认为这是为我编译的原因是因为这些东西是由iOS提供的GLKit,虽然显然他们并不是真正的开箱即用替代品。但是,GLKit提供了一些标准着色器来模仿OpenGL ES 1.1中的照明功能。

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


我是OpenGL的新手,所以提前为任何混淆道歉。我在iOS上使用OpenGL ES 2.0。我在互联网上找到的许多例子似乎已经过时了。例如,glBegin(GL_POLYGON)方法似乎已被弃用,因此我不认为我可以使用glNormal3f,正如大多数旧例子所做的那样。

我从Ray Wenderlich的好tutorial开始,并将其与我建造的游戏集成,成功渲染了一些房间的基本几何形状。现在我只想添加灯光。我不确定什么不起作用,但我怀疑它与法线有关。

我做了一个混乱的尝试来启用GL_LIGHT0,主要来自OpenGL documentation

//* 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:方法中(我修改了顶点结构以包括位置和颜色位置之间的法线)。

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中的情况有所不同,但是从来没有任何详细的例子。提前谢谢。

1 个答案:

答案 0 :(得分:5)

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

您通常在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中处理法向量的方法。