颜色与光照的关系
- 我们看到的物体的颜色,实际是光照射物体后发射的光进入眼睛后感受到的颜色,而不是物体实际材料的颜色。
- 光照射到物体上,一部分会被物体吸收,一部分被发射进入眼睛,我们看到的颜色就是反射后进入我们眼睛的颜色。
- 颜色吸收和反射的过程可以表示为:
LightIntensity * ObjectColor = ReflectColor
. - 计算为:
(R, G, B) * (X, Y, Z) = (XR, YG, ZB)
Phong Reflection Model
-
Phong Reflection Model是经典的光照模型,它计算光照为:
环境光 + 漫反射光 + 镜面光
环境光
- 环境光是场景中光源给定或者全局给定的一个光照常量。
- 它一般很小,主要是为了模拟计时场景中没有光照时,也不是全部黑屏的效果。
环境光 = 光源的环境光颜色 * 物体的环境光材质颜色
```
varying vec3 objectColor;
void main() {
// 环境光成分,至少有10%的光照到物体的所有面
float = ambientStrength = 0.1f;
// 环境光颜色
vec3 ambient = ambientStrength * lightColor * objectColor;
gl_FragColor = vec4(ambient, 1.0);
}
```
漫反射光
-
漫反射光强度与光线入射方向和物体表面的法向量之间的夹角θ相关。
-
需要计算的两个向量为:
- 光源和顶点位置之间的向量L.
- 法向量N 可以通过顶点属性指定,但顶点经过模型视图变换后需要重新计算。
-
在世界坐标系中,计算L时,光源lightPos是在世界坐标系中指定的位置,直接使用即可。
顶点位置需要变换到世界坐标系中,利用模型(Model)矩阵进行变换:
FragPos = vec3(model * vec4(position, 1.0))
.计算N时,不能直接使用
Model * normal
来换取变换后的法向量,需要使用:Normal = mat3(transpose(inverse(model))) * normal
.其中
inverse()
为求矩阵的逆,transpose()
求矩阵的转置。
-
漫反射颜色 = 光源的漫反射颜色 * 物体的漫反射材质颜色 * DiffuseFactor
,其中DiffuseFactor = max(0, dot(N,L))
顶点着色器代码:
#version 330 layout(location = 0) in vec3 position; layout(location = 1) in vec2 textCoord; layout(location = 2) in vec3 normal; out vec3 FragPos; out vec2 TextCoord; out vec3 FragNormal; uniform mat4 projection; uniform mat4 view; uniform mat4 model; void main() { gl_Position = projection * view * model * vec4(positon, 1.0); FragPos = vec3(model * vec4(position, 1.0)); TextCoord = textCoord; mat3 normalMatrix = mat3(transpose(inverse(model))); FragNormal = normalMatrix * normal; }
片元着色器代码:
#version 330 precision mediump float; uniform vec3 lightPos; // 光源位置 uniform vec3 lightColor; uniform vec3 objectColor; in vec3 FragPos; // 模型变换的顶点位置 in vec2 TextCoord; in vec3 FragNormal; void main() { vec3 lightDir = normalize(lightPos - FragPos); vec3 normal = normalize(FragNormal); float diffuseFactor = max(dot(lightDir, normal), 0.0); vec3 diffuse = diffuseFactor * lightColor * objectColor; gl_FragColor = vec4(diffuse, 1.0); }
镜面反射光
- 镜面光模拟的是物体表面光滑时反射的高亮光,镜面光反射的通常是光的颜色,而不是物体的颜色。
-
计算镜面光,需要考虑光源和顶点位置之间的向量L、法向量N、反射方向R、观察者和顶点位置之间的向量V之间的关系。
当R和V的夹角θ越小时,人眼观察到的镜面光成分越明显。镜面反射系数定义为:
specFactor = cos(θ)^s
, 其中s表示镜面高光系数(shininess),它的值一般取2的整数幂,值越大则高光部分越集中。-
镜面反射颜色 = 光源的镜面光颜色 * 物体的镜面光材质颜色 * specFactor
#version 330 precision mediump float; uniform vec3 lightPos; // 光源位置 uniform vec3 lightColor; uniform vec3 objectColor; uniform vec3 viewPos; in vec3 FragPos; // 模型变换的顶点位置 in vec2 TextCoord; in vec3 FragNormal; void main() { // 镜面反射成分 float specularStrength = 0.5f; vec3 reflectDir = normalize(reflect(-lightDir, normal)); vec3 viewDir = normalize(viewPos - FragPos); float specFactor = pow(max(dot(reflectDir, viewDir), 0.0), 32); //32为镜面高光系数 vec3 specular = specularStrength * specFactor * lightColor * objectColor; gl_FragColor = vec4(specular, 1.0); }
利用reflect函数计算光的出射方向时,要求入射方向指向物体表面位置,因此这里翻转了lightDir
材质属性
不同物体对光有不同的反映,要模拟不同物体接受光照后的效果,需要考虑物体的材质属性。
为物体指定材质属性时,可以为物体指定这三个不同成分的光的强度作为材质属性,同时加上高光系数shininess。
-
同时可以为光源不同成分指定不同的强度。片元着色器的代码为:
#version 330 in vec3 FragPos; in vec2 TextCoord; in vec3 FragNormal; out vec4 color; // 材质属性结构体 struct MaterialAttr { vec3 ambient; // 环境光 vec3 diffuse; // 漫反射光 vec3 specular; // 镜面光 float shininess; //镜面高光系数 }; // 光源属性结构体 struct LightAttr { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; uniform MaterialAttr material; uniform LightAttr light; uniform vec3 viewPos; void main() { // 环境光成分 vec3 ambient = light.ambient * material.ambient; // 漫反射光成分 此时需要光线方向为指向光源 vec3 lightDir = normalize(light.position - FragPos); vec3 normal = normalize(FragNormal); float diffFactor = max(dot(lightDir, normal), 0.0); vec3 diffuse = diffFactor * light.diffuse * material.diffuse; // 镜面反射成分 此时需要光线方向为由光源指出 float specularStrength = 0.5f; vec3 reflectDir = normalize(reflect(-lightDir, normal)); vec3 viewDir = normalize(viewPos - FragPos); float specFactor = pow(max(dot(reflectDir, viewDir), 0.0), material.shininess); vec3 specular = specFactor * light.specular * material.specular; vec3 result = ambient + diffuse + specular; color = vec4(result , 1.0f); }
光源类型
方向光源
方向光源的方向几乎是平行,只有一个方向。
不考虑光的衰减,它与光源的具体位置无关,只需要为它指定方向即可。
-
一般我们指定方向光源的方向时,习惯从光源指向物体,而在计算光照时,又需要从物体指向光源的方向,因此需要做一个翻转。
// 方向光源属性结构体 struct DirLightAttr { vec3 direction; // 方向光源 vec3 ambient; vec3 diffuse; vec3 specular; };
计算光照时,直接使用
direction
vec3 lightDir = normalize(-light.direction)
点光源
- 物体与光源的距离d增大时,光照的强度将会减弱
- 光照强度的衰减系数Fatt与距离d之间的关系为:
点光源结构体:
```
// 点光源属性结构体
struct PointLightAttr
{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant; // 衰减常数
float linear; // 衰减一次系数
float quadratic; // 衰减二次系数
};
```
计算光照强度后乘以衰减系数
```
// 计算衰减因子
float distance = length(light.position - FragPos); // 在世界坐标系中计算距离
float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * distance * distance);
vec3 result = (ambient + diffuse + specular) * attenuation;
color = vec4(result , 1.0f);
```
聚光灯光源
-
聚光灯光源的特点是光只在一个指定的范围内发散。
-
聚光灯需要指定3个属性:
- SpotDir 聚光灯的灯轴的方向
- LightPos 聚光灯的位置
- Cutoff 聚光灯的张角 即图中的ϕ
-
计算聚光灯的光照效果时需要计算的量为:
- lightDir 物体的位置和光源位置之差构成的光照射方向
- θ是lightDir与SpotDir之间的夹角
// 聚光灯光源属性结构体 struct SpotLightAttr { vec3 position; // 聚光灯的位置 vec3 direction; // 聚光灯的spot direction float cutoff; // 聚光灯张角的余弦值 vec3 ambient; vec3 diffuse; vec3 specular; float constant; // 衰减常数 float linear; // 衰减一次系数 float quadratic; // 衰减二次系数 };
片元着色器实现思路:
void main() { // 环境光成分 vec3 lightDir = normalize(light.position - FragPos); // 光线与聚光灯spotDir夹角余弦值 float theta = dot(lightDir, normalize(-light.direction)); if(theta > light.cutoff) { // 在聚光灯张角范围内 计算漫反射光成分 镜面反射成分 } else { // 不在张角范围内时只有环境光成分 } }
参考博客
OpenGL学习脚印:光源类型和使用多个光源(Light source and multiple lights)
OpenGL学习脚印: 光照基础(basic lighting)
OpenGL学习脚印: 光照中材质和lighting maps使用(material and lighting maps)