OpenGL总结15-光照模型

光照模型

绘制地形图时发现图像看不出起伏,觉得应该是关照模型和法线的问题,所以查找了一些资料,这里做一个总结。

在查看计算机图形学的时候,书中提到光照模型与表面绘制,为了避免属于混乱,定义了光照模型和表面绘制的定义。

  • 光照模型:对单个点进行光照计算的模型
  • 表面绘制:利用光照模型获得投影中所有表面像素颜色

物体的表面光照一般包括反射、透明、纹理、阴影。一般采用物理模型对光照的显示进行模拟。

光源分类

  • 点光源:通过RGB颜色值指定的单色光点,通过位置和颜色定义。一般用于距离较远的场景(包括距离以及相对物体而言较小的光源,跟透视矩阵的缩放差不多)
  • 无穷远光源:也就是平行光,因为距离过远几乎近似于平行的方式照射,例如太阳,手电
  • 环境光:无处不在的光,方向认为是任意方向,但是表面的明暗程度是相同的,这也就是为什么当不对OpenGL进行光源设置的时候仍然可以看到模型,因为有默认的全局环境光(不过我的地形图显示不出明暗效果应该就是它的原因)
  • 聚光灯:跟舞台上的聚光灯道理一样,有一个圆形的集中光照,这种光照是从椎体发出的,需要指定方向和椎体的顶角

光源的影响因素

  1. 点光源:颜色、位置、衰减因素、强度
  2. 环境光:强度、颜色
  3. 平行光:颜色、方向、强度
  4. 聚光灯:强度、位置、衰减因素、方向、夹角、颜色

辐射强度衰减

光会随着距离的增长进行衰减,但是仅限位置光,无限远的光源、全局环境光不会发生衰减,因为无限远的光源距离已经不是主要因素了,所以认为不衰减。距离对光源的影响有以下公式进行定义:
f ( d l ) = { 1.0 1 a 0 + a 1 d l + a 2 d l 2 f(d_{l}) = \begin{cases} 1.0\\ \cfrac{1}{a_{0}+a_{1}d_{l}+a_{2}d_{l} ^2} \end{cases} f(dl)=1.0a0+a1dl+a2dl21
光源与物体的距离是d,本来衰减应该按照 1 d 2 \frac{1}{d^2} d21进行,但是点光源对于真实情况的模拟并不总是产生真实感, 1 d 2 \frac{1}{d^2} d21对于较近的物体变化过大,较远物体变化又过小,所以为了更真实的模拟真实情况,采用的包含线性二项式的公式进行。方向光照不衰减,所以始终是1。

方向光源与角度衰减
在这里插入图片描述
方向光源就是在确定了光源的位置和颜色后,还要指定光的方向和角度(也就是照射的范围),上面的简图中b是光源规定的角,vl是圆锥的中轴(光源中强度最大的方向,其他方向会有一定的衰减),a是照射物体表面与中轴的夹角,vo是指向物体的光线,超过角度的规定范围的对象不会过得光照。可以根据以下的公式进行判断:
v o ⋅ v l = cos ⁡ a v_{o}\cdot v_{l}=\cos a vovl=cosa
只有当
cos ⁡ a ≥ cos ⁡ b \cos a \geq \cos b cosacosb
时对象才能获得光照。

角强度的衰减与与上面的式子类似
f ( x ) = cos ⁡ a x f(x) = \cos^a x f(x)=cosax
a是一个正值的衰减指数,x是光线与中轴的夹角(如果不是方向方向光源的则不存在衰减,超出光源角的对象没有光照)。

表面光照效果

因为物体会对光进行反射,根据表面材料的不同反射率不同。
反射类型可以分为以下几种:

  • 漫反射:粗糙的表面会将光向各个方向均匀进行反射,因而从任何角度看便面的亮度均相同
  • 镜面反射:光集中进行反射形成光斑,表面与光滑越容易出现
  • 环境光:或者叫背景光,通过环境中其他物体的反射获得的光照,即使不直接出现在光源范围内也可以通过环境中其他物体的反射观察到物体。

物体表面的反射光是物体自身反射光与反射环境光的总和。

基本光照模型

环境光:默认给定的是一个全局的环境光,所有对象的环境光都相同,并近似给出了各个表面的全局漫反射。

漫反射:假设是理想反射体,各个方向都以相同的强度进行反射,与观察位置无关,符合朗伯余弦定理。不同方向的反射强度与反射方向与法线的夹角的余弦值成正比。对于朗伯体而言各个方向的反射系数是一个常数。(简单的将就是计算一个余弦的投影)

每个表面有一个系数,反射值等于入射值乘以这个系数,得到的是一个常数值。此时如果只使用环境光就会出现缺少立体感的图像。

假设一个平行光照射一个平面,平面的亮度与入射光的数量有关,整个公式与投影面积相关:
I = k I i n cos ⁡ a I = kI_{in}\cos a I=kIincosa
k是漫反射系数, I i n I_{in} Iin是入射光,a是平面与光线的夹角,角度(0-90)。将公式与之前的光线夹角公式相结合,得到一个点光源漫反射公式:
I = { k I i n ( v n ⋅ v l ) , v n ⋅ v l > 0 0.0 , v n ⋅ v l ≤ 0 I=\begin{cases} kI_{in}(v_{n}\cdot v_{l}) ,& v_{n}\cdot v_{l}>0\\ 0.0,& v_{n}\cdot v_{l}\leq0 \end{cases} I={kIin(vnvl),0.0,vnvl>0vnvl0
其中 v n v_{n} vn为法向量, v l v_{l} vl为光线方向向量。无穷远的光源没有位置,只有方向,所以使用光源的逆方向作为 v l v_{l} vl的方向。

整体的漫反射是包含光源与环境光的总和,所以整合公式得到完整的漫反射公式,OpenGL中还包含了对环境光系数的设定,所以公式中添加一个环境光的系数用于调整环境光的反射。
I d i f f = { k a m b i I a m b i + k d i f f I l ( v n ⋅ v l ) , v n ⋅ v l > 0 k a m b i I a m b i , v n ⋅ v l ≤ 0 I_{diff} = \begin{cases} k_{ambi}I_{ambi} + k_{diff}I_{l}(v_{n}\cdot v_{l}),& v_{n}\cdot v_{l}>0\\ k_{ambi}I_{ambi},& v_{n}\cdot v_{l}\leq0 \end{cases} Idiff={kambiIambi+kdiffIl(vnvl),kambiIambi,vnvl>0vnvl0

  • k a m b i k_{ambi} kambi——环境光反射系数
  • k d i f f k_{diff} kdiff——点光源反射系数
  • I a m b i I_{ambi} Iambi——环境光入射光强度
  • I l I_{l} Il——点光源入射光强度

反射系数都依赖于表面材质,OpenGL中是0.0-1.0的范围之间(OpenGL中设置值的时候尽量避免使用int,有的时候不起作用,比如颜色)。

镜面反射:入射光的绝大部分全被反射(亮瞎眼,反射率比较高的时候会看到刺眼的光),镜面反射的入射角等于反射角,在理想的镜面反射中只有当眼睛与反射角一致的时候才可以观察到反射光,非理想反射体中,反射光会出现在一定的范围中,分布在反射角附近,表面光滑则范围会比较小,如果表面粗糙则会分布在较大的范围内。

在这里插入图片描述
Phong模型认为反射强度与 cos ⁡ n s p e c b \cos^{n_{spec}} b cosnspecb成正比(0-90), n s p e c n_{spec} nspec是镜面反射参数。然后根据一大堆物理定律推导出了一个公式
I s p e c = w ( a ) I l cos ⁡ n s p e c b I_{spec} = w(a)I_{l}\cos^{n_{spec}} b Ispec=w(a)Ilcosnspecb

  • I l I_{l} Il——入射光
  • b b b——反射光与观察视线的夹角
  • w ( a ) w(a) w(a)——反射系数,是不同材质的界面随着入射角的变化所对应的反射强度,一般角度变大值会变大,非透明物体基本可以使用一个常数代替(因为几乎所有入射角反射量都一样)

镜面反射时,如果观察方向和入射方向位于法线同一侧或者背面的时候,不会产生镜面反射,镜面的反射系数设为一个常量,某一点的镜面反射可以有以下公式表示:
I s p e c = { k s I l ( V ⋅ R ) n s , V ⋅ R > 0 0 , V ⋅ R ≤ 0 I_{spec} = \begin{cases} k_{s}I_{l}(V \cdot R)^{n_{s}},& V \cdot R>0\\ 0,& V \cdot R\leq0 \end{cases} Ispec={ksIl(VR)ns,0,VR>0VR0

  • k s k_{s} ks——镜面反射系数
  • I l I_{l} Il——入射光
  • V V V——观察方向
  • R R R——反射方向
  • n s n_{s} ns——镜面反射系数

反射向量可以通过入射角和法向量计算得到,但是并不方便,为了简化计算采用了观察向量和入射向量的半角向量 H H H替代(其实就是向量 V + L V+L V+L,比起需要通过法向量和夹角计算的 R R R来说省事很多)。如果是无限远的光源则认为 V , L , H V,L,H V,L,H都是常量,当 H H H与法线的夹角大于90度的时候,镜面反射为零。另外当 V , L , R V,L,R V,L,R共面的时候,a角的b/角,不共面的时候a>b/2(起始我没弄明白)。

漫反射与镜面反射的合并公式:
I = I d i f f + I s p e c = k a m b i I a m b i + k d i f f I l + k s p e c I l ( N ⋅ H ) n s I = I_{diff} +I_{spec}=k_{ambi}I_{ambi} +k_{diff}I_{l}+k_{spec}I_{l}(N\cdot H)^{n_{s}} I=Idiff+Ispec=kambiIambi+kdiffIl+kspecIl(NH)ns
如果光源位于表面的时候则只存在环境光,如果观察方向与入射方向为同一侧,则不产生镜面反射。

表面发光:物体自己就是发光体。 I s u r f I_{surf} Isurf

在计算机图形学中有一个将以上信息总和起来的公式,这个公式包含了衰减和高光的模型。
I = I s u r f + I a m b d i f f + ∑ l = 1 n f r a d a f a n g ( I d i f f + I s p e c ) I=I_{surf}+I_{ambdiff}+\sum_{l=1}^{n}f_{rada}f_{ang}(I_{diff}+I_{spec}) I=Isurf+Iambdiff+l=1nfradafang(Idiff+Ispec)
其中f代表辐射衰减和角度衰减(针对的是漫反射和表面反射),光照强度一般都会归一化到0-1之间。

I a m b d i f f I_{ambdiff} Iambdiff——代表环境光漫反射
I d i f f I_{diff} Idiff——代表点光源漫反射
I s p e c I_{spec} Ispec——代表镜面反射
I s u r f I_{surf} Isurf——物体表面发射的光,也就是辐射光

多边形绘制算法
OpenGL是通过多边形面片来进行图像的显示的,对于表面的显示有几种不同的方式。

  • 恒定强度明暗:最简单的绘制方式,对所有的点都给予相同的颜色,这样的可以快速的生成一般曲面的外观(但是多边形的棱角会比较分明)
  • Gouraud明暗处理:通过对曲面的每个顶点的法向量的均值进行一定的平面插值,可以达到平滑表面的目的(光照相对均匀过渡,可以有一个光滑的曲面),但是会出现过渡明亮或者过渡暗的条纹(马赫带),可以通过增加细分或者更精确的计算来消除
  • Phong明暗:Gouraud是对表面的强度进行插值,Phong是对表面的法向量进行插值(光照模型中的法向量太有用了),可以得到更精确的插值信息,同时可以减少马赫带
  • 快速Phong明暗:就像名字一样,节省Phong的计算时间

OpenGL光照相关接口

OpenGL中已经具有了一些基本的模型,可以通过参数的设置来调整效果。

OpenGL点光源

OpenGL可以通过以下函数设置点光源:

glLight*(lightNum,property,value);

glLight是一个统一的名称,后面的*代表的是数据类型,lightNum代表的是光源的编号,与纹理的ID类似,OpenGL的固定管线中最多允许有8个光源也就是GL_LIGHT0到GL_LIGHT7,可编程管线可以超出这个数(具体看实现)。property代表要设置的内容(有10个OpenGL符号属性常量),比如漫反射GL_DIFFUSE,光源的位置GL_POSITION,value代表设置内容的具体值,比如颜色值,位置坐标等。

光源与纹理一样不启用设置了也没用,所以要启动光源。

glEnable(lightNum);

然后像灯一样,要开灯,设置使用光照,因为OpenGL默认是不启用光照的。

glEnable(GL_LIGHTING);

指定光源的位置

在指定光源位置的时候会用到四个坐标,我一般会使用glLightfv的形式,前三个坐标是X,Y,Z坐标,最后一个代表的是光源的类型。OpenGL在这里将光源分为的两种类型,一种是距离较近的点光源,通过X,Y,Z的坐标决定,另一种的是无穷远发出的与位置无关的光源,只具有方向(方向光),方向为X,Y,Z坐标到原点的方向。最后一个值为1则是点光源,如果是0则是方向光。
如果不指定光源的位置和类型,则默认一个(0,0,1,0)的光源,表示从z轴负方向无穷远处发射向z轴正方向的的光线。

glLightfv(GL_LIGHT0, GL_POSITION, Pos);

光源与物体一样可以受到矩阵变化的影响,如果希望与物体保持相对位置,则在设置了物体的矩阵变换之后再进行设置,相反则会改变与物体的相对位置。为了方便区分我一般会使用glPushMatrix和glPopMatrix这两个操作对矩阵进行压栈,但是使用的时候要注意栈的释放和数量,OpenGL中对栈的数量有限制,同时不正确的操作会造成很多其他的显示效果。

光源颜色

光源的颜色采用的仍然是RGBA的颜色模型,可以用来设置环境光(GL_AMBIENT),漫反射(GL_DIFFUSE),镜面反射(GL_SPECULAR),辐射光(GL_EMISSION)。

glLightfv(GL_LIGHT0, GL_DIFFUSE, diff);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_SPECULAR, spe);
glLightfv(GL_LIGHT0, GL_EMISSION, emission);//放射光

光线的衰减系数设置
之前提到过光线的漫反射的衰减公式是一个多项式,所以可以设置多项式的系数。

glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1);//常量参数
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.5);//一项参数
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.1);//二项参数

材质
如果使用了glColor设置颜色,同时想要在进行光照的时候可以保留物体自己的颜色,需要进行材质的设置,同时为了显示不同的效果也需要对表面的材质进行调整,可以出现镜面等效果。

glEnable(GL_COLOR_MATERIAL);//开光照就一定要开材质,否则物体本身的颜色会被盖掉
glMaterialf(GL_FRONT, GL_DIFFUSE, 0.7);//正面的漫反射
glMaterialf(GL_FRONT, GL_AMBIENT, 0.5);
glMaterialf(GL_FRONT, GL_SPECULAR, 0.3);

上面的函数设置的正面的漫反射,还可以通过改变GL_FRONT参数来设置正面或者背面亦或是同时设置两面。

未完,待续。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值