光照详解

基于平行光的环境光

​ 环境光是平行光,平行光有特定的方向但是没有特定的光源,所有的光都互相平行,计算时完全忽略光的方向,整个场景被均匀照亮。平行光的另外一个重要性质是不管它离物体多远亮度是不变的。

​ 光源的颜色定义为一个包含三个浮点数的三元组,浮点数介于[0,1]之间。光源的颜色和物体表面的颜色相乘得到反射回来的颜色。同时,加入强度,可以定义为一个[0,1]之间的一个单一的浮点数,然后和之前的相乘,从而得到最终的颜色值。

image-20230106111932045

​ 一般只是添加少量的环境光来避免出现物体一面被照亮而另一面完全是黑色的现象。例如希望物体的背后有一点亮光,因为在现实生活中灯泡会照亮它背后的墙,而墙会反过来(微弱地)照亮物体的背后,但计算这种光照的代价过大,因此通常可以简单地以假的光源代替这种计算。

基于点光源的漫反射光

​ 漫射光的特性依赖光线的方向,漫射光使物体朝向它的那一面比其他背向光的面要更亮,亮度取决于光线和物体表面的角度。光线如果要对物体表面的亮度产生影响,那么光线和法线的角度要在0-90度之间但不包含90度。这种影响通过光向量l和法线向量n的点积计算。基于点光源的漫反射,表面收到的光通量依赖于表面到光源的距离:越远光越少,与距离的平方成反比。

​ 顶点和法线都定义在本地坐标系空间,MVP变化后到裁剪空间。然而光照在世界空间中定义,所以在计算之前首先要将法线向量变换到世界坐标系空间。

​ 一个多边形面上分布的任意法向量都是一样的,足以用其中一个代表来计算顶点着色器中的漫射光。但有时一个顶点的多个面法线不同。需要使用到一个概念叫做‘顶点法线’,顶点法线是共用一个顶点的所有三角形法线的平均值。将顶点法线作为一个成员属性传给片段着色器。光栅器会得到三个不同的法向量并对其之间进行插值运算。片段着色器将会对每个像素计算其特定的插值法向量对应的颜色值,对漫射光的计算可以达到像素级别。效果是光照效果在每个相邻三角形面之间会平滑的变化。

基于点光源的镜面反射光

​ 基于点光源的镜面反射,比起漫反射还包含了观察者的位置。镜面反射时光以一定角度照射到物体表面,同时会在法线的另一侧对称的角度上反射出去,如果观察者刚好在反射光线的路径上那么就会看到格外强烈的光线。镜面反射最终的结果是物体在从某个角度看上去会十分明亮,而移动开后这个光亮又会消失。镜面反射光的存在更取决于反射物体的材料性质而不是光源本身。

image-20230106144515706

​ 亮度取决于观察者和反射光的夹角,随着角度增大反射光衰弱。这种影响通过R和V的点积计算。随着夹角’α’增大余弦值慢慢减小,直到夹角达到90°时无镜面反射的效果,夹角大于90°时余弦值为负,也没有任何反射效果,也就是观察者不在反射光的路径范围内。

反射光线’R’使用’I’向量来计算,如下图:

image-20230106144537465

​ 向量没有起点的概念,所有方向相同且长度相同的向量都是同一个向量。因此,图中将入射光向量’I’复制到表面下面位置向量本身是不变的。根据向量的加法,’R’等于’I’+’V’,’I’已知求’V’。法线’N’的反向向量为’-N’,计算’I’和’-N’的点积可以得到’I’在’-N’上的投影,这是’V’的模长度的一半。另外’V’和’N’的方向是相同的,所以只要用计算的那个投影长度乘以单位向量’N’再乘以2就是向量’V’了。用公式简单表示如下,通过’reflect’内部函数计算:

image-20230106144604599

​ 计算镜面反射的最终公式:

image-20230106144620480

​ 开始先是将光的颜色和物体表面的颜色相乘,这个和在计算环境光以及漫反射光时一样。得到的结果再和材料的镜面反射强度参数(’M’)相乘。如果材料没有反射性能,比如木头,那么镜面反射参数就为0,而像金属这种发光材料镜面反射能力就会很强。之后再乘以光线和观察者视线夹角的余弦值,即镜面参数’或者叫做‘发光参数’,用来增强加剧反射光区域边缘的强度。

聚光灯光源

​ 聚光灯光源也会随着距离衰减,相当于取点光源的一个锥形的一小部分,聚光灯光源呈锥形,离光源越远,照亮的圆形区域会越大(光源位于锥形体的尖端)。如下图:

image-20230106144647823

​ 图中L是光源方向,实现让光源只照亮两条红线夹角之间的区域。可以定义光锥为光线方向L和红线之间的夹角(两条红线之间夹角的一半)。点积计算夹角的余弦值‘C’以及L和V夹角的余弦,其中V指的是光源到某个像素的向量,如果后者的值大于余弦值‘C’,说明L和V之间的夹角偏小,该像素就位于被照亮的区域内。反之,像素位于区域外就不会被该光源照亮。

​ 如果仅按照上面说的在照亮区域内就点亮像素,否则就不点亮,照亮区域和未照亮区域之间的边界边缘会非常明显。一个真实的聚光灯光源会从照亮区域的中心向圆形边缘慢慢衰减。可以利用上面计算得到的那些点积作为一个衰减的参数。但是用余弦来做衰减参数会有问题,因为聚光灯光源的夹角不能太大,否则范围太广就失去了聚光灯的效果,但是在夹角从0到一个比较小的角度范围内,cos值得变化是很缓慢的,导致衰减不明显。要想衰减效果明显这个参数范围应该是[0,1]。解决方法是将这个参数的小范围映射到[0,1]的范围。

​ 聚光灯光源需要光源的方向向量和截断光源照亮范围的一个阈值。阈值代表的是光源方向向量和光源到可照亮像素之间的最大夹角。比这个阈值夹角大的像素是不会被该光源照亮的。

​ 首先得到光源到某个像素的向量,将向量单位化方便点积运算,然后和单位化了的光源方向向量进行点积运算得到他们之间夹角的余弦值。将得到的余弦值和光源的阈值(定义光源范围的最大夹角的余弦值)进行比较,如果余弦值比阈值小,说明夹角太大像素在照亮圆区域的外面,这样像素就不会被该光源点亮。反之如果像素在照亮区域内,我们就先像点光源那样计算光源的基础颜色。然后将计算的余弦值插值到0到1的范围,最后和点光源颜色相乘计算得到最终的聚光灯颜色值。

基于点光源的着色器文件编写

顶点着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
attribute vec3 vertexPosition_modelspace;
attribute vec2 vertexUV;
attribute vec3 vertexNormal_modelspace;//法线数组

varying vec2 UV;
varying vec3 Position_worldspace;
varying vec3 Normal_cameraspace;
varying vec3 EyeDirection_cameraspace;
varying vec3 LightDirection_cameraspace;

uniform mat4 MVP;
uniform mat4 V;
uniform mat4 M;
uniform vec3 LightPosition_worldspace;

void main(){

gl_Position = MVP * vec4(vertexPosition_modelspace,1);

//计算世界空间下的物体矩阵(便于等下计算世界空间下光与表面的距离)
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;

//计算观察空间下的物体矩阵
vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;

EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;

//观察空间下光的位置
vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;

//观察空间下表面到点光源的向量
//如果是基于平行光的漫反射此处直接归一化光的方向
LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;

//计算观察空间下的法线数组
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz;

UV = vertexUV;
}

片段着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#version 120

varying vec2 UV;
varying vec3 Position_worldspace;
varying vec3 Normal_cameraspace;
varying vec3 EyeDirection_cameraspace;
varying vec3 LightDirection_cameraspace;

uniform sampler2D myTextureSampler;
uniform mat4 MV;
uniform vec3 LightPosition_worldspace;

void main(){

//光照颜色和强度
vec3 LightColor = vec3(1,1,1);
float LightPower = 50.0f;

//材质本身的颜色也影响最终颜色
//漫反射分量
vec3 MaterialDiffuseColor = texture2D( myTextureSampler, UV ).rgb;
//环境光分量
vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;
//镜面光分量
vec3 MaterialSpecularColor = vec3(0.3,0.3,0.3);

//计算光与表面的距离:光通量与距离的平方成反比(世界空间下)
float distance = length( LightPosition_worldspace - Position_worldspace );

//归一化n和l(观察空间下计算(可以是任意空间))
vec3 n = normalize( Normal_cameraspace );
vec3 l = normalize( LightDirection_cameraspace );

//n:表面法线,l表面到光源的单位向量(与光相反,简化计算),二者计算点积
//如果光源在三角形后面,n和l方向相反,那么n.l是负值。这意味着colour将是一个负值,没有意义。因此这种情况下必须用clamp()将cosTheta截取为0:
float cosTheta = clamp( dot( n,l ), 0,1 );

//E:观察者,R反射光,二者计算点积
vec3 E = normalize(EyeDirection_cameraspace);
vec3 R = reflect(-l,n);//内置函数计算反射光
float cosAlpha = clamp( dot( E,R ), 0,1 );

//最终颜色计算
//聚光灯需要添加余弦参数
gl_FragColor.rgb =
MaterialAmbientColor +
MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) +
//pow(cosAlpha,5)用来控制镜面反射的波瓣,镜面反射与材料本身有关
MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);
}
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信