当前位置:网站首页>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);
} 

综合以上三种光源,效果可以参考 ->

原网站

版权声明
本文为[农场主er]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_42403042/article/details/125573185