OpenGL(十三)——世界光照

目录

一、前言

二、平行光

 2.1 片段着色器

2.2 app渲染

三、点光源

3.1 距离衰减

 3.2 衰减片段着色器

四、聚光

4.1 片段着色器

4.2 光照入射方向 

4.3 平滑边缘


一、前言

Light Caster :光投射(Cast)到物体的光源。现实世界中通常多种不同的光源类型共同作用在物体表面,包括平行光(定向光)、点光源、聚光。

二、平行光

当光源处于一个很远地方,在物体上的光线可以看作是近似相互平行的。这种光线被称为定向光或者平行光,由于光线是相同的方向,所有它与光源位置是无关的。因此直接使用光线向量来替代位置向量来计算light Direction。

 2.1 片段着色器

将光结构体的位置向量去掉,使用direction变量,然后重新计算lightDir即可

#version 330 core

struct Material{
    sampler2D diffuse;//漫反射纹理贴图
    sampler2D specular;
    float shininess;
};

struct Light{
    vec3 direction;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

out vec4 FragColor;

in vec3 Normal;  //法向量:垂直于顶点表面的向量
in vec3 FragPos;  //片段位置向量:世界坐标系下
in vec2 TexCoords;

uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量

uniform vec3 lightColor;
uniform Material material;
uniform Light light;

void main()
{
    // 环境光照
    vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
  	
    // 漫反射 
    vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
    vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
    float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
    vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
    
    // 镜面反射 + 反光强度Shininess(镜面高光散射半径)
    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);  
        
    FragColor = vec4(ambient + diffuse + specular, 1.0);
} 

2.2 app渲染

为了体现光照对不同姿态物体的影响,设置物体的全局位置和旋转角度,计算模型矩阵(从局部到世界空间)

//箱子全局位置
glm::vec3 cubePositions[] = {
    glm::vec3(0.0f,  0.0f,  0.0f),
    glm::vec3(2.0f,  5.0f, -15.0f),
    glm::vec3(-1.5f, -2.2f, -2.5f),
    glm::vec3(-3.8f, -2.0f, -12.3f),
    glm::vec3(2.4f, -0.4f, -3.5f),
    glm::vec3(-1.7f,  3.0f, -7.5f),
    glm::vec3(1.3f, -2.0f, -2.5f),
    glm::vec3(1.5f,  2.0f, -2.5f),
    glm::vec3(1.5f,  0.2f, -1.5f),
    glm::vec3(-1.3f,  1.0f, -1.5f)
};
//设置光源向量
ObjShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);

//模型矩阵, 世界坐标不同位置的立方体
        glBindVertexArray(ObjVAO);
        for (unsigned int i = 0; i < 10; i++)
        {
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cubePositions[i]);
            float angle = 20 * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            ObjShader.setMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

 

三、点光源

3.1 距离衰减

点光源是世界坐标中定义某一个位置的光源,它朝所有方向发光,但是光线会随着距离逐渐衰减。

 使用一个方程来表示随距离增长而线性减少光强度:

 d表示片段光源的距离,k是可配置的衰减常数项系数。Kc通常为1;一次项保证以线性方式减少强度,二次项在距离近时影响比一次项小很多,当距离远时影响比较大。下表是在指定3项的情况下能覆盖的距离:

 3.2 衰减片段着色器

  1. 在光源结构体中添加衰减项系数
  2. 计算光源位置到片段的距离
  3. 计算衰减值
  4. 计算环境光照、漫反射、镜面反射强度
  5. 使用衰减值和强度值计算最终颜色
#version 330 core

struct Material{
    sampler2D diffuse;//漫反射纹理贴图
    sampler2D specular;
    float shininess;
};

struct Light{
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

out vec4 FragColor;

in vec3 Normal;  //法向量:垂直于顶点表面的向量
in vec3 FragPos;  //片段位置向量:世界坐标系下
in vec2 TexCoords;

uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量

uniform vec3 lightColor;
uniform Material material;
uniform Light light;

void main()
{
    // 环境光照
    vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
  	
    // 漫反射 
    vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
    //vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
    vec3 lightDir = normalize(FragPos - light.position);//从片段到光源的光线方向
    float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
    vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
    
    // 镜面反射 + 反光强度Shininess(镜面高光散射半径)
    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);  
    
    //衰减
    float distance = length(light.position - FragPos );
    float attenuate =  1/(light.constant + light.linear*distance + light.quadratic*distance*distance);

    FragColor = vec4((ambient + diffuse + specular)*attenuate, 1.0);
} 

在app中设置衰减参数

        //ObjShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
        ObjShader.setVec3("lightPos", lightPos);
        //ObjShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
        ObjShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
        ObjShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
        ObjShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
        //衰减
        ObjShader.setFloat("light.constant", 1.0f);
        ObjShader.setFloat("light.linear", 0.09f);
        ObjShader.setFloat("light.quadratic", 0.032f);

 

四、聚光

聚光是环境中某个位置的光源,它只朝某一个特定方向而不是所有方向照射光线。效果是只有在聚光方向的特定半径内的物体才会被照亮,其它物体都会保持黑暗,类似路灯或者手电筒。

  •  LightDir是片段到光源的向量
  • SpotDir是聚光所指向的方向
  • Φ是聚光半径的切光角,落在这个角度之外的物体都不会被这个聚光所照亮
  • θ是LightDir和SpotDir夹角

计算LightDir和SpotDir向量之间的点击,得到的结果与切光角对比。LightDir与direction向量之间的点积得到夹角余弦值。

 越接近90度表示的是0的余弦值

4.1 片段着色器

在光照中定义聚光信息,然后对比lightDir和SpotDir夹角是否在切光角范围内执行不同光照算法:

#version 330 core

struct Material{
    sampler2D diffuse;//漫反射纹理贴图
    sampler2D specular;
    float shininess;
};

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

out vec4 FragColor;

in vec3 Normal;  //法向量:垂直于顶点表面的向量
in vec3 FragPos;  //片段位置向量:世界坐标系下
in vec2 TexCoords;

uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量

//uniform vec3 lightColor;
uniform Material material;
uniform Light light;

void main()
{
    vec3 lightDir = normalize(light.position - FragPos);//从片段到光源的光线方向
    // 检查光照是否在切光角内
    float theta = dot(lightDir, normalize(-light.direction)); 
    if(theta > light.cutOff)
    {
        // 环境光照
        vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
  	
        // 漫反射 
        vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
        //vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
    
        float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
        vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
    
        // 镜面反射 + 反光强度Shininess(镜面高光散射半径)
        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);  
    
        //衰减
        float distance = length(light.position - FragPos );
        float attenuate =  1/(light.constant + light.linear*distance + light.quadratic*distance*distance);
        //ambient  *= attenuate;//导致较远的距离,在切光角内更黑
        diffuse   *= attenuate;
        specular *= attenuate;  
        FragColor = vec4(ambient + diffuse + specular, 1.0);
    }
    else
    {
        //使用环境光,让场景在聚光之外时不至于完全黑暗
        FragColor = vec4(light.ambient * texture(material.diffuse, TexCoords).rgb, 1.0);
    }
} 

在app中对聚光信息设值:

        ObjShader.setVec3("light.position", lightPos);
        ObjShader.setVec3("light.direction", camera.Front);
        ObjShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));

4.2 光照入射方向 

关于光源LightDir的疑惑,到底是light.position - FragPos还是 FragPos -light.position,取决于场景,如漫反射是从物体到光源方向的反射所以是light.position - FragPos,

模拟平行光是从光源出发的向量lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f); 光的方向是朝下的,要参考右手定则,设为vec3 lightDir = normalize(-light.direction);对向量取反,得到一个从光源出发的全局方向。

4.3 平滑边缘

为了创建一个边缘平滑的聚光,模拟聚光有一个内圆锥、外圆锥。将内圆锥设置为上一部分中的那个圆锥,外圆锥来让光从内圆锥逐渐减暗,直到外圆锥的边界

定义一个余弦值来代表聚光方向向量 和 外圆锥向量(等于它的半径)夹角。

如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。

 ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ,I就是在当前片段聚光的强度

 片段着色器:

#version 330 core

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;
};

out vec4 FragColor;

in vec3 Normal;  //法向量:垂直于顶点表面的向量
in vec3 FragPos;  //片段位置向量:世界坐标系下
in vec2 TexCoords;

uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量

//uniform vec3 lightColor;
uniform Material material;
uniform Light light;

void main()
{
    // 环境光照
    vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
  	
    // 漫反射 
    vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
    //vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
    vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
    
    // 镜面反射 + 反光强度Shininess(镜面高光散射半径)
    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;  
    
    //聚光+平滑
    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;

    //衰减
    float distance = length(light.position - FragPos );
    float attenuate =  1/(light.constant + light.linear*distance + light.quadratic*distance*distance);
    ambient  *= attenuate;
    diffuse   *= attenuate;
    specular *= attenuate;  
    FragColor = vec4(ambient + diffuse + specular, 1.0);
} 

app设置外圆锥切角:

ObjShader.setFloat("light.outerCutOff", glm::cos(glm::radians(17.5f)));

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值