环境光具有自己的用途。但是,对于绝大多数视图对现实世界进行建模的应用程序而言,必须指定一个或多个特定的光源。除了强度颜色和颜色之外,这些光源具有位置和方向。这些光源可以极大的影响场景的外光。
OpenGL至少支持8中独立的光源,他们可以出现在场景的任何地方或者视景体之外。可以把一个光源看成位于位于无限远处,并把它的光线看成是平行的。或则,我们也可以把光源放在附近,让他的光线向四周发射。还可以指定一个聚光灯,从他发射出一道特定的光锥,并且对它的特征进行操纵。
下面是一个例子:
void RenderInit()
{
GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};
GLfloat mat_shininess[] = {50.0};
GLfloat white_light[] = {1.0, 1.0, 1.0, 1.0};
GLfloat lmodel_ambient[] = {0.1, 0.1, 0,1, 1.0};
glClearColor(0.0, 0.0, 0.0, 1.0);
glShadeModel(GL_SMOOTH);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(xRot, 1.0f, 0.0f, 0.0f);
glRotatef(yRot, 0.0f, 1.0f, 0.0f);
GLfloat light_position[] = {0, 0, 100, 0.0};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glPopMatrix();
glPushMatrix();
glutSolidSphere(100, 40, 40);
glPopMatrix();
glutSwapBuffers();
}
程序运行结果,如下图:
指定材料属性:
void glMaterialv(GLenum face, GLenum pname, TYPE *param);
参数face指物体那个面,可以取值GL_FRONT、GL_BACK、GL_FRONT_AND_BACK;pname指出要设置的哪种材质属性;param为要设置的属性值,是一个指向数组的指针(向量版本)或一个数值(非向量版本)。
pname可能取值和相对param的默认值,如下:
参数值 |
默认值 |
意义 |
GL_AMBIENT |
(0.2,0.2,0.2,1.0) |
材质的环境颜色 |
GL_DIFFUSE |
(0.8,0.8,0.8,1.0) |
材质的散射颜色 |
GL_AMBIENT_AND_DIFFUSE |
|
材质的环境颜色和散射颜色 |
GL_SPECULAR |
(0.0,0.0,0.0,1.0) |
材质的镜面反射颜色 |
GL_SHININESS |
0.0 |
镜面反射指数 |
GL_EMISSION |
(0.0,0.0,0.1,1.0) |
材质的发射光颜色 |
GL_COLOR_INDEXES |
(0, 1, 1) |
环境颜色索引、散射颜色索引和镜面反射颜色索引 |
指定光源属性:
void glLightfv(GLenum light, GLenum pname, const GLfloat *params);
参数light指定光源,OpenGL中至少支持8个光源GL_LIGHT0....GL_LIGHT7;
pname是参数类型,常见参数及默认值:
参数值 |
默认值 |
意义 |
GL_AMBIENT |
(0.0,0.0,0.0,1.0) |
环境光强度 |
GL_DIFFUSE |
(1.0,1.0,1.0,1.0) |
散射光强度 |
GL_SPECULAR |
(1.0,1.0,1.0,1.0) |
反射光强度 |
GL_POSIZION |
(0.0,0.0,1.0,0.0) |
光源位置 |
光照模型:
OpenGL的光照模型参数包括4个部分:
(1)全局环境光强度
(2)观察点位于场景中还是无限远处
(3)物体正反两面是否执行不同的光照计算
(4)镜面光颜色是否从环境和漫反射颜色中分离出来,并在纹理操作后在应用
void APIENTRY glLightModelfv (GLenum pname, const GLfloat *params);
参数及默认值:
参数值 |
默认值 |
意义 |
GL_LIGHT_MODEL_AMBIENT |
(0.2,0.2,0.2,1.0) |
整个场景中的环境光强度 |
GL_LIGHT_MODEL_LOCAL_VIEWER |
0.0/GL_FALSE |
场景中观察点,默认无限远处 |
GL_LIGHT_MODEL_TWO_SIDED |
0.0/GL_FALSE |
单独对多边形两面进行明暗处理 |
GL_LIGHT_MODEL_COLOR_CONTROL |
GL_SINGLE_COLOR(默认)、GL_SEPATATE_SPECULAR_COLOR |
镜面光是否与慢反射和环境光分开计算 |
环境光依赖于每个光源的颜色,因此需要对每个光源指定环境光强度;OpenGL中也可以定义一个对测试非常有用的全局环境光:
GLfloat lmodel_ambient[] = {0.1, 0.1, 0,1, 1.0};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
启用光源:
明暗处理的计算由下述命令启用:
glEnable(GL_LIGHTING);
当光照被激活后,glColor()命令将被忽略
必须单独激活每一个光源:
glEnable(GL_LIGHT0);
法线向量:
物体的法线向量定义了它的表面在空间中的朝向,即,定义了表面相对于光源的方向。因为OpenGL是使用法线向量来确定一个物体表面的某个顶点所接受的光照。
指定法线向量:
void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz);
void glNormal3fv(const GLfloat *v);
在OpenGL实现中,所有的表面法线最终必须转换为单位法线。单位法线的长度为1的法线向量。可以让OpenGL自动把一条法线向量转换为单位法线:
glEnable(GL_NORMALIZE);
但是,这种方法在效率上有所牺牲。我们最好自己预先计算单位法线,而不是依赖OpenGL自动执行。
应该注意,调用glScale转换函数是还会对法线的长度进行缩放。如果使用glScale和光照可能会在OpenGL中看到不希望看到的结果。这时候就需要使用新的方法,使用GL_RESCALE_NORMALS替代GL_NORMALIZE:
glEnable(GL_RESCALE_NORMALS);
在Glut提供的绘制函数中法线向量在函数中已经设置好了,如glutSolidSphere。如果需要自己实现物体的绘制,就需要自己设置法线向量了,下面说明一种法线的计算方法:
如下图,显示3个点,P1,P2,P3,可以定义两个向量:从P1到P2的向量V1,从P1到P3的向量V2。从数学的角度,三维空间两个向量定义一个平面。可以得到该平面的法相向量为V3=V1 X V2。
假设V1 = (a1, b1, c1),V2 = (a2, b2, c2)
得到V3 = (b1xc2-c1xb2, a1xc2-c1xa2, a1xb2-b1xa2)
计算法线向量方法的代码如下:
typedef float M3DVector3f[3];
void m3dFindNormal(M3DVector3f normal, const M3DVector3f p1, const M3DVector3f p2, const M3DVector3f p3)
{
M3DVector3f v1, v2;
v1[0] = p3[0] - p2[0];
v1[1] = p3[1] - p2[1];
v1[2] = p3[2] - p2[2];
v2[0] = p1[0] - p2[0];
v2[1] = p1[1] - p2[1];
v2[2] = p1[2] - p2[2];
m3dCrossProduct(normal, v1, v2);
}
void m3dCrossProduct(M3DVector3f normal, const M3DVector3f v1, const M3DVector3f v2)
{
normal[0] = v1[1]*v2[2] - v1[2]*v2[1];
normal[1] = v1[0]*v2[2] - v1[2]*v2[0];
normal[2] = v1[0]*v2[1] - v1[1]*v2[0];
}