在OPENGL中,仅处理三种光,镜面反射光(specular),环境光(ambient)和散射光(diffuse)[2]。在设置OPENGL的光照时,要分别设置光源的各种参数和物体材质的各种参数。光源,R、G、B值等于R、G、B对其最大强度的百分比,即红黄蓝各占的百分比。而材质,R、G、B值为材质对光的R、G、B成分的反射率。也就是材质对光源的各个分量的反射程度。二者的乘积才是最终看到的光。比如,一种材质的R=1.0、G=0.5、B=0.0,则材质反射全部的红色成分,一半的绿色成分,不反射蓝色成分。也就是说,若OpenGL的光源颜色为(LR、LG、LB),材质颜色为(KR、KG、KB),那么,在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LR*KR、LG*KG、LB*KB)[3]。

图1 光的理想镜面反射模型
R=2(N•I)N-I 原来我一直不明白这个公式是怎么得到的。后来终于想明白了。N•I是标量,N是单位向量,所以(N•I)N表示在N方向大小为(N•I)的向量,而-I是I的反方向的单位向量,这样向量-I+2(N•I)N正好是R。

图2 粗糙表面的光照模型
根据微平面理论[1],在粗糙表面上,由于微平面的相互遮挡或屏蔽而使光产生的衰减。根据Phong光照明模型[1],利用反射方向和视线方向的夹角来体现视线衰减,即E•R。但为了简化计算,通常用N•H代替E•R。这样一方面是H的计算比较简单,此外如果使用平行光源和正交(平行)观察,那么光线方向和观察方向就为常量,同时H在整个场景中也保持不变[4]。参数r描述了表面的光亮度,r越大,物体表面就越亮。我对这点也是心存疑问。因为N•H或E•R是小于1的,那么r越大应该越小才对。后来我的理解是这样的,此处说的越亮是相对的。在-p/2到p/2之间,(N•H)r是随着r的增加函数曲线变化就越剧烈。这样高光点与其周围点的亮度差距就越大,就会显得越亮。这在下图中可以观察到。

图3 Phong光照明模型示意彩图
图中Kd表示材质的diffuse反射率,Ks表示材质的specular反射率。在OPENGL中,是按照下面的公式进行计算的
![]()
V即视线方向E,通常用N•H代替R•V
可以参考glsl的源代码(来源于Stanford的computer graphics课件)
vec3 N, L, V, H; float NdotL, NdotH;
vec4 Cd = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
vec4 Ca = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
Vec4 Cs = vec4(0.0); //specular color
N = normalize(gl_NormalMatrix * gl_Normal); //物体表面法向量
L = normalize(vec3(gl_LightSource[0].position)); //光方向法向量
NdotL = max(dot(N, L), 0.0); //光与法线角度的cos值
if (NdotL > 0.0) {
Cs = gl_FrontMaterial.specular * gl_LightSource[0].specular; //镜面反射光
H = normalize( gl_LightSource[0].halfVector.xyz ); //半向量H方向,即E+L的方向,E为人眼的入射方向。
NdotH = max(dot(N, H), 0.0);
Cs *= pow(NdotH, gl_FrontMaterial.shininess); //shininess为物体表面的粗糙度
}
gl_FrontColor = Ca + NdotL * Cd + Cs;
散射光——来自同一方向,照射到物体表面后,将沿各个方向均匀反射,因此,无论从哪个方向观察,表面的亮度都相同.[2]
环境光——经过多次反射而来的光称为环境光,无法确定其最初的方向,但当特定的光源关闭后,它们将消失[2]
所以最终的结果中散射光Cd要乘光的入射角,而环境光不需要。
【1】真实感图形学
【2】OpenGL 光照常识
【3】OpenGL光照
【4】Tomes Akenine-Moller,Eric Haines, 普建涛译,实时计算机图形学(第2版),北京大学出版社,2004
浙公网安备 33010602011771号