第十一课,OpenGL光照之投光物

1.平行光

平行光,即光线方向为定值。
在点光源中,我们通过 lightDir = normalize(light.position.xyz - FragPos);来确定光线照射角度,而在平行光中lightDir(光照角度)是给定的,故我们只需要用给定的光照角度即可。

但是我们如何判定一个光源是平行光还是点光源呢?
我们已知:平行光是一个方向向量,点光源是一个位置向量。
平行光可以改变方向,而不可以移动。点光源可移动。
故如果我们对世界坐标所有对象做一次旋转,则用一个旋转矩阵 乘 平行光可改变平行光照射方向,而用一个旋转矩阵 乘 点光源 可改变点光源位置。
但如果用一个平移矩阵对世界坐标所有对象做一次位移,则只会改变点光源位置。

由先前学习知,vec4(x,y,z,w),当w0.0f时,变化矩阵对该向量的位移失效。
故,方向向量就会像这样来表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)。(w==0)
而位置向量会用vec4(0.2f, 1.0f, 0.3f, 1.0f)(w == 1)来表示。

这正是旧OpenGL(固定函数式)决定光源是定向光还是位置光源(Positional Light Source)的方法,并根据它来调整光照。

代码实现:

struct Light {
    vec4 position;
    vec3 color;
};

position存储点光源和平行光的数据。

vec3 lightDir;
if(light.position.w != 0.0f ){//点光源 	
    lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源    
}
else{//平行光
    lightDir = normalize(-light.position.xyz);
}

position.w来确定是点光源还是平行光。

2.点光源的衰减

F

a

t

t

=

1.0

/

(

K

c

+

K

l

d

+

K

q

d

2

)

Fatt=1.0/(Kc+Kl∗d+Kq∗d^2)

Fatt=1.0/(Kc+Kld+Kqd2)
d 代表了片段距光源的距离
定义3个(可配置的)项:常数项Kc、一次项Kl和二次项Kq。

  1. 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
  2. 一次项会与距离值相乘,以线性的方式减少强度。
  3. 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。

设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等

实现衰减

struct Light {
    vec4 position;
    vec3 color;

    float constant;//常数项Kc,恒为1,保证分母永远不会比1小
    float linear;//一次项Kl,与距离值相乘,以线性的方式减少强度。
    float quadratic;//二次项Kq,与距离的平方相乘,让光源以二次递减的方式减少强度。
};

quadratic 美 /kwɑːˈdrætɪk/ adj. [数] 二次的。

点光源的衰减实现:

vec3 lightDir;
float attenuation = 1.0f;
if(light.position.w != 0.0f ){//点光源 	
    float distance = length(light.position.xyz - FragPos);//点光源距离
    attenuation = 1.0 / (light.constant + light.linear * distance + \\
                light.quadratic * (distance * distance));点光源强度衰减系数计算
    lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源    
}
else{//平行光
    lightDir = normalize(-light.position.xyz);
}

attenuation 美 /əˌtenjuˈeɪʃn/ n. [物] 衰减;变薄;稀释

3.聚光(手电筒,台灯等大多光源)

在这里插入图片描述

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
  • Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。

所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积,并将它与切光角ϕ值对比

代码

首先传入片元着色器数据

lightShader.setVec3("light.direction", -lightpos);
		//聚焦(0.0)点
lightShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
		//聚光角度12.5°

通过cos值来计算是否在聚光角度内部

//聚光
    vec3 diffuse = vec3(0.0f,0.0f,0.0f);
    vec3 specular = vec3(0.0f,0.0f,0.0f);
    float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
    
    if(costheta > light.cutOff){
        // diffuse漫反射光照值
        float diff = max(dot(norm, lightDir), 0.0);
        diffuse = (material.diffuseStrength * light.color) * diff * objectColor;
        // specular镜面反射值
        vec3 viewDir = normalize(viewPos - FragPos);//绘制点指向摄像机
        vec3 reflectDir = reflect(-lightDir, norm); //reflect(入射光线,法线),返回反射向量
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//两向量点乘后的32次方,次方数大--集中程度高     vec3 specular = (material.specularStrength * light.color) * spec * specularColor; //反光强度*反光角度权值*光照颜色 
        specular = material.specularStrength * spec * specularColor;  
    }

costheta > light.cutOff时,说明在聚光内,计算漫反射值以及镜面反射值。

运行结果

在这里插入图片描述
可以看出,效果并不是很好,有明显的明暗界限,适合用于一些恐怖游戏视角,但这并不是现实手电筒的效果!!!

平滑/软化边缘

I

=

(

θ

γ

)

/

ϵ

,

ϵ

=

ϕ

γ

I=(θ−γ)/ϵ \\,, ϵ=ϕ−γ

I=(θγ)/ϵ,ϵ=ϕγ

  • θ

    θ

    θ为光源到该片元的余弦值(costheta)

  • γ

    γ

    γ外圈余弦值(outerCutOff)

  • ϕ

    ϕ

    ϕ内圈余弦值(cutOff)

  • ϵ

    ϵ

    ϵ为内外圈余弦值差(epsilon)

  • I

    I

    I强度值(intensity)

首先需要传入内外光圈角度

lightShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));//内光圈角度12.5°
lightShader.setFloat("light.outerCutOff", glm::cos(glm::radians(15.5f)));//外光圈角度15.5°

然后

float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
float epsilon   = light.cutOff - light.outerCutOff;//内外光圈cos差
float intensity = clamp((costheta - light.outerCutOff) / epsilon, 0.0, 1.0); //clamp GLSL内置函数,返回介于参数2和参数3之间的数

计算

c

o

s

θ

cosθ

cosθ

ϵ

ϵ

ϵ 以及 intensity (强度)

T clamp(T x, T minVal, T maxVal)函数为GLSL内置函数,返回值x被限定在minVal到maxVal之间。

之后,强度值直接乘以光照分量。

diffuse  *= intensity;
specular *= intensity;

完整代码

#version 450 core
out vec4 FragColor;

in vec3 Normal;  
in vec3 FragPos;  
in vec2 TexCoords;

struct Material {
    sampler2D sampler;  
    sampler2D sampler1;
    float shininess;
    float ambientStrength;
    float diffuseStrength;
    float specularStrength;
}; 
  
struct Light {
    vec4 position;
    vec3 direction;//聚光方向
    vec3 color;
    float cutOff;//内光圈
    float outerCutOff;//外光圈

    float constant;//常数项Kc,恒为1,保证分母永远不会比1小
    float linear;//一次项Kl,与距离值相乘,以线性的方式减少强度。
    float quadratic;//二次项Kq,与距离的平方相乘,让光源以二次递减的方式减少强度。
};

uniform Material material;
uniform Light light;

uniform vec3 viewPos;

vec3 getResult(Light light){
    //纹理颜色
    vec3 objectColor = texture(material.sampler, TexCoords).rgb; 
    vec3 specularColor = texture(material.sampler1, TexCoords).rgb; 

    // ambient全局光照值
    vec3 ambient = (material.ambientStrength * light.color) * objectColor;
    
    //该点光线方向 及 衰减值
    vec3 lightDir;
    float attenuation = 1.0f;
    if(light.position.w != 0.0f ){//点光源 	
        float distance = length(light.position.xyz - FragPos);//点光源距离
        attenuation = 1.0 / (light.constant + light.linear * distance + \\
                    light.quadratic * (distance * distance));点光源强度衰减系数计算
        lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源    
    }
    else{//平行光
        lightDir = normalize(light.position.xyz);
    }

    // diffuse漫反射光照值
    vec3 norm = normalize(Normal);
    float diff = max(dot(norm, lightDir), 0.0);//向量点乘,角度越大数值越小.(不为负数)
    vec3 diffuse = (material.diffuseStrength * light.color) * diff * objectColor;//漫反射强度与光线到该点与该平面的夹角有关,夹角越小,光照越弱.
    
    // specular镜面反射值
    vec3 viewDir = normalize(viewPos - FragPos);//绘制点指向摄像机
    vec3 reflectDir = reflect(-lightDir, norm); //reflect(入射光线,法线),返回反射向量
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//两向量点乘后的32次方,次方数大--集中程度高     vec3 specular = (material.specularStrength * light.color) * spec * specularColor; //反光强度*反光角度权值*光照颜色 
    vec3 specular = material.specularStrength * spec * specularColor;  

    //聚光
    float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
    float epsilon   = light.cutOff - light.outerCutOff;//内外光圈cos差
    float intensity = clamp((costheta - light.outerCutOff) / epsilon, 0.0, 1.0); //clamp GLSL内置函数,返回介于参数2和参数3之间的数
    diffuse  *= intensity;
    specular *= intensity;

    vec3 result = (ambient + diffuse + specular) * attenuation;//最终颜色 * 距离衰减系数
    return result;
} 

void main()
{
    vec3 result = getResult(light);
    FragColor = vec4(result, 1.0);
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容