《OpenGL游戏程序设计》 K.霍金/D.阿斯特 著 田昱川 译
1. 定义颜色
glColor*()
2. 明暗处理
明暗处理可以是单调的,也可以是平滑的。
单调的明暗处理用单一的颜色进行绘制,通常使用最后一个顶点的颜色(OpenGL的GL_PLOYGON,它是用其指定的第一个顶点的颜色)。
平滑的明暗处理用插值法确定图元之间的颜色
void glShadeMode(GLenum mode);函数指定明暗处理模型
参数:
GL_SMOOTH使用平滑明暗处理
GL_FLAT使用单调明暗处理
3. 光照
OpenGL通过将光近似地分解成红、绿、蓝分量来计算光和光照。即一个光的颜色由此光中的红、绿、蓝分量的数量决定。当光照照射到一个表面时,OpenGL根据这个表面的材质来确定此表面应该反射的光的红、绿、蓝分量的百分比数量。
OpenGL通过4种光的组合来模拟真实世界的光照:
1. 环境光。环境光看上去不是来自任何特定的方向。即使存在一个光源。具有强烈的散射性而不确定方向。被环境光照射的表面会将其向各个方向均匀反射。
2. 散射光。来自于一个确定的方向,但是它一旦遇到一个表面,就会被向各个方向均匀地反射,无聊眼睛处于什么位置,此表面都具有同样的亮度。
3. 镜面反射光。具有方向性,表面的反射也有特定的方向,经常被称为亮光
4. 发射光。带有发射光的物体看起来就好像其自身会发光,只不过这样的光不会对场景中的其他物体产生影响。在OpenGL中,发射光增加了物体的亮度,但任何光源都不会影响发射光。
4. 材质
OpenGl根据材质对红、绿、蓝色光的反射来近似模拟材质颜色。
例如一个纯绿色的表面将反射所有照射过来的绿光而吸收掉所有照射过来的红光和蓝光。如果将绿色表面放在纯红光中,表面看起来会是黑色的。因为表面只能反射绿光。绿色表面放置在白光中,就能看到绿色的表面,放在绿光中也看到绿色的表面。
材质有3个与光一样的颜色属性:环境光、散射光、镜面反射光。这3个属性决定了材质反射相应的光分量的程度。例如,一个属性为高环境光反射率、低散射光反射率、低镜面光反射率的材质将会注意反射环境光而吸收散射光和镜面反射光。
通常所指定的环境光和散射光的反射率决定了材质的颜色,一般这两个值相等。
为了确保材质表面的镜面反射高光区以光源的镜面反射光分量的颜色来终结,通常将镜面反射光的反射设为灰色或白色。如一个蓝色表面大部分是蓝色的,但是高光区是白色的。
5. 法线的使用
在OpenGl中为一个表面指定法线,调用glNormal3f()函数,此函数为其下要绘制的一个或者一组顶点定义了法线。
void glNormal3f(GLfloat nx, GLfloat ny, Glfloat nz);
传给此函数的3个参数是此表面法向量的x,y,z坐标分量。调用完此函数后紧接着就调用相应的顶点函数。在对场景进行光照时,OpenGL对平面的所有光照计算都使用这个法向量。
为了进行光照计算,OpenGL必须将传递给glNormal3f()函数的法线转换成所谓的单位法线(unit normals)。确定OpenGL使用单位法线的方法是调用带有GL_NORMALIZE或者GL_RESCALE_NORMALIZE参数的glEnable()函数。
GL_RESCALE_NORMALIZE:法线各向均匀地缩放并使长度为1.
GL_NORMALIZE更常用,通知OpenGL计算单位矢量,不必自己计算法线,但会带来性能方面的问题。设置后OpenGL会检查所传递的法线是否是单位法线,不是的话OpenGL会自己计算。
最好自己提前计算好单位法线。
6. OpenGL光照的使用
OpenGL允许在场景中至多同时使用8个光。
向场景中添加光照需要4个步骤:
1) 为每个物体的每个顶点计算法向量。法线确定了物体相对于光源的指向。
2) 创建、选择并定位所有的光源
3) 创建并选择一种光照模型。光照模型定义了环境光,并设置用于光照计算的视点位置
4) 位场景中的物体定义材质属性
2) 创建、选择并定位所有的光源 例
float ambientLight[] = {0.3f, 0.5f ,0.8 f ,1.0f}; //环境光
float diffuseLight[] = {0.25f ,0.25f, 0.25f ,1.0f};//散射光
float lightPosition[] ={0.0f ,0.0f, 0.0 f, 1.0f }; //光源位置
环境光具有偏向蓝色的色调
散射光的效果是物体被散射光直接照射的面比其他没被照射的面亮的多。
光源被设置在3D世界的原点(0,0,0),光源位置的第四个值通知OpenGL前3个值是表示一个点还是一个矢量。第四个值是1,表示前3个坐标表示一个点光源的位置。如果第四个值是0.0,OpenGL认为此光来自于一个特定的方向,而光源位置的前3个数值所定义的矢量就是用于指定这个特定方向。如果矢量是(0,0,0),这个光不会进行照射。
float lightPosition[]={0.0f , 0.0f, 1.0f, 0.0f};//来自z轴正方向的光
4) 定义材质属性
float matAmbient[]={1.0f ,1.0f, 1.0f,1.0f};
float matDiff[]={1.0f,1.0f,1.0f,1.0f};
第一个变量matAmbient定义了材质表面对应光线中的环境光分量是如何反应的,每一个值都是1.0意味着环境光的红、绿、蓝分量全部会被此表面反射。matDiff变量定义了材质表面对光线中的散射光分量的反应。
5) 例子
glShadeMode(GL_SMOOTH); 使用平滑明暗处理
glEnable(GL_DEPTH_TEST); 剔除隐藏面
glEnable(GL_CULL_FACE); 不计算多边形背面
glFrontFace(GL_CCW);多边形逆时针方向是正面
glEnabel(GL_LIGHTING); 启用光照
//为light0设置材质
glMaterialfv(GL_FRONT, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiff);
//设置light0
glLightfv(GL_LIGHT0,GL_AMBIENT ,ambientLight);设置环境光分量
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);散射光分量
glLightfv(GL_LIGHT0,GL_POSITION,ligthPosition);设置光源在场景中的位置
glEnable(GL_LIGHT0);//启用光0
GL_DEPTH_TEST标记将被遮挡的表面隐藏掉
GL_CULL_FACE不对多边形的背面进行任何计算。
为了确定多边形的正面和背面,调用带有GL_CCW的glFrontFace函数。
glMaterialfv()设置用于所有光的光照计算的材质属性,为多边形的正面设置了环境光和散射光的材质属性。
glLightfv()函数用来设置所用的每一个光的属性。
7. OpenGL光照的使用(二)
1. 光源的创建
可以定义光的颜色、位置、方向。
最常见的函数是
void glLightfv(GLenum light ,GLenum pname TYPE *param)
参数light的值是GL_LIGHT0~~GL_LIGHT7 。
参数pname:
GL_AMBIENT 光的环境光分量的强度
GL_DIFFUSE 光的散射光分量的强度
GL_SPECULAR 光的镜面反射光分量的强度
GL_POSITION 光的位置,点坐标(x,y,z,w)形式
GL_SPOT_DIRECTION 聚光灯的方向,矢量(x,y,z)的形式
GL_SPOT_EXPONENT 聚光灯指数(聚光灯焦点)
GL_SPOT_CUTOFF 聚光灯边界(圆锥面与轴线之间的角度)
GL_CONSTANT_ATTENUATION 常衰减系数值
GL_LINEAR_ATTENUATION 线性衰减系数
GL_QUADRATIC_ATTENUATION 二次衰减系数值
在指定一个光的环境光分量时,也是在指定此光向场景添加的环境光的RGBA强度。默认情况下没有环境光。
散射光可以认为是光源所发出的光的颜色,LIGHT0的默认值是(1.0,1.0,1.0,1.0),其余的默认值是(0.0,0.0,0.0,0.0)。
镜面反射光决定着高亮区的颜色,通常与GL_DIFFUSE相同,可以得到更加真实的视觉效果,GL_SPECULAR在LIGHT0的默认值是(1.0,1.0,1.0,1.0),其余的默认值是(0.0,0.0,0.0,0.0)。
2. 光源的定位
光源的位置通过GL_POSITION参数和一个四值的矢量(x,y,z,w)来定义。
如果w=0.0,(x,y,z)就定义了一个矢量,指定了光线照过来的方向,这样的光源称为定向光源(directional light source),其所有光线是平行的。好像光源的位置处于无穷远。
例
float liaghtPosition[]={0.0f,0.0f,1.0f,0.0f};
glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
定义一个来自于z轴正方向的定向光源,在默认的视体中指向屏幕外面
(指向屏幕外面 :不是来自z轴负方向?)
当w值非零时,所定义的是一个顶点光源,对于一个顶点光源,(x,y,z)定义了此光源在物体其次坐标系中的坐标位置,油灯和灯泡都是定点光源。
例
float lightPosition []= {0.0f,0.0f,0.0f,1.0f};
glLightfv(GL_LIGHT0,GL_POSITION,lightPositon);
3. 光的衰减
衰减是光随着距离光源的距离的增大其强度的减小。只适应于定点光源。定向光源的衰减因子毫无意义,因为定向光源在无穷远处。
GL_CONSTANT_ATTENUATION 默认是1.0
GL_LINEAR_ATTENUATION 默认是0.0
GL_QUADRATIC_ATTENUATION 默认是0.0
发散光(发射光?)和全局环境光不受衰减影响
其他环境光、散射光、镜面反射光都会受衰减影响。
使用衰减会减慢游戏运行速度。
4. 聚光灯
将定点光源的辐射面从全方位减小到某一特定的方向,就会得掉一个聚光灯。创建聚光灯,除了需要做创建定点光源时做的所有事情之外,还要设定一些聚光灯特有的参数:聚光灯的边界、聚光灯的方向、聚光灯的焦点。
聚光灯:沿其方向产生一个圆锥形的光柱。
GL_SPOT_CUTOFF参数设定圆锥面与轴线之间的角度。180度的GL_SPOT_CUTOFF意味着此光源将向所有方向辐射光线(180*2=360)。OpenGL允许的范围是0°到90°。GL_SPOT_DIRECTION参数设定聚光灯的指向,值是一个(x,y,z)形式的矢量,默认是(0.0,0.0,-1.0),指向z轴负方向。
GL_SPOT_EXPONENT参数设定聚光灯焦点,也就是聚光灯在其光柱中的聚光点,从此点到光柱边界,光的强度随之衰弱,直至在光柱的边界消失。聚光灯指数越高,光源的聚光性越好。
例
float ambientLight[]={0.5f,0.5f,0.5f,1.0f}; //环境光
float diffuseLight[]={0.5f,0.5f,0.5f,1.0f};//散射光
float spotlightPosition[]={6.0f,0.5f,0.0f,1.0f};聚光灯位置
float spotlightDirection[]={-1.0f,0.0f,-1.0f};聚光灯方向
glEnable(GL_LIGHTING);启用光照
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);设置环境光分量
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight)设置散射光分量
glLightfv(GL_LIGHT0,GL_POSITION,spotlightPosition)光源位置
聚光灯属性
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,40.0f);80度张角
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,30.0f);焦点
glLightfv(GL_LIGHT0,GL_SPOT_EIRECTION,spotlightDirection);
glEnable(GL_LIGHT0);启动光源0
5. 材质的定义
设置一个材质与创建一个光源类似,只是函数不同
void glMaterialf(GLenum face ,GLenum pname,TYPE param);
void glMaterialfv(GLenum face ,GLenum panme ,TYPE*param);
参数face指定了材质如何应用于物体的多边形,可以为GL_FRONT,GL_BACK,GL_FRONT_AND_BACK这三个值之一。
GL_FRONT材质用于多边形的正面
GL_BACK材质用于多边形的背面
GL_FRONT_AND_BACK材质用于多边形的两个面
参数pname:
GL_AMBIENT 材质的环境光颜色
GL_DIFFUSE 材质的散射光颜色
GL_AMBIENT_AND_DIFFUSE 材质的环境光和散射光颜色
GL_SPECULAR 材质的镜面反射光颜色
GL_SHININESS 镜面反射光指数
GL_EMISSION 材质的发射光颜色
调用glMaterial*()函数之后,所绘制的多边形都会受其设置的材质影响,直到再次调用glMaterail*()函数。
设置材质属性的另一个方法叫做颜色跟踪。颜色跟踪可以调用glColor*()函数来设置材质属性。应用颜色跟踪,首先要调用:
glEnable(GL_COLOR_MATERIAL); //启用颜色跟踪
然后调用glColorMaterial()函数来指定将会受glColor*()函数调用影响的材质属性参数,
glEnable(GL_COLOR_MATERIAL);启用颜色跟踪
glColorMaterial(GL_FRONT,GL_DIFFUSE);多边形正面,材质的散射光颜色属性
glColor3f(1.0f,0.0f,0.0f);
6. 光照模型
OpenGL的光照模型允许设置4个影响场景的因素:
u 场景中的环境光强度
u 视点是否处于无穷远(影响镜面反射光反射角度的计算)
u 单面光照还是双面光照
u 镜面反射光颜色是否与环境光颜色和散射光颜色分离
使用glLightMode*()函数来定义光照模型
void glLightModel[if] (GLenum pname ,TYPE param);
void glLightMdoel[if]v (GLenum pname ,TYPE param);
参数pname指定了将要定义的光照模型的属性,参数param就是相应的光照模型属性的设置值,根据使用的函数版本形式的不同,可能是一个浮点型数值或者是一个数组。
pname:
GL_LIGHT_MODEL_AMBIENT场景的环境光强度(RGBA),默认是(0.2,0.2,0.2,1.0);一个光源的环境光分量对整个场景都是起作用的,一旦在光照模型中设定了GL_LIGHT_MODEL_AMBIENT参数,就会通知OpenGL在场景中设置一个环境光,它并不是来自于任何一个特定的光源,称为全局环境光。
GL_LIGHT_MODEL_LOCAL_VIEWER视点是否处于无穷远,默认是GL_FALSE处于无穷远,当创建一个镜面反射光后,OpenGL会为场景中的物体计算其镜面反射光的高亮区,相关物体顶点的方向和视点都将影响镜面反射光高亮区的强度,在场景中使用非无穷远的视点可以增加真实感,但速度性能会降低,因为要为每个顶点增加方向计算,默认情况是无穷远
GL_LIGHT_MODEL_TWO_SIDE单面光照还是双面光照,默认是GL_FALSE单面光照,此参数用来决定是否对多边形的背面进行正确的光照计算,假如有一立方体,将其剖开,就能看到多边形的背面(内部的光照不正确),要想使多边形背面的光照被正确计算,需设置glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);,告诉OpenGL将多边形正面的法线反转过来作为背面的法线。运行速度会减慢。
GL_LIGHT_MODEL_COLOR_CONTROL镜面反射光颜色计算时是否与环境光和散射光分量,默认是GL_SINGLE_COLOR(不分离)。当对纹理映射进行光照,并且纹理与镜面反射光的高光之间的配合不是很好的时候,使用此属性。设置了参数后,OpenGL不会像通常将环境光分量、散射光分量、镜面反射光分量和发射光分量的材质属性都加在一起,而是会为光照下物体的每个顶点生成两个颜色:主颜色和副颜色。主颜色包括了所有的非镜面反射光分量,副颜色包括了镜面反射光分量。进行纹理映射的时候,只有住颜色被应用,映射完成后副颜色被添加到结果上。这样可以使镜面反射光的高光效果更加明显。
要想将镜面反射光与其他分量分离:
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR);
要将镜面反射光与其他分量结合使用:
用GL_SINGLE_COLOR参数代替GL_SEPARATE_SPECULAR_COLOR参数。
只有在纹理时才应该设置此参数,不进行纹理映射就没必要将镜面反射光分量与其他分量分离。
7. 镜面反射光的光照效果
float specularLight[]={1.0f,1.0f,1.0f,1.0f};镜面反射光的值
float lightPosition[]={0.0f,0.0f,0.0f,1.0f};光源的位置
glEnable(GL_LIGHTING);启用光照
//设置光照
glLightfv(GL_LIGHT0,GL_SPECULAR,specularLight);
glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
glEnable(GL_LIGHT0);
上述代码只设置了LIGHT0的镜面反射光分量和位置,也可以同样向LIGHT0添加环境光分量和散射光分量。这时镜面反射光的分量被设置为(1.0,1.0,1.0,1.0)是一种非常明亮的白光,类似与阳光。
为了得到镜面反射光的光照效果,必须定义响应的材质的镜面反射光属性:
float matSpecular[]={1.0f,1.0f,1.0f,1.0f};镜面反射光材料属性
//材质具有最小的光泽
glMaterialfv(GL_FRONT,GL_SPECULAR,matSpecular);
glMaterialf(GL_FRONT,GL_SHININESS,10.0f);
matSpecular变量被称为镜面反射光的反射率,此例中为(1.0,1.0,1.0,1.0),意思是此材质之后创建的任何表面会将几乎所有照射在其表面上的镜面反射光反射出去。
GL_SHININESS属性定义镜面发射光高光的聚焦程度,属性的取值范围是1.0~128.0,0.0代表镜面反射光不聚焦,128.0表面会十分显眼和明亮。
8. 光源的移动和旋转
光源可以移动和旋转,调用glLight*()函数定义一个光源的位置和方向时,所指定的信息会被应用与当前的模型视图矩阵。
如果移动的光源具有聚光灯的特效,需要为其设置GL_SPOT_DIRECTION参数,如果是向所有方向发射光线的点光源,就只要定义GL_POSITION参数。
探照灯(前灯)是目前的3D游戏中常用的一种光源,光源的位置相对于眼睛或者视点的位置保持相对固定不变。为了达到这个效果,需要在眼睛坐标控件中定义光源的位置,先将模型视图矩阵设为单位矩阵,然后在其原点定义光源位置,如果没有设置光源的方向,就只能得到一个位于视点的点光源。而如果希望得到探照灯或者前灯的效果,就必须将光源的方向设置为指向z轴的负方向。因为此光源的位置是 相对固定的,可以在初始化程序的时候指定其位置。避免每绘制一帧场景都要重新指定光源的位置。
除了投影矩阵堆栈、模型视图矩阵堆栈、纹理矩阵堆栈,还有另外一种堆栈可以用来存储当前的绘制状态,通过传递给函数glPushAttrib()参数GL_LIGHTING_BIT可以保存此场景中的所有光照信息,这样就可以先关掉光照,使我们能在没有任何光照影响的情况下绘制物体,然后再调用glPopAttrib()函数打开光照,恢复存储在属性堆栈中的光照属性。
9.
8. 颜色混合
OpenGL中的颜色混合可以为场景带来像透明这样的视觉效果。利用透明,可以模
拟水、窗户、玻璃以及其他可以看穿的物体。
启用混合,就是通知OpenGL要将传入图元的颜色和帧缓冲中已有的颜色合并,然
后再将结果存回帧缓冲中。混合操作通常被视为表示颜色的RGB值与表示不透明的
alpha值之间的运算。较低的不透明度或者alpha将导致较高的透明性和半透明性。
将传入的图元称为源,将当前帧缓存中的像素称为目标。
为了使用混合,需要使用:
glEnable(GL_BLEND);
然后调用glBendFunc()函数来定义源和目标的混合因子。glBendFunc()函数中,默认的源混合因子是GL_ONE,目标的混合因子是GL_ZERO
源混合因子:
GL_ZERO 源的颜色设为(0,0,0,0)
GL_ONE 使用源的当前颜色
GL_DST_COLOR 源的颜色与目标的颜色相乘
GL_ONE_MINUS_DST_COLOR 源的颜色与[(1,1,1,1)-目标的颜色]相乘
GL_SRC_ALPHA 源的颜色与源的alpha相乘
GL_ONE_MINUS_SRC_ALPHA 源的颜色与[1-源alpha]相乘
GL_DST_ALPHA 源的颜色与目标alpha相乘
GL_ONE_MINUS_DST_ALPHA 源的颜色与[1-目标的alpha]相乘
GL_SRC_ALPHA_SATURATE 源的颜色与源的alpha值和[1-目标的alpha值]中最小的相乘
目标的混合因子
GL_ZERO 目标颜色为(0,0,0,0)
GL_ONE 使用目标的当前颜色
GL_SRC_COLOR 将目标颜色与源的颜色相乘
GL_ONE_MINUS_SRC_COLOR 将目标的颜色与[(1,1,1,1)-源的颜色]相乘
GL_SRC_ALPHA 目标的颜色与源的alpha相乘
GL_ONE_MINUS_SRC_ALPHA 目标的颜色与[1-源的alpha]相乘
GL_DST_ALPHA 目标的颜色与目标的alpha相乘
GL_ONE_MINUS_DST_ALPHA 目标的颜色与[1-目标的alpha]相乘
GL_SRC_ALPHA_SATURATE 目标的颜色与源的alpha和[1-目标alpha]中小的相乘
透明效果:
源:GL_SRC_ALPHA
目标:GL_ONE_MINUS_SRC_ALPHA
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
在3D中,为了保证场景被正确地绘制和混合,需要注意:
最重要的是用下面的命令打开深度检测:
glEnable(GL_DEPTH_TEST);
启用深度检测的目的是为了将位于其他物体后面的物体隐藏起来,深度缓冲是被用来跟踪视点与窗口中的物体的每个像素之间的距离的。当一个颜色被应用与一个像素时,只有当此颜色所属的物体距离视点的距离较原来像素颜色所属的物体距视点的距离近时,新的颜色才会取代原来像素的颜色。一旦有取代发生,新的像素的深度值就会相应的被存入深度缓存中。这样使OpenGL能够隐藏位于不透明物体后面的物体。
在绘制场景时,要想在OpenGL中正确的进行混合就要涉及深度缓存的打开和关闭。先在深度缓存的正常状态下绘制所有的不透明物体,然后调用glDepthMask()函数将深度缓冲设置为只读状态,这样就将绘制不透明物体时的深度值保护起来了,在深度缓存为只读状态时,对透明物体的绘制不会影响到已经绘制好的不透明物体,因为深度缓存不能被改变。但是透明物体的仍然进行深度检测,与深度缓存中的值比较,如果位于不透明物体后面,就不会被绘制,如果位于不透明物体的前面,透明物体将与不透明物体混合。
使用glDepthMask()函数,如果设置为只读状态,参数为GL_FALSE;
如果设置为正常状态,参数为GL_TRUE。
9.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ezhong的博客签名-------------------------------------
以上内容来自ezhong的博客园,作者:ezhong
ezhong的博客园: http://www.cnblogs.com/ezhong
感谢您的阅读。感谢您的分享。