Learn OpenGL:光照

颜色

直接混合物体的灯光的颜色

1
FragColor = vec4(lightColor * objectColor, 1.0);

基础光照

  • 环境光照(Ambient Lighting):可以认为是常量

  • 漫反射光照(Diffuse Lighting):物体表面与光照越垂直则越亮,可以认为是单位面积收到的光线数量(能量)减少了。需要法向量和灯光方向

  • 镜面光照(Specular Lighting):Phong光照模型通过判断观察方向与光线反射方向的接近程度,Blinn-Pnong光照模型则是通过判断观察方向与光线方向的半程向量与表面法线的夹角的大小。需要法向量、灯光方向和观察方向(用摄像机位置计算)

顶点着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#version 330 core
layout(location = 0) in vec3 aPos; // 0号属性为顶点位置
layout(location = 1) in vec2 aTexCoord; // 1号属性为uv
layout(location = 2) in vec3 aNormal; // 2号属性为法向量

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;

out vec2 TexCoord; // 传递给片段着色器
out vec3 FragPos;
out vec3 Normal;

void main(){
gl_Position = projMat * viewMat * modelMat * vec4(aPos.xyz, 1.0);
TexCoord = aTexCoord;
FragPos = (modelMat * vec4(aPos.xyz, 1.0)).xyz;
Normal = mat3(transpose(inverse(modelMat))) * aNormal;
}

片段着色器

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
#version 330 core

in vec2 TexCoord; // 从顶点着色器接收
in vec3 FragPos;
in vec3 Normal;

uniform sampler2D ourTexture; // 在C++程序里设置
uniform vec3 lightPos;
uniform vec3 ambientColor;
uniform vec3 cameraPos;

out vec3 FragColor;

void main(){
vec3 lightDir = normalize(lightPos - FragPos);
vec3 cameraDir = normalize(cameraPos - FragPos);
vec3 normal = normalize(Normal);
vec4 objColor = texture(ourTexture, TexCoord);
// diffuse
vec3 diffuseColor = max(dot(normal, lightDir), 0) * lightColor;
// specular
vec3 specularColor;
// Phong
vec3 reflectDir = reflect(-lightDir, normal); // 需要的是入射方向
specularColor = pow(max(dot(cameraDir, reflectDir), 0), 100) * lightColor;
// Blinn-Phong
vec3 halfwayDir = normalize(lightDir + cameraDir);
specularColor = pow(max(dot(normal, halfwayDir), 0), 100) * lightColor;

FragColor = vec4(ambientColor + diffuseColor + specularColor, 1) * objColor;
}

这里是在世界空间里进行光照计算,所以需要传入观察者(相机)位置。如果是在观察空间里,那么观察者的位置就是(0,0,0),这样的话FragPosNormalLightPos还要左乘以viewMat

如果是在顶点着色器里计算顶点颜色,传给片段着色器之后直接输出,就叫做Gouraud着色。其他像素的着色是每个三角形三个点直接插值的结果,于是每两个三角形间会有明显的分界线,很不真实,好处是效率高。

材质

把几种系数封装起来,在shader中声明材质结构体并定义实例,可以在在C++程序中指定的uniform名为material.ambient的值

片段着色器

1
2
3
4
5
6
7
8
9
10
...
struct Material{
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess; // 高光系数(指数)
};
uniform Material material;
...
// 计算的ambient、diffuse和specular都乘以结构体的系数

光照贴图

  • 漫反射贴图:将shader里自定的物体颜色去掉,改用贴图上采样出来的颜色作为物体颜色,环境光也是,改为与漫反射一样
  • 镜面光贴图:同样是采样然后作为系数,实现的是类似于遮罩的效果:贴图对应黑色的地方就会使得高光乘积为0,而高光贴图白色的地方对应高光更明显,也就实现了部分高光的效果(画工细腻的话可以在白色里话黑线,实现裂痕的效果)
  • 自发光贴图:累加采样出来的颜色,可以传入时间实现循环亮暗和移动

片段着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
struct Material{
vec3 ambient;
sampler2D diffuse;
sampler2D specular;
sampler2D emission;
float shininess;
};
uniform Material material;
...
void main(){
...
vec3 diffuseTexColor = texture(material.diffuse, TexCoord).rgb;
vec3 ambient = diffuseTexColor * material.ambient * ambientColor;
vec3 diffuse = diffuseTexColor * max(dot(normal, lightDir), 0) * lightColor;
vec3 specular = texture(material.specular, TexCoord).rgb * pow(max(dot(normal, halfwarDir), 0), material.shininess) * lightColor;
vec3 emission = texture(material.emission, TexCoord).rgb; // 对TexCoord取时间上y位移可以实现贴图移动

FragColor = vec4(ambient + diffuse + specular + emission, 1);
}

光源

平行光

光源无限远时光线近乎平行,可以视作平行光。光线方向与物体位置无关,可以认为光线不衰减。需要知道光的方向

将片段着色器中计算的lightDir改为传入的灯光方向即可

点光源

光线方向是光源与物体的连线,会随距离发生平方衰减(二次函数为分母)。需要知道光源和物体的位置

片段着色器

只需要增加衰减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
struct LightPoint{
float constant; // 常数项,一般是1
float linear; // 一次项系数,比如0.1
float quadratic; // 二次项系数,比如0.01
};
uniform LightPoint lightP;
...
void main(){
float dis = length(lightPos - FragPos);
float coefficient = 1.0 /
(lightP.constant + lightP.linear * dis + lightP.quadratic * dis * dis);
...
FragColor = vec4(ambient + emission + (diffuse + specular) * coefficient), 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
...
struct LightSpot{
float cosPhyInner; // 内张角的cos值
float cosPhyOuter; // 外张角的cos值
};
LightSpot lightS;
uniform vec3 lightDirUnifrom; // 聚光灯中心方向(指向灯)
...
void main(){
vec3 lightDir = normalize(lightPos - FragPos); // 光源与像素点的连线
...
float spotRatio;
float cosTheta = dot(-lightDir, -lightDirUniform); // 像素点与灯的连线和光线中心的夹角cos值
if(cosTheta > lightS.cosPhyInner){ // 在内圈
spotRatio = 1;
}
else if(cosTheta > lightS.cosPhyOuter){ // 在外圈
spotRatio = (cosTheta - lightS.cosPhyOuter) / (lightS.cosPhyInner - lightS.cosPhyOuter);
}
else{ // 在聚光灯之外
SpotRatio = 0;
}
// spotRatio的计算也可以简化为clamp((cosTheta - lightS.cosPhyOuter) / (lightS.cosPhyInner - lightS.cosPhyOuter), 0, 1);
FragColor = vec4(ambient + emission + (diffuse + specular) * spotRatio), 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
...
uniform LightDirectional lightD;
uniform LightPoint lightP0;
uniform LightPoint lightP1;
uniform LightPoint lightP2;
uniform LightPoint lightP3;
uniform LightSpot lightS;
...
void main(){
vec3 finalResult = vec3(0, 0, 0);
vec3 uNormal = normalize(Normal); // unit normal
vec3 dirToCamera = normalize(cameraPos - FragPos);

finalResult += CalcLightDirectional(lightD, uNormal, dirToCamera);
finalResult += CalcLightPoint(lightP0, uNormal, dirToCamera);
finalResult += CalcLightPoint(lightP1, uNormal, dirToCamera);
finalResult += CalcLightPoint(lightP2, uNormal, dirToCamera);
finalResult += CalcLightPoint(lightP3, uNormal, dirToCamera);
finalResult += CalcLightSpot(lightS, uNormal, dirToCamera);

vec3 ambient = texture(material.diffuse, TexCoord).rgb * material.ambient * ambientColor;
finalResult += ambient;

FragColor = vec4(finalResult, 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
struct LightDirectional{
vec3 pos;
vec3 color;
vec3 dirToLight; // 光的方向
};
...
// light:平行光,uNormal:着色点单位法向量,dirToCamera:从着色点指向相机
vec3 CalcLightDirectional(LightDirectional light, vec3 uNormal, vec3 dirToCamera){

// diffuse
float diffIntensity = max(dot(light.dirToLight, uNormal), 0); // 光的方向
vec3 diffuseColor = texture(material.diffuse, TexCoord).rgb * diffIntensity * light.color;

// specular Phong
// vec3 refl = normalize(reflect(-light.dirToLight, uNormal));
// float specIntensity = pow(max(dot(refl, dirToCamera), 0), material.shininess);
// vec3 specularColor = texture(material.specular, TexCoord).rgb * specIntensity * light.color;

// specular Blinn-Phong
vec3 halfwayDir = normalize(light.dirToLight + dirToCamera);
float specIntensity = pow(max(dot(halfwayDir, uNormal), 0), material.shininess);
vec3 specularColor = texture(material.specular, TexCoord).rgb * specIntensity * light.color;

vec3 result = diffuseColor + specularColor;
return result;
}

计算点光

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
struct LightPoint{
vec3 pos;
vec3 color;
vec3 dirToLight;
float constant;
float linear;
float quadratic;
};
...
vec3 CalcLightPoint(LightPoint light, vec3 uNormal, vec3 dirToCamera){

// attenuation
vec3 dirToLight = normalize(light.pos - FragPos);
float dist = length(light.pos - FragPos);
float attenuation = 1 / (light.constant + light.linear * dist + light.quadratic * (dist * dist));

// diffuse
float diffIntensity = max(dot(dirToLight, uNormal), 0); // 不是用光的方向,要衰减
vec3 diffuseColor = texture(material.diffuse, TexCoord).rgb * diffIntensity * light.color;

// specular Phong
// vec3 refl = normalize(reflect(-normalize(light.pos - FragPos), uNormal));
// float specIntensity = pow(max(dot(refl, dirToCamera), 0), material.shininess) * attenuation; // 不是用光的方向,要衰减
// vec3 specColor = specIntensity * light.color * texture(material.specular, TexCoord).rgb;

// specular Blinn-Phong specular
vec3 halfwayDir = normalize(dirToLight + dirToCamera);
float specIntensity = pow(max(dot(halfwayDir, uNormal), 0), material.shininess);
vec3 specularColor = texture(material.specular, TexCoord).rgb * specIntensity * light.color;

vec3 result = (diffuseColor + specularColor) * attenuation;
return result;
}

计算聚光

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
struct LightSpot{
vec3 pos;
vec3 color;
vec3 dirToLight;
float constant;
float linear;
float quadratic;
float cosPhyInner;
float cosPhyOuter;
};
...
vec3 CalcLightSpot(LightSpot light, vec3 uNormal, vec3 dirToCamera){

// attenuation
vec3 dirToLight = normalize(light.pos - FragPos);
float dist = length(light.pos - FragPos);
float attenuation = 1 / (light.constant + light.linear * dist + light.quadratic * (dist * dist));

float cosTheta = dot(normalize(FragPos - light.pos), -light.dirToLight);
float spotRatio;
if(cosTheta > light.cosPhyInner){
spotRatio = 1;
}
else if(cosTheta > light.cosPhyOuter){
spotRatio = (cosTheta - light.cosPhyOuter) / (light.cosPhyInner - light.cosPhyOuter);
}
else{
spotRatio = 0;
}

// diffuse
float diffIntensity = max(dot(dirToLight, uNormal), 0); // 不是用光的方向,要衰减
vec3 diffuseColor = diffIntensity * light.color * texture(material.diffuse, TexCoord).rgb;

// specular Phong
// vec3 refl = normalize(reflect(-dirToLight, uNormal));
// float specIntensity = pow(max(dot(refl, dirToCamera), 0), material.shininess); // 不是用光的方向,要衰减
// vec3 specColor = specIntensity * light.color * texture(material.specular, TexCoord).rgb;

// specular Blinn-Phong
vec3 halfwayDir = normalize(dirToLight + dirToCamera);
float specIntensity = pow(max(dot(halfwayDir, uNormal), 0), material.shininess);
vec3 specularColor = texture(material.specular, TexCoord).rgb * specIntensity * light.color;

vec3 result = (diffuseColor + specularColor) * attenuation * spotRatio;
return result;
}