OpenGL把现实世界中的光照系统近似归为三部分,分别是光源、材质和光照环境。
光源就是光的来源,是“光”这种物质的提供者; 材质是指被光源照射的物体的表面的反射、漫反射(OpenGL不考虑折射)特性;
材质反映的是光照射到物体上后物体表现出来的对光的吸收、漫反射、反射等性能;
光照环境反应环境中所有光源发出的光经过无数次反射、漫反射之后整体环境所表现出来的光照效果。指定合适的光照环境参数可以使得最后形成的画面更接近于真实场景。
一、光源
光照模型
OpenGL中的光照模型中的反射光分为三个分量,分别是环境反射光(Ambient Light)、漫反射光(Diffuse Light)和镜面反射光(Specular Light)。
- 环境光Ambient:是由光源发出经环境多次散射而无法确定其入射方向的光,即似乎来自所有方向。其特征是入射方向和出射方向均为任意方向。
- 漫射光Diffuse:来自特定方向,它垂直于物体时比倾斜时更明亮。一旦它照射到物体上,则在各个方向上均匀地发散出去,效果为无论视点在哪里它都一样亮,其特征是入射方向唯一、出射方向为任意方向。
- 镜面光Specular:来自特定方向并沿另一方向反射出去,一个平行激光束在高质量的镜面上产生100%的镜面反射,其特征是入射方向和出射方向均唯一。
创建光源
OpenGL中用函数glLightfv来创建光源,函数原型是:
void glLightfv (GLenum light, GLenum pname, const GLfloat *params)
第二个参数pname指定光源特性,这个参数的具体信息见下表所示。
第三个参数设置相应的光源特性值。
例如下边定义了一个位置在(0,0,0),没有环境光,镜面反射光和漫反射光都为白光的光源:
GLfloat light_position[] = { 0.0, 0.0, 0.0, 0.0 };
GLfloat light_ambient [] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse [] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
光源的位置坐标采用齐次坐标(x, y, z, w)设置。第四个参数w为0.0时,定义相应的光源是定向光源,其所有的光线几乎是互相平行的,典型的如太阳光。光源方向由定义的坐标(x, y, z)指向(0,0,0);
w为1.0时,光源为定位光源。(x, y, z, w)指定光源在齐次坐标系下的具体位置,该位置会根据模型视点矩阵进行变换。
光源的衰减
离光源越远则光强越弱。由于定向光源是模拟的无穷远得光源,所以不会根据距离改变而衰减,所以在定向光源中是禁用衰减的; 对于定位光源有衰减。OpenGL的光衰减是通过光源的发光量乘以衰减因子来实现衰减的。
衰减系数=1/(K0+K1*D+K2*D2)
其中: D=光源位置与顶点之间的距离
K0= GL_CONSTANT_ATTENUATION //常数衰减因子
K1= GL_LINER_ATTENUATION // 线性衰减因子
K2= GL_QUADRATIC_ATTENUATION // 二次衰减因子
OpenGL中默认的衰减因子为(1,0,0),即不进行衰减。
设置好以上光源的属性之后,要使用glEnable(GL_LIGHTING)和glEnable(GL_LIGHTX)来启用光照和第X号光源。
二、 材质
OpenGL用材质对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。
物体的材质跟光源一样,也分为环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。其中对环境光和漫反射光的反射程度决定了物体的颜色。
比如对于一朵红色的花,它对绿色和蓝色的反射能力比较弱,对红色分量的反射能力比较强,所以呈现出来是红色。材质的镜面反射率在RGB三个分量上通常是一致的,即反射的光跟光源的颜色基本是一致的,只不过强度会减弱。镜面反射光的强度还取决于观察点的位置,当观察点正好处于入射光的反射光线上,亮斑的亮度达到最大值。
这3个材质的属性都是反应的物体对外界光线的反射情况,有些物体本身可以发射弱光,OpenGL中是通过设置材料的辐射光来实现的,可以使物体看起来像是发射出设定的辐射光一样,以达到特殊的效果。物体的辐射光使用glMaterialfv(GL_FRONT, GL_EMISSION, earth_mat_emission)来设置。
总体来说材质定义了物体对环境光、漫反射光、镜面光的反射(吸收)能力。我们看到的物体的颜色(或亮度)是光源的颜色(或亮度)经过物体的材质反射(吸收)之后发散出来的颜色(或亮度)。
定义材质的函数使用glMaterialfv,函数原型是:
void glMaterialfv (GLenum face, GLenum pname, const GLfloat *params);
第一个参数face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;
第二个参数pname指定正在设定的材质特性,这个参数的辅助信息下表所示:
第三个params参数设置相应的材质的特性值。
三、 光照环境
光照环境是指该环境中的光线不是来自于特定的光源,它是一个全局环境光,即那些在环境中经过了充分散射的光,这个光在环境中物体的各个表面上均匀泛射,环境中所有的物体都会受到该环境光的影响。
使用函数flLightModelfv来设定环境光,如下定义了一个微微发出蓝光的环境光,在这种情况下,即使环境中没有光源存在,也可以看到场景中的物体:
GLfloat mat_ambient[] = {0.0f, 0.0f, 0.2f, 1.0f};
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
总结一下在OpenGL中加入光照的步骤为:
- 1. 设置光源参数: 需要多次调用带有不同参数的glLightfv函数,设置环境光(Ambient Light)、漫射光(Diffuse Light)、镜面光(Specular Light)和光照位置(Position)等。
- 2. 开启光照,使用glEnable(GL_LIGHTING)和glEnable(GL_LIGHTXX)来开启光照和XX号光源,前者相当于光 源的总开关,后者是对应的开启相应号数的光源。 第3步是法线的设置,本文不涉及。
下图是对光照应用的例子,红色球代表太阳,材质的辐射光为红光,绿色球代表地球,绕太阳公转,材质的辐射光为蓝光,镜面反射光为红色(蓝色地球上的红色区域):
另一种演示效果,设置太阳的辐射演示为白色,地球的镜面反射为白色,漫反射为红色:
完整代码如下,可以设置不同的材质属性,查看不同的效果:
#include <gl/glut.h>
#include <Windows.h>
static GLfloat angle = 0.0f;
void myDisplay(void)
{
glClearColor(0.3,0.7,0.5,0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清理颜色和深度缓存
// 创建透视效果视图
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(80.0f, 1.0f, 1.0f, 20.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, 12.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}; //RGBA模式的环境光,为0
GLfloat sun_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //RGBA模式的漫反射光,全白光
GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; //RGBA模式下的镜面光 ,全白光
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}; //定义材质的环境光颜色,为0
GLfloat sun_mat_diffuse[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的漫反射光颜色,为0
GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的镜面反射光颜色,为0
GLfloat sun_mat_emission[] = {0.8f, 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(3.0, 40, 32);
}
// 定义地球的材质并绘制地球
{
GLfloat earth_mat_ambient[] = {0.0f, 0.0f, 1.0f, 1.0f}; //定义材质的环境光颜色,骗蓝色
GLfloat earth_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f}; //定义材质的漫反射光颜色,偏蓝色
GLfloat earth_mat_specular[] = {1.0f, 0.0f, 0.0f, 1.0f}; //定义材质的镜面反射光颜色,红色
GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的辐射光颜色,为0
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(7.0f, 0.0f, 0.0f);
glutSolidSphere(3.0, 40, 32);
}
Sleep(10);
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(400, 400);
glutCreateWindow("OpenGL光照演示");
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);
glutMainLoop();
return 0;
}