[OpenGL]投光物LightCasters

在Unity中,有 DirectonLight/PointLight/SpotLight 三种基础灯光。这节就来实现这三样啦。

 

首先,先上本接要用的箱子数据

glm::vec3 LightCasters::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)
};

其次,上本章的顶点着色器

#Shader Vertex
#version 330 core
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
layout(location = 2) in vec2 a_texCoord;

uniform mat4 u_Model;
uniform mat4 u_View;
uniform mat4 u_Projection;

out vec3 v3_Normal;
out vec3 v3_WorldPos;
out vec2 v2_TexCoord;

void main()
{
    vec3 vertexPosition = a_position + vec3(0.0, 1.0, 0.0);
    gl_Position = u_Projection * u_View * u_Model * vec4(vertexPosition, 1.0);
    v3_WorldPos = vec3(u_Model * vec4(vertexPosition, 1.0));
    v3_Normal = mat3(transpose(inverse(u_Model))) * a_normal;

    v2_TexCoord = a_texCoord;
}

 

 

平行光
class DirectionLight : public LightTexture::Basic
{
public:
    virtual void Setup() override
    {
        m_CubeShader = "SandBox/12_LightCasters/DirectionLight.shader";
        Basic::Setup();
        m_Texture[1] = new Texture("Resources/Textures/woodbox_specular.png");

        m_Light.direction = glm::vec3(-0.2f, -1.0f, -0.3f);
        m_Light.ambient = glm::vec3(0.2f, 0.2f, 0.2f);
        m_Light.diffuse = glm::vec3(0.5f, 0.5f, 0.5f);
        m_Light.specular = glm::vec3(1.0f, 1.0f, 1.0f);
    }

    virtual void Render() override
    {
        m_Texture[0]->Bind(0);
        m_Texture[1]->Bind(1);
        for (int i = 0; i < 10; i++)
        {
            m_Shader[0]->Bind();

            glm::mat4 model = glm::identity<glm::mat4>();
            model = glm::translate(model, cubePositions[i]);
            float angle = i * 20.0f;
            model = glm::rotate(model, glm::radians(angle) , glm::vec3(1.0f, 0.3f, 0.5f));
            m_Shader[0]->SetMat4f("u_Model", model);

            m_Shader[0]->SetMat4f("u_Projection", m_Camera->GetProjectionMatrix());
            m_Shader[0]->SetMat4f("u_View", m_Camera->GetViewMatrix());
            m_Shader[0]->SetVec3("u_ViewPos", m_Camera->GetEye());

            m_Shader[0]->SetVec3("light.direction", m_Light.direction);
            m_Shader[0]->SetVec3("light.ambient", m_Light.ambient);
            m_Shader[0]->SetVec3("light.diffuse", m_Light.diffuse);
            m_Shader[0]->SetVec3("light.specular", m_Light.specular);

            m_Shader[0]->SetInt("material.diffuse", 0);
            m_Shader[0]->SetInt("material.specular", 1);
            m_Shader[0]->SetFloat("material.shininess", m_Material.shininess);

            this->m_Renderer.Draw(m_VA[0], 36, m_Shader[0]);
        }
    }
};
// fragment shader
#Shader Fragment
#version 330 core

out vec4 fragColor;

in vec3 v3_Normal;
in vec3 v3_WorldPos;
in vec2 v2_TexCoord;

struct Material
{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};
uniform Material material;

struct Light
{
    vec3 direction; // 定向光不需要位置,只需要方向

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

uniform vec3 u_ViewPos;

void main()
{
    vec3 diffuseColor = vec3(texture(material.diffuse, v2_TexCoord));

    vec3 ambient = light.ambient * diffuseColor;

    vec3 N = normalize(v3_Normal);
    vec3 lightDir = normalize(-light.direction); // 传入的lightDir是从光源出发的一个全局方向,而shader中计算要求lightDir是片元到光源的方向,所以要取反
    float diff = max(dot(N, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffuseColor);

    vec3 specularColor = vec3(texture(material.specular, v2_TexCoord));
    vec3 viewDir = normalize(u_ViewPos - v3_WorldPos);
    vec3 reflectDir = reflect(-lightDir, N);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * specularColor);

    vec3 result = ambient + diffuse + specular;
    fragColor = vec4(result, 1.0);
}

 

directionlight

 

点光源

点光源,根据衰减公式可以得到不同距离上的强度。如下图

衰减值

// 点光源是有衰减的
// 随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)
// 在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,
// 但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。
// F_{att} = 1.0 / (K_c + K_l ∗ d + K_q ∗ d^2)  k_c是常数项 k_l是一次项  d是光源到顶点的距离 k_q是二次项
class PointLight : public LightTexture::Basic
{
public:
    virtual void Setup() override
    {
        m_CubeShader = "SandBox/12_LightCasters/PointLight.shader";
        Basic::Setup();
        m_Texture[1] = new Texture("Resources/Textures/woodbox_specular.png");

        m_Light.position = glm::vec3(1.2f, 1.0f, 2.0f);
        m_Light.ambient = glm::vec3(0.2f, 0.2f, 0.2f);
        m_Light.diffuse = glm::vec3(0.5f, 0.5f, 0.5f);
        m_Light.specular = glm::vec3(1.0f, 1.0f, 1.0f);
    }

    virtual void Render() override
    {
        m_Texture[0]->Bind(0);
        m_Texture[1]->Bind(1);
        for (int i = 0; i < 10; i++)
        {
            m_Shader[0]->Bind();

            glm::mat4 model = glm::identity<glm::mat4>();
            model = glm::translate(model, cubePositions[i]);
            float angle = i * 20.0f;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            m_Shader[0]->SetMat4f("u_Model", model);

            m_Shader[0]->SetMat4f("u_Projection", m_Camera->GetProjectionMatrix());
            m_Shader[0]->SetMat4f("u_View", m_Camera->GetViewMatrix());
            m_Shader[0]->SetVec3("u_ViewPos", m_Camera->GetEye());

            m_Shader[0]->SetVec3("light.position", m_Light.position);
            m_Shader[0]->SetVec3("light.ambient", m_Light.ambient);
            m_Shader[0]->SetVec3("light.diffuse", m_Light.diffuse);
            m_Shader[0]->SetVec3("light.specular", m_Light.specular);
            m_Shader[0]->SetFloat("light.constant", 1.0f);
            m_Shader[0]->SetFloat("light.linear", 0.09f);
            m_Shader[0]->SetFloat("light.quadratic", 0.032f);

            m_Shader[0]->SetInt("material.diffuse", 0);
            m_Shader[0]->SetInt("material.specular", 1);
            m_Shader[0]->SetFloat("material.shininess", m_Material.shininess);

            this->m_Renderer.Draw(m_VA[0], 36, m_Shader[0]);
        }


        this->m_Renderer.Draw(m_VA[1], 36, m_Shader[1]);
        m_Shader[1]->SetMat4f("u_Projection", m_Camera->GetProjectionMatrix());
        m_Shader[1]->SetMat4f("u_View", m_Camera->GetViewMatrix());
        m_Shader[1]->SetVec3("u_LightColor", 1.0f, 1.0f, 1.0f);
        m_Model = glm::identity<glm::mat4>();
        m_Model = glm::translate(m_Model, m_Light.position);
        m_Model = glm::scale(m_Model, glm::vec3(0.2f));
        m_Shader[1]->SetMat4f("u_Model", m_Model);

    }
};
#Shader Fragment
#version 330 core

out vec4 fragColor;

in vec3 v3_Normal;
in vec3 v3_WorldPos;
in vec2 v2_TexCoord;

struct Material
{
    sampler2D diffuse;
    sampler2D specular;
    float shininess; // 反光度
};
uniform Material material;

struct Light
{
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant; // 常数项
    float linear;   // 一次项
    float quadratic; // 平方项
};
uniform Light light;

uniform vec3 u_ViewPos;

void main()
{
    vec3 diffuseColor = vec3(texture(material.diffuse, v2_TexCoord));

    vec3 ambient = light.ambient * diffuseColor;

    vec3 N = normalize(v3_Normal);
    vec3 lightDir = normalize(light.position - v3_WorldPos);
    float diff = max(dot(N, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffuseColor);

    vec3 specularColor = vec3(texture(material.specular, v2_TexCoord));
    vec3 viewDir = normalize(u_ViewPos - v3_WorldPos);
    vec3 reflectDir = reflect(-lightDir, N);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * specularColor);

    float distanceFromLightToFrag = length(light.position - v3_WorldPos);
    float attenuation = 1.0 / (light.constant + light.linear * distanceFromLightToFrag +
        light.quadratic * (distanceFromLightToFrag * distanceFromLightToFrag));

    vec3 result = (ambient + diffuse + specular) * attenuation;
    fragColor = vec4(result, 1.0);
}

 

pointlight

 

聚光灯
// LightDir:从片段指向光源的向量。
// SpotDir:聚光所指向的方向。
// Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
// Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。
// 软化边缘的聚光灯: I= (theta − out) / e;  e是内锥和外锥的余弦值差
class SpotLight : public LightTexture::Basic
{
    bool softness = true;
public:
    virtual void Setup() override
    {
        if (softness)
            m_CubeShader = "SandBox/12_LightCasters/SpotLightSoftness.shader";
        else
            m_CubeShader = "SandBox/12_LightCasters/SpotLight.shader";
        Basic::Setup();
        m_Texture[1] = new Texture("Resources/Textures/woodbox_specular.png");

        m_Light.position = glm::vec3(1.2f, 1.0f, 2.0f);
        m_Light.ambient = glm::vec3(0.2f, 0.2f, 0.2f);
        m_Light.diffuse = glm::vec3(0.5f, 0.5f, 0.5f);
        m_Light.specular = glm::vec3(1.0f, 1.0f, 1.0f);
    }

    virtual void Render() override
    {
        m_Texture[0]->Bind(0);
        m_Texture[1]->Bind(1);
        for (int i = 0; i < 10; i++)
        {
            m_Shader[0]->Bind();

            glm::mat4 model = glm::identity<glm::mat4>();
            model = glm::translate(model, cubePositions[i]);
            float angle = i * 20.0f;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            m_Shader[0]->SetMat4f("u_Model", model);

            m_Shader[0]->SetMat4f("u_Projection", m_Camera->GetProjectionMatrix());
            m_Shader[0]->SetMat4f("u_View", m_Camera->GetViewMatrix());
            m_Shader[0]->SetVec3("u_ViewPos", m_Camera->GetEye());

            m_Shader[0]->SetVec3("light.position", m_Camera->GetEye()); // 由于我们模拟的是手电筒 所以灯光的信息要填摄像机的信息
            m_Shader[0]->SetVec3("light.direction", m_Camera->Front);
            m_Shader[0]->SetFloat("light.cutOff", glm::cos(glm::radians(12.5f))); // 边界线 dot 半径 角度为25度
            if (softness)
                m_Shader[0]->SetFloat("light.outterCutOff", glm::cos(glm::radians(15.5f))); // 外圆锥
            m_Shader[0]->SetVec3("light.ambient", m_Light.ambient);
            m_Shader[0]->SetVec3("light.diffuse", m_Light.diffuse);
            m_Shader[0]->SetVec3("light.specular", m_Light.specular);

            m_Shader[0]->SetInt("material.diffuse", 0);
            m_Shader[0]->SetInt("material.specular", 1);
            m_Shader[0]->SetFloat("material.shininess", m_Material.shininess);

            this->m_Renderer.Draw(m_VA[0], 36, m_Shader[0]);
        }
    }

    virtual void OnGUI() override
    {
        ImGui::Begin("SpotLightTest");
        {
            if (ImGui::Checkbox("Softness", &softness))
            {
                if (softness)
                    m_CubeShader = "SandBox/12_LightCasters/SpotLightSoftness.shader";
                else
                    m_CubeShader = "SandBox/12_LightCasters/SpotLight.shader";
                m_Shader[0] = new Shader(m_CubeShader);
            }
        }
        ImGui::End();
    }
};

// 硬边缘

// ...略
struct Light
{
    vec3 position;
    vec3 direction;
    float cutOff; // 边界

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

uniform vec3 u_ViewPos;

void main()
{
    vec3 lightDir = normalize(light.position - v3_WorldPos);
    // 计算是否在聚光灯内部
    float theta = dot(lightDir, normalize(-light.direction));
    if (theta > light.cutOff)  // 当theta叫比fi角小的时候就说明在聚光灯内部,但是这里的theta和fi是余弦值,在[0, 90]范围内的角度和余弦值呈反比
    {
        vec3 diffuseColor = vec3(texture(material.diffuse, v2_TexCoord));

        vec3 ambient = light.ambient * diffuseColor;

        vec3 N = normalize(v3_Normal);
        float diff = max(dot(N, lightDir), 0.0);
        vec3 diffuse = light.diffuse * (diff * diffuseColor);

        vec3 specularColor = vec3(texture(material.specular, v2_TexCoord));
        vec3 viewDir = normalize(u_ViewPos - v3_WorldPos);
        vec3 reflectDir = reflect(-lightDir, N);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
        vec3 specular = light.specular * (spec * specularColor);

        // 在聚光灯内部
        vec3 result = ambient + diffuse + specular;
        fragColor = vec4(result, 1.0);
    }
    else
    {
        fragColor = vec4(light.ambient * vec3(texture(material.diffuse, v2_TexCoord)), 1.0); // 使用环境光,让场景在聚光之外时不至于完全黑暗
    }

}

// 软边缘

struct Light
{
    vec3 position;
    vec3 direction;
    float cutOff; // 内边界
    float outterCutOff; // 外边界

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

uniform vec3 u_ViewPos;

void main()
{
    vec3 lightDir = normalize(light.position - v3_WorldPos);

    float theta = dot(lightDir, normalize(-light.direction));
    float epsilon = light.cutOff - light.outterCutOff;
    float intensity = clamp((theta - light.outterCutOff) / epsilon, 0.0, 1.0);

    vec3 diffuseColor = vec3(texture(material.diffuse, v2_TexCoord));

    vec3 ambient = light.ambient * diffuseColor;

    vec3 N = normalize(v3_Normal);
    float diff = max(dot(N, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffuseColor);

    vec3 specularColor = vec3(texture(material.specular, v2_TexCoord));
    vec3 viewDir = normalize(u_ViewPos - v3_WorldPos);
    vec3 reflectDir = reflect(-lightDir, N);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * specularColor);

    // 不对环境光做操作,要保持物体不是全黑的
    vec3 result = ambient + intensity * (diffuse + specular);
    fragColor = vec4(result, 1.0);
}

SpotLight

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像