OpenGL---------光照的基本知识

从生理学的角度上讲,眼睛之因此看见各类物体,是由于光线直接或间接的从它们那里到达了眼睛。人类对于光线强弱的变化的反应,比对于颜色变化的反应来得灵敏。所以对于人类而言,光线很大程度上表现了物体的立体感。
请看图1,图中绘制了两个大小相同的白色球体。其中右边的一个是没有使用任何光照效果的,它看起来就像是一个二维的圆盘,没有立体的感受。左边的一个是使用了简单的光照效果的,咱们经过光照的层次,很容易的认为它是一个三维的物体。

OpenGL对于光照效果提供了直接的支持,只须要调用某些函数,即可以实现简单的光照效果。可是在这以前,咱们有必要了解一些基础知识。
1、创建光照模型
在现实生活中,某些物体自己就会发光,例如太阳、电灯等,而其它物体虽然不会发光,但能够反射来自其它物体的光。这些光经过各类方式传播,最后进入咱们的眼睛——因而一幅画面就在咱们的眼中造成了。
就目前的计算机而言,要准确模拟各类光线的传播,这是没法作到的事情。好比一个四面都是粗糙墙壁的房间,一盏电灯所发出的光线在很短的时间内就会通过很是屡次的反射,最终几乎布满了房间的每个角落,这一过程即便使用目前运算速度最快的计算机,也没法精确模拟。不过,咱们并不须要精确的模拟各类光线,只须要找到一种近似的计算方式,使它的最终结果让咱们的眼睛认为它是真实的,这就能够了。
OpenGL在处理光照时采用这样一种近似:把光照系统分为三部分,分别是光源、材质和光照环境。光源就是光的来源,能够是前面所说的太阳或者电灯等。材质是指接受光照的各类物体的表面,因为物体如何反射光线只由物体表面决定(OpenGL中没有考虑光的折射),材质特色就决定了物体反射光线的特色。光照环境是指一些额外的参数,它们将影响最终的光照画面,好比一些光线通过屡次反射后,已经没法分清它到底是由哪一个光源发出,这时,指定一个“环境亮度”参数,可使最后造成的画面更接近于真实状况。
在物理学中,光线若是射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。光线若是射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。现实生活中的物体在反射光线时,并非绝对的镜面反射或漫反射,但能够当作是这两种反射的叠加。对于光源发出的光线,能够分别设置其通过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也能够分别设置光线通过镜面反射和漫反射后的光线强度。这些因素综合起来,就造成了最终的光照效果。

2、法线向量
在OpenGL中,法线的方向是用一个向量来表示。不幸的是,OpenGL并不会根据你所指定的多边形各个顶点来计算出这些多边形所构成的物体的表面的每一个点的法线(这话听着有些迷糊),一般,为了实现光照效果,须要在代码中为每个顶点指定其法线向量。
指定法线向量的方式与指定颜色的方式有雷同之处。在指定颜色时,只须要指定每个顶点的颜色,OpenGL就能够自行计算顶点之间的其它点的颜色。而且,颜色一旦被指定,除非再指定新的颜色,不然之后指定的全部顶点都将以这一贯量做为本身的颜色。在指定法线向量时,只须要指定每个顶点的法线向量,OpenGL会自行计算顶点之间的其它点的法线向量。而且,法线向量一旦被指定,除非再指定新的法线向量,不然之后指定的全部顶点都将以这一贯量做为本身的法线向量。使用glColor*函数能够指定颜色,而使用glNormal*函数则能够指定法线向量。
注意:使用glTranslate*函数或者glRotate*函数能够改变物体的外观,但法线向量并不会随之改变。然而,使用glScale*函数,对每一坐标轴进行不一样程度的缩放,颇有可能致使法线向量的不正确,虽然OpenGL提供了一些措施来修正这一问题,但由此也带来了各类开销。所以,在使用了法线向量的场合,应尽可能避免使用glScale*函数。即便使用,也最好保证各坐标轴进行等比例缩放。
3、控制光源
在OpenGL中,仅仅支持有限数量的光源。使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推,OpenGL至少会支持8个光源,即GL_LIGHT0到GL_LIGHT7。使用glEnable函数能够开启它们。例如,glEnable(GL_LIGHT0);能够开启第0号光源。使用glDisable函数则能够关闭光源。一些OpenGL实现可能支持更多数量的光源,但总的来讲,开启过多的光源将会致使程序运行速度的严重降低,玩过3D Mark的朋友可能多少也有些体会。一些场景中可能有成百上千的电灯,这时可能须要采起一些近似的手段来进行编程,不然以目前的计算机而言,是没法运行这样的程序的。
每个光源均可以设置其属性,这一动做是经过glLight*函数完成的。glLight*函数具备三个参数,第一个参数指明是设置哪个光源的属性,第二个参数指明是设置该光源的哪个属性,第三个参数则是指明把该属性值设置成多少。光源的属性众多,下面将分别介绍。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性表示了光源所发出的光的反射特性(以及颜色)。每一个属性由四个值表示,分别表明了颜色的R, G, B, A值。GL_AMBIENT表示该光源所发出的光,通过很是屡次的反射后,最终遗留在整个光照环境中的强度(颜色)。GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时通过漫反射,所获得的光的强度(颜色)。GL_SPECULAR表示该光源所发出的光,照射到光滑表面时通过镜面反射,所获得的光的强度(颜色)。
(2)GL_POSITION属性。表示光源所在的位置。由四个值(X, Y, Z, W)表示。若是第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。这种光源称为方向性光源,一般,太阳能够近似的被认为是方向性光源。若是第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。对于位置性光源,设置其位置与设置多边形顶点的方式类似,各类矩阵变换函数例如:glTranslate*、glRotate*等在这里也一样有效。方向性光源在计算时比位置性光源快了很多,所以,在视觉效果容许的状况下,应该尽量的使用方向性光源。
(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性。表示将光源做为聚光灯使用(这些属性只对位置性光源有效)。不少光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,好比手电筒,只向一个较小的角度发射光线。GL_SPOT_DIRECTION属性有三个值,表示一个向量,即光源发射的方向。GL_SPOT_EXPONENT属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。数值越大,聚光效果就越明显。GL_SPOT_CUTOFF属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半(见图2),其取值范围在0到90之间,也能够取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。

(4)GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION属性。这三个属性表示了光源所发出的光线的直线传播特性(这些属性只对位置性光源有效)。现实生活中,光线的强度随着距离的增长而减弱,OpenGL把这个减弱的趋势抽象成函数:
衰减因子 = 1 / (k1 + k2 * d + k3 * k3 * d)
其中d表示距离,光线的初始强度乘以衰减因子,就获得对应距离的光线强度。k1, k2, k3分别就是GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION。经过设置这三个常数,就能够控制光线在传播过程当中的减弱趋势。

属性还真是很多。固然了,若是是使用方向性光源,(3)(4)这两类属性就不会用到了,问题就变得简单明了。
4、控制材质
材质与光源类似,也须要设置众多的属性。不一样的是,光源是经过glLight*函数来设置的,而材质则是经过glMaterial*函数来设置的。
glMaterial*函数有三个参数。第一个参数表示指定哪一面的属性。能够是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。分别表示设置“正面”“背面”的材质,或者两面同时设置。(关于“正面”“背面”的内容须要参看前些课程的内容)第2、第三个参数与glLight*函数的第2、三个参数做用相似。下面分别说明glMaterial*函数能够指定的材质属性。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性与光源的三个对应属性相似,每一属性都由四个值组成。GL_AMBIENT表示各类光线照射到该材质上,通过不少次反射后最终遗留在环境中的光线强度(颜色)。GL_DIFFUSE表示光线照射到该材质上,通过漫反射后造成的光线强度(颜色)。GL_SPECULAR表示光线照射到该材质上,通过镜面反射后造成的光线强度(颜色)。一般,GL_AMBIENT和GL_DIFFUSE都取相同的值,能够达到比较真实的效果。使用GL_AMBIENT_AND_DIFFUSE能够同时设置GL_AMBIENT和GL_DIFFUSE属性。
(2)GL_SHININESS属性。该属性只有一个值,称为“镜面指数”,取值范围是0到128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也能够产生较大的亮点。该值越大,表示材质越相似于镜面,光源照射到上面后,产生较小的亮点。
(3)GL_EMISSION属性。该属性由四个值组成,表示一种颜色。OpenGL认为该材质自己就微微的向外发射光线,以致于眼睛感受到它有这样的颜色,但这光线又比较微弱,以致于不会影响到其它物体的颜色。
(4)GL_COLOR_INDEXES属性。该属性仅在颜色索引模式下使用,因为颜色索引模式下的光照比RGBA模式要复杂,而且使用范围较小,这里不作讨论。
5、选择光照模型
这里所说的“光照模型”是OpenGL的术语,它至关于咱们在前面提到的“光照环境”。在OpenGL中,光照模型包括四个部分的内容:全局环境光线(即那些充分散射,没法分清究竟来自哪一个光源的光线)的强度、观察点位置是在较近位置仍是在无限远处、物体正面与背面是否分别计算光照、镜面颜色(即GL_SPECULAR属性所指定的颜色)的计算是否从其它光照计算中分离出来,并在纹理操做之后在进行应用。
以上四方面的内容都经过同一个函数glLightModel*来进行设置。该函数有两个参数,第一个表示要设置的项目,第二个参数表示要设置成的值。
GL_LIGHT_MODEL_AMBIENT表示全局环境光线强度,由四个值组成。
GL_LIGHT_MODEL_LOCAL_VIEWER表示是否在近处观看,如果则设置为GL_TRUE,不然(即在无限远处观看)设置为GL_FALSE。
GL_LIGHT_MODEL_TWO_SIDE表示是否执行双面光照计算。若是设置为GL_TRUE,则OpenGL不只将根据法线向量计算正面的光照,也会将法线向量反转并计算背面的光照。
GL_LIGHT_MODEL_COLOR_CONTROL表示颜色计算方式。若是设置为GL_SINGLE_COLOR,表示按一般顺序操做,先计算光照,再计算纹理。若是设置为GL_SEPARATE_SPECULAR_COLOR,表示将GL_SPECULAR属性分离出来,先计算光照的其它部分,待纹理操做完成后再计算GL_SPECULAR。后者一般可使画面效果更为逼真(固然,若是自己就没有执行任何纹理操做,这样的分离就没有任何意义)。

6、最后的准备
到如今能够说是完事俱备了。不过,OpenGL默认是关闭光照处理的。要打开光照处理功能,使用下面的语句:
glEnable(GL_LIGHTING);
要关闭光照处理功能,使用glDisable(GL_LIGHTING);便可。
7、示例程序
到如今,咱们已经能够编写简单的使用光照的OpenGL程序了。
咱们仍然以太阳、地球做为例子(此次就不考虑月亮了^-^),把太阳做为光源,模拟地球围绕太阳转动时光照的变化。因而,须要设置一个光源——太阳,设置两种材质——太阳的材质和地球的材质。把太阳光线设置为白色,位置在画面正中。把太阳的材质设置为微微散发出红色的光芒,把地球的材质设置为微微散发出暗淡的蓝色光芒,而且反射蓝色的光芒,镜面指数设置成一个比较小的值。简单起见,再也不考虑太阳和地球的大小关系,用一样大小的球体来代替之。
关于法线向量。球体表面任何一点的法线向量,就是球心到该点的向量。若是使用glutSolidSphere函数来绘制球体,则该函数会自动的指定这些法线向量,没必要再手工指出。若是是本身指定若干的顶点来绘制一个球体,则须要本身指定法线响亮。
因为咱们使用的太阳是一个位置性光源,在设置它的位置时,须要利用到矩阵变换。所以,在设置光源的位置之前,须要先设置好各类矩阵。利用gluPerspective函数来建立具备透视效果的视图。咱们也将利用前面课程所学习的动画知识,让整个画面动起来。

下面给出具体的代码:程序员

#include <gl/glut.h>

#define WIDTH 400
#define HEIGHT 400

static GLfloat angle = 0.0f;

void myDisplay(void)
{
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

     // 建立透视效果视图
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     gluPerspective(90.0f, 1.0f, 1.0f, 20.0f);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     gluLookAt(0.0, 5.0, -10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

     // 定义太阳光源,它是一种白色的光源
     {
     GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
     GLfloat sun_light_ambient[]   = {0.0f, 0.0f, 0.0f, 1.0f};
     GLfloat sun_light_diffuse[]   = {1.0f, 1.0f, 1.0f, 1.0f};
     GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};

     glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);
     glLightfv(GL_LIGHT0, GL_AMBIENT,   sun_light_ambient);
     glLightfv(GL_LIGHT0, GL_DIFFUSE,   sun_light_diffuse);
     glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);

     glEnable(GL_LIGHT0);
     glEnable(GL_LIGHTING);
     glEnable(GL_DEPTH_TEST);
     }

     // 定义太阳的材质并绘制太阳
     {
         GLfloat sun_mat_ambient[]   = {0.0f, 0.0f, 0.0f, 1.0f};
         GLfloat sun_mat_diffuse[]   = {0.0f, 0.0f, 0.0f, 1.0f};
         GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
         GLfloat sun_mat_emission[] = {0.5f, 0.0f, 0.0f, 1.0f};
         GLfloat sun_mat_shininess   = 0.0f;

         glMaterialfv(GL_FRONT, GL_AMBIENT,    sun_mat_ambient);
         glMaterialfv(GL_FRONT, GL_DIFFUSE,    sun_mat_diffuse);
         glMaterialfv(GL_FRONT, GL_SPECULAR,   sun_mat_specular);
         glMaterialfv(GL_FRONT, GL_EMISSION,   sun_mat_emission);
         glMaterialf (GL_FRONT, GL_SHININESS, sun_mat_shininess);

         glutSolidSphere(2.0, 40, 32);
     }

     // 定义地球的材质并绘制地球
     {
         GLfloat earth_mat_ambient[]   = {0.0f, 0.0f, 0.5f, 1.0f};
         GLfloat earth_mat_diffuse[]   = {0.0f, 0.0f, 0.5f, 1.0f};
         GLfloat earth_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
         GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
         GLfloat earth_mat_shininess   = 30.0f;

         glMaterialfv(GL_FRONT, GL_AMBIENT,    earth_mat_ambient);
         glMaterialfv(GL_FRONT, GL_DIFFUSE,    earth_mat_diffuse);
         glMaterialfv(GL_FRONT, GL_SPECULAR,   earth_mat_specular);
         glMaterialfv(GL_FRONT, GL_EMISSION,   earth_mat_emission);
         glMaterialf (GL_FRONT, GL_SHININESS, earth_mat_shininess);

         glRotatef(angle, 0.0f, -1.0f, 0.0f);
         glTranslatef(5.0f, 0.0f, 0.0f);
         glutSolidSphere(2.0, 40, 32);
     }

     glutSwapBuffers();
}
void myIdle(void)
{
     angle += 1.0f;
     if( angle >= 360.0f )
         angle = 0.0f;
     myDisplay();
}

int main(int argc, char* argv[])
{
     glutInit(&argc, argv);
     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
     glutInitWindowPosition(200, 200);
     glutInitWindowSize(WIDTH, HEIGHT);
     glutCreateWindow("OpenGL光照演示");
     glutDisplayFunc(&myDisplay);
     glutIdleFunc(&myIdle);
     glutMainLoop();
     return 0;
}

小结:
本课介绍了OpenGL光照的基本知识。编程

OpenGL把光照分解为光源、材质、光照模式三个部分,根据这三个部分的各类信息,以及物体表面的法线向量,能够计算获得最终的光照效果。
光源、材质和光照模式都有各自的属性,尽管属性种类繁多,但这些属性都只用不多的几个函数来设置。使用glLight*函数可设置光源的属性,使用glMaterial*函数可设置材质的属性,使用glLightModel*函数可设置光照模式。
GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR这三种属性是光源和材质所共有的,若是某光源发出的光线照射到某材质的表面,则最终的漫反射强度由两个GL_DIFFUSE属性共同决定,最终的镜面反射强度由两个GL_SPECULAR属性共同决定。
可使用多个光源来实现各类逼真的效果,然而,光源数量的增长将形成程序运行速度的明显降低。
在使用OpenGL光照过程当中,属性的种类和数量都很是繁多,一般,须要不少的经验才能够熟练的设置各类属性,从而造成逼真的光照效果。(各位也看到了,其实这个课程的示例程序中,属性设置也不怎么好)。然而,设置这些属性的艺术性远远超过了技术性,每每是一些美术制做人员设置好各类属性(并保存为文件),而后由程序员编写的程序去执行绘制工做。所以,即便目前没法熟练运用各类属性,也没必要过于担忧。若是条件容许,能够玩玩相似3DS MAX之类的软件,对理解光照、熟悉各类属性设置会有一些帮助。
在课程的最后,咱们给出了一个样例程序,演示了太阳和地球模型中的光照效果。

函数

相关文章
相关标签/搜索