这一章将在你的程序中加入灯光,使场景看起来和真实场景一样。
灯光
opengl中灯光分为好几种,都可以加入到你的场景中。
Ambiend Light 环境光
环境光没有确切的来源方向,当环境光照射到物体时,光被反射到各个方向。
Diffuse Light 漫射光
漫射光不同于环境光,它来自某个方向,但和环境光一样,照射到物体时,会被反射到各个方向。
Specular Light 镜面光
镜面光和漫射光一样是有方向的,但它反射方向是一定的,而不像漫射光一样反射到各个方向。所以当镜面光照射到物体时,你会看到物体表面被照射的亮点。
Emissive Light 发射光
它来自于某一物体,该物体散发出大量的光,但不会被任何物体面反射。
为了更好的理解这几种光,我从网络上摘抄了一段定义:
* 环境光——经过多次反射而来的光称为环境光,无法确定其最初的方向,但当特定的光源关闭后,它们将消失.
* 全局环境光——它们并非来自特定的光源,这些光经过了多次散射,已经无法确定其光源位于何处.
* 散射光——来自同一方向,照射到物体表面后,将沿各个方向均匀反射,因此,无论从哪个方向观察,表面的亮度都相同.
* 镜面反射光——来自特定方向,也被反射到特定方向.镜面反射度与之相关.
* 材质发射光——用于模拟发光物体.在OpenGL光照模型中,表面的发射光增加了物体的亮度,它不受光源的影响,另外,发射光不会给整个场景中增加光线.
材质
你不光可以设置光的属性,而且还可以指定不同的面对光照作出的反应,这就要指定材质属性。
这就指定了一个面对光源反射多少。
法线
法线是一个向量垂直于(90度)某一特定面,就称这个向量是某个面的法线。法线可以用于光的计算。如果你想让画出的物体对光源产生影响,那么必须指定物体每个面的法线。下面将会说明。
另一个需要注意的一点是,法线要单位化,我们不会深入数学计算,这不是我们这章的目的。如果需要会在将来某章中讲解。简明的说,一个向量的长度等于各个向量分量的平方和的平方根,再把每个向量的分量除以这个值。现在不需要过多担心这个。
程序代码:
下面定义两个颜色数组,一个用于环境光,一个用于漫射光,它们是光源的颜色值。
float lightAmbient[] = { 0.2f, 0.3f, 0.6f, 1.0f };
float lightDiffuse[] = { 0.2f, 0.3f, 0.6f, 1.0f };
下面创建一个材质属性数组,分别用于环境光和漫射光。
用材质属性值乘以光源值得出面的反射颜色值,下面的值将会导致面反射的光失去接收光的百分之四十。每个值表示特定颜色被反射的数量。
float matAmbient[] = { 0.6f, 0.6f, 0.6f, 1.0f };
float matDiffuse[] = { 0.6f, 0.6f, 0.6f, 1.0f };
void init()
{
首先先启用光源,这样光才会在场景中起作用。
glEnable(GL_LIGHTING);
opengl最多允许8个光源,要使用某个光源,需要使用glEnable打开它,光源的编号是GL_LIGHTX,X的值是0---7。
指定材质属性,可以使用glMaterialfv和glMaterialf ,glMaterialfv接受向量数组,而glMaterialf只接受一个向量。第一个参数指定那个面被更新,在opengl es中只可以使用GL_FRONT_AND_BACK,其他参数不起作用。之所以存在这个参数,是因为opengl可以设置多个参数。
第二个参数指定光源的类型,GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION 和 GL_AMBIENT_AND_DIFFUSE.
最后一个参数指定一个数组或单个值,取决于你使用的哪个函数。
下一行设置面的材质属性:
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matDiffuse);
灯光的设置和材质的设置相同,使用glLightfv或glLightf函数:
glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
init函数没有发生改变:
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepthf(1.0f);
glVertexPointer(3, GL_FLOAT, 0, box);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
}
display函数的开头部分没有发生改变:
void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAtf(
0.0f, 0.0f, 3.0f,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
前面我们讨论了法线,法线是垂直于面的,所以前平面的法线是(0, 0, 1),后平面的法线是(0, 0, -1),两个法线的长度为1,所以不用再单位化。
法线由glNormal3f 函数指定,并在渲染时调用。这个函数由3个float类型的数据组成单位化的向量。
// FRONT AND BACK
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glNormal3f(0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glNormal3f(0.0f, 0.0f, -1.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
其他页面设置也同上,
// LEFT AND RIGHT
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glNormal3f(-1.0f, 0.0f, 0.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
glNormal3f(1.0f, 0.0f, 0.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
// TOP AND BOTTOM
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glNormal3f(0.0f, 1.0f, 0.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
glNormal3f(0.0f, -1.0f, 0.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
glFlush();
glutSwapBuffers();
}
最后菜单增加一项彩色材质,这项选择打开或关闭色彩跟踪。色彩跟踪根据当前面的色彩反色不同色的光。
case 2 :
if (glIsEnabled(GL_COLOR_MATERIAL))
glDisable(GL_COLOR_MATERIAL);
else
glEnable(GL_COLOR_MATERIAL);
break;
下面两张图是程序的运行结果,分别是普通灯光和色彩追踪的效果。
普通灯光 色彩跟踪
现在学会了在场景中添加灯光,它提供了灵活的设置,使得场景更加真实。