当前位置:网站首页>OpenGL - Lighting
OpenGL - Lighting
2022-07-05 09:12:00 【农场主er】
我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。换句话说,那些不能被物体所吸收(Absorb)的颜色就是我们能够感知到的物体的颜色。
简单理解就是RGB
分量的乘法:
glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);
为了模拟更真实的光照效果,按照冯氏光照模型(Phong Lighting Model)
分为环境光(Ambient Lighting)
、漫反射(Diffuse Lighting)
和镜面反射(Specular Lighting)
。
Ambient lighting
光的一个属性是,它可以向很多方向发散并反弹,从而能够到达不是非常直接临近的点。所以,光能够在其它的表面上反射,对一个物体产生间接的影响。我们使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。
// fragment shader
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
Diffuse lighting
为计算漫反射,我们需要知道:
- 法向量(
Normal vector
):一个垂直于顶点表面的向量。 - 定向的光线(
The directed light ray
):作为光源的位置与片段的位置之间向量差的方向向量。
法向量是作用在世界坐标系中的,为了解决物体非等比缩放导致的法向量和表面不垂直,可以通过法线矩阵(Normal Matrix)
纠正这种行为:
Normal = mat3(transpose(inverse(model))) * aNormal;
Specular Lighting
和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。由此产生的效果就是,我们看向在入射光在表面的反射方向时,会看到一点高光。
计算反射向量可以用reflect(光源指向片段的向量,法向量)
,计算镜面分量可以用:
// This 32 value is the shininess value of the highlight.
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
Materials
现实世界中不同物体处于同一光源下表现也不尽相同,原因在于材质不同:
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
对于光源而言,三种属性对物体的影响比例也不同:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
综合两种结构,物体的实际颜色计算如下:
in vec3 FragPos;
in vec3 Normal;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse);
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
Lighting maps
上个小节中材质中的漫反射和镜面反射的颜色对于整个物体来说都是一样的,如果把纹理应用到反射中,就可以称为漫反射贴图和镜面光贴图。环境光颜色在几乎所有情况下都等于漫反射颜色,所以我们不需要环境光贴图。
Light casters
现实世界中,我们有很多种类的光照,每种的表现都不同。将光投射(Cast)到物体的光源叫做投光物(Light Caster)
。
Directional Light
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行,只需要光源方向就可以模拟出定向光。
struct Light {
// vec3 position; // 使用定向光就不再需要了
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
...
void main()
{
vec3 lightDir = normalize(-light.direction);
...
}
Point lights
点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但随着光线传播距离的增长,会逐渐削减光的强度,称为衰减(Attenuation)
,规律如下:
由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:
至于这些常数项如何选择,可以参考 ->
代码实现如下:
#version 330 core
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
Spotlight
聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),即计算LightDir
向量和SpotDir
向量之间的点积,并将它与切光角ϕ
值对比,如果符合要求的话,我们就会相应地照亮片段。
为了创建一种看起来边缘平滑的聚光,我们需要模拟聚光有一个内圆锥(Inner Cone)
和一个外圆锥(Outer Cone)
。我们可以将内圆锥设置为上一部分中的那个圆锥,但我们也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
为了创建一个外圆锥,我们只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。
代码实现如下:
#version 330 core
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
// spotlight (soft edges)
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = (light.cutOff - light.outerCutOff);
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
diffuse *= intensity;
specular *= intensity;
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
边栏推荐
- OpenFeign
- 2011-11-21 training record personal training (III)
- 深入浅出PyTorch中的nn.CrossEntropyLoss
- AdaBoost use
- Composition of applet code
- [code practice] [stereo matching series] Classic ad census: (5) scan line optimization
- Progressive JPEG pictures and related
- OpenFeign
- Ros- learn basic knowledge of 0 ROS - nodes, running ROS nodes, topics, services, etc
- 什么是防火墙?防火墙基础知识讲解
猜你喜欢
Nodemon installation and use
nodejs_ fs. writeFile
[code practice] [stereo matching series] Classic ad census: (4) cross domain cost aggregation
Svg optimization by svgo
Multiple solutions to one problem, asp Net core application startup initialization n schemes [Part 1]
AUTOSAR从入门到精通100讲(103)-dbc文件的格式以及创建详解
Nodejs modularization
Hi Fun Summer, play SQL planner with starrocks!
Applet data attribute method
混淆矩阵(Confusion Matrix)
随机推荐
c#比较两张图像的差异
容易混淆的基本概念 成员变量 局部变量 全局变量
What is a firewall? Explanation of basic knowledge of firewall
Hi Fun Summer, play SQL planner with starrocks!
编辑器-vi、vim的使用
Huber Loss
Driver's license physical examination hospital (114-2 hang up the corresponding hospital driver physical examination)
阿里云发送短信验证码
Luo Gu p3177 tree coloring [deeply understand the cycle sequence of knapsack on tree]
Golang foundation -- map, array and slice store different types of data
2311. Longest binary subsequence less than or equal to K
Add discount recharge and discount shadow ticket plug-ins to the resource realization applet
Pearson correlation coefficient
Rebuild my 3D world [open source] [serialization-1]
Programming implementation of ROS learning 6 -service node
Nodemon installation and use
微信H5公众号获取openid爬坑记
My experience from technology to product manager
Programming implementation of ROS learning 5-client node
Use and programming method of ros-8 parameters