OpenGL编程指南第五章:光照

来源:互联网 发布:大麦户源码php 编辑:程序博客网 时间:2024/05/16 09:58
1、隐藏面移除

opengl在渲染的时候,需要绘出离视点近的物体,移除被遮挡的远处的物体。2D绘图里面一般通过控制绘制的顺序是来达到目的。3D场景要复杂得多,可能存在相互部分遮挡的情况,改变视角也会改变遮挡的情况,opengl通过Depth Buffer来达到消除隐藏部分的目的。

Depth Buffer存储每个像素的深度值(一般为片段离近剪切面的距离)。初始时或glClear(GL_DEPTH_BUFFER_BIT),depth buffer被初始化成最大值(一般为远剪切面的深度);在fragment操作视,会对比其深度值和buffer中对应位置的深度值,如果小于,才继续操作并替换该深度值,否则直接丢弃。要激活这个功能,调用glEnable(GL_DEPTH_TEST)。

2、关照系统

在真实世界中,物体表面所呈现的颜色,取决于其反射的光线颜色;光源(或多个光源)照射到物体表面的光线,有些被物体吸收,有些被反射;有些物体表面比较光滑,会把入射光线往特定的方向反射,某些则往各个方向散射,大部分物体基于两者之间。OpenGL通过把光分解为RGB三个分量来模拟光照系统:光源的颜色取决于其RBG三个分量的强度,物体表面的材质定义为其往各个方向所反射的RBG三个分量的百分比。

OpenGL中,光来自于相互独立的若干光源。物体所受的光或者来自某个特定的方向或位置,或者均匀分布于环境当中。比如房间内有一盏灯,那么房间内物体所受的大部分光来自于这盏灯;但是还有一些光经过了多次的反射已经无法分别其方向了,但是这些光还是来自这盏灯的,如果这个光源关闭,这些光会消失。还有,环境本身有一些光,这些光无法确定其来源和方向。


环境光(ambient),漫射光(diffuse),反射光(specular),放射光(emisive)
物体所受的光可以分成以上四个部分,这个四个部分会被单独计算然后再结合在一起。
环境光:均匀散布于环境当中,无法区分方向;环境光与物体面相接触会往各个方向均匀反射;
漫射光:来自某个方向,如果直射的话会显得更加明亮,与物体面接触后会往各个方向均匀放射;PS:可以看出,这种光对物体的作用与入射角度是相关的,但与观察角度无关。
反射光:来自某个方向,往某个方向反射,起高光作用;
放射光:物体本省释放的光,影响物体自身,不与光源相互作用,也不会给环境增加光。

材料颜色(Material Color)

材料颜色实际上是材料对光的反射比,同样分成ambient,diffuse,specular三种,每种又按RGB分别取值。ambient值与光照ambient值相结合,同样diffuse值于光照diffuse值相结合,specular值与光照specular值相结合。材料的ambient和diffuse一般来说是一样的,这两个分量的反射(漫射)决定了物体的颜色;而specular值一般就是白色或灰色,也即反射后的颜色即为光照的颜色,用于产生高光效果。打个比方,一个红球在白色光照下,主要是呈红色,高光处程白色。

颜色计算

光的颜色RGB和一般颜色RGB值意义相同,而材料的颜色RGB值代表了对光的反射比,前者可用(LR,LG,LB)表示,而后者可用(MR,MG,MB)表示,那么物体反射的颜色就是(LR*MR,LG*MG,LB*MB)。如果有两个光源,分别给物体产生(R1,G1,B1)和(R2,G2,B2)两种颜色,那么最终物体的颜色是(R1+R2,G1+G2,B1+B2),如果某个值大于1.0,那么被限制为1.0。

法线

表面法线定义了表面和光源的相方向,会影响到光照的效果;这里要求法线是规范化的,你需要调用glEnable(GL_NORMALIZE)和glEnable(GL_RESCALE_NORMAL)来开启法线自动规范化。


3、创建光源

首先要glEnable(GL_LIGHTING)激活光照系统;再glEnable(GL_LIGHTi)激活第i个光源。然后就是定义光源的属性,通过glLight*(GLenum light, GLenum pname, TYPE param)和glLight*v(GLenum light, GLenum pname, const TYPE *param)两个API来设定属性值:light是光源身份也即GL_LIGHT0,GL_LIGHT1...,pname是属性名;param是属性值。
属性列表及其默认值请参考原书,值得注意
的是GL_DIFFUSE和GL_SPECULAR两个属性的默认值对GL_LIGHT0和其他光源不一样。

Color

给可以给光源的GL_AMBIENT,GL_DIFFUSE,GL_SPECULAR三个分量设定颜色值,比如:
GLfloat light_ambient[] = { 0.0, 0.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);


位置和方向

从位置的角度讲,有两种光源,一种是directional光源,它位于无穷远处,所有光线的方向是一致的,也不会衰减,比如“太阳光”。另一种是positional光源,它处于场景中,它的具体位置会影响光照效果
给光源设定位置的API如下:
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
position的第四个分量如果是0,那么光源就是directional的,postion值实际是一个向量;否则这个光源就是postional,position就是他的位置。光源的位置和方向都会受ModelView Matrix的影响。

衰减

物体离光源越远,光的强度会衰减,对于directional光源来说衰减是没有意义的,所以衰减是针对positional光源来说的。衰减就是用光源的强度乘以一个衰减因子,衰减因子的公式如下:

d = 顶点和光源的距离
Kc = GL_CONSTANT_ATTENUATION
Kl = GL_LINEAR_ATTENUATION
Kq = GL_QUADRATIC_ATTENUATION
Kc的默认值是1.0,Kl和Kq的默认值是0,可以通过API设定:
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5);


聚光
可以给positional光源添加聚光效果,聚光就是指定光源照射的方向和范围(一个椎体):
GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
上面指定了光源照射的方向,也即椎体的轴线方向;
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
上面制定了椎体边界和周线之间的夹角,如果角度为180的话,相当于没有聚光效果。

还可以给聚光增加中轴到边缘的衰减效果,通过GL_SPOT_EXPONENT属性。


控制光源位置和方向
OpenGL对待光源位置和方向与对待顶点位置和法线是一样的,都会被ModelView Matrix转换成眼坐标系坐标,这一节讲述如何控制光源位置以满足几种常见的要求:
1)固定光源位置:把光源放到某个固定位置,不受其他操作的影响;再设定好视点之后,立即指定光源位置。
2)独立地移动光源:一般在display中设定光源位置,只要使用glPushMatrix和glPopMatrix不难做到。
3)随视点移动光源:在指定视点之前,指定光源位置,此后动态调整视点时,光源相对位置不变。注:指定视点之前指定的位置,从世界坐标系转换到眼坐标系时不会发生变化,因此其相对视点的位置也就没有变化。

设置光照模式

设置光照模式的API是glLightModel{if}(GLenum pname, TYPE param)或void glLightModel{if}v(GLenum pname, const TYPE *param),pname是属性名字。

全局环境光(global ambient):与光源无关的环境光,名字是GL_LIGHT_MODEL_AMBIENT

本地或无限远的视点(local or Infinit viewport):在计算高光效果时,顶点的高光强度取决于三个方向:光源和顶点的相对方向、法线方向、视点和顶点的相对方向;不同的顶点和视点的相对方向是不一样的,使用无限远的视点就是忽略这种不一致,使用一个固定值,以提升效率。这里的设置并不会真的改变视点位置,只是针对光照而已。Opengl默认是使用Infinit Viewport的,改变使用glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE)。

背面光照:Opengl默认只会对多边形的front-face做关照处理,某些情况下可能需要对back-face做关照,比如一个球体被剪切一部分,此时需要使用下面的API:glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

延迟高光处理:OpenGL流水线中,光照处理发生在纹理处理之后,这样高光效果就会被极大弱化。为了是高光效果明显一些,就要延迟一下高光处理,开启方法:glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR),
这样在做光照处理时,先合成ambient,diffuse,emissve分量至顶点颜色,这个颜色角primary color,specular分量产生的颜色角secondary color,暂存起来;等到texture完成后,再将specular值secondary加至顶点颜色。关闭该功能:glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR)。


4、定义材料属性

定义材料属性使用API:
void glMaterial{if}(GLenum face, GLenum pname, TYPE param);
void glMaterial{if}v(GLenum face, GLenum pname, const TYPE *param);
face指物体正面或背面有,
pname是属性名,可能的值有:GL_AMBIENT,GL_DIFFUSE,GL_AMBIENT_AND_DIFFUSE,GL_SPECULAR,GL_EMISSION,GL_SHININESS(高光度)。
Diffuse和Ambient很大程度上决定了物体的最终颜色;
Sepcular效果与视点的位置有极大关系,如果视角方向与反射的方向一致,则高光效果最明显;
Shiniess用来控制高光的区域和明亮度,值范围是[0.0,128.0],值越大,则高光区域越小,颜色越亮;
emission会使物体有一种发光的效果,但并不能起到光源的作用。


5、光照数学计算


第一行:物体自身释放的光颜色;
第二行:全局环境光对物体产生的颜色;
第三行:衰减和聚光效果,衰减前面已经讲过,聚光效果的值分一下几种情况:1)放射角度为180,此时没有聚光效果,值为1;2)顶点在聚光椎体之外,值为0;3)为max(v*d,0)对GL_SPOT_EXPONENT求冥,v是从光源位置到顶点的单位向量,d是聚光源的中轴方向;
第四行:光源i的环境光效果
第五行:光源i散射光产生的效果,L是从顶点到光源的单位向量,n是顶点法线向量;
第六行:光源i产生的高光效果,S是(从顶点到光源的单位向量+从顶点到视点的单位向量)结果的规范化,n是顶点法线向量,从“顶点到视点的单位向量”这一项如果GL_LIGHT_MODEL_LOCAL_VIEWER没有打开的话取值为(0,0,1)。


如果启用GL_LIGHT_SECONDARY_COLOR的话,只需要把specular部分提取出来作为secondary Color,剩下的是primary Color。