Skip to content

File ShadowSystem.cpp

File List > render > ShadowSystem.cpp

Go to the documentation of this file

#include "ShadowSystem.h"
#include "xdz_math.h"
#include "Input.h"
#include "shaders/ComputeShader.h"

ShadowSystem::ShadowSystem()
    :cache_w(SCREEN_W), cache_h(SCREEN_H)
{}

ShadowSystem::~ShadowSystem()
{}

void ShadowSystem::Init()
{
    point_buffer = StorageBuffer(CUSTOM_LIST, 0);
    sun_buffer = StorageBuffer(CUSTOM_LIST, 1);
    spot_buffer = StorageBuffer(CUSTOM_LIST, 2);
    area_buffer = StorageBuffer(CUSTOM_LIST, 3);
    poly_buffer = StorageBuffer(CUSTOM_LIST, 4);
    poly_verts_buffer = StorageBuffer(CUSTOM_LIST, 5);
    info = UniformBuffer(6);
}

void ShadowSystem::Bind() const
{
    point_buffer.BindBufferBase();
    sun_buffer.BindBufferBase();
    spot_buffer.BindBufferBase();
    area_buffer.BindBufferBase();
    poly_buffer.BindBufferBase();
    poly_verts_buffer.BindBufferBase();
    info.Bind(0);
    BindShadowMap();
}

ShadowSystem::PointStruct::PointStruct(const Light& light)
    :color(light.light_color),
    pos(light.o_position),

    power(light.light_power),
    use_shadow((int)light.use_shadow),
    radius(light.light_radius)
{
    assert(light.light_type == POINTLIGHT);
}

ShadowSystem::SunStruct::SunStruct(const Light& light, const glm::mat4& proj)
    :color(light.light_color),
    pos(light.o_position),
    dir(glm::cross(light.o_dir_up, light.o_dir_right)),

    power(light.light_power),
    use_shadow((int)light.use_shadow),
    proj_trans(proj)
{
    assert(light.light_type == SUNLIGHT);
}

ShadowSystem::SpotStruct::SpotStruct(const Light& light)
    :color(light.light_color),
    pos(light.o_position),
    dir(-glm::cross(light.o_dir_up, light.o_dir_right)),

    power(light.light_power),
    use_shadow((int)light.use_shadow),
    cutoff(light.spot_cutoff),
    outer_cutoff(light.spot_outer_cutoff)
{
    assert(light.light_type == SPOTLIGHT);
}

ShadowSystem::AreaStruct::AreaStruct(const Light& light)
    :color(light.light_color),
    trans(light.o_Transform),

    power(light.light_power),
    use_shadow((int)light.use_shadow),
    ratio(light.area_ratio)
{
    assert(light.light_type == AREALIGHT);
}

void ShadowSystem::ParseLightData(const std::unordered_map<int, std::shared_ptr<Light>>& light_list, bool using_moment_shadow)
{
    point_list.clear();
    sun_list.clear();
    spot_list.clear();
    area_list.clear();
    light_info_cache.clear();

    for (auto& [id, light] : light_list)
    {
        UpdateProjMatrix(light.get());
        InitShadowMap(light.get(), using_moment_shadow);

        shadow_cache[id] = Texture((int)cache_w, (int)cache_h, Texture::LIGHTING_CACHE);

        switch (light->light_type)
        {
        case NONELIGHT:
            break;
        case POINTLIGHT:
            light_info_cache[id] = _LightInfo(point_list.size(), light.get());
            point_list.emplace_back(*light.get());
            break;
        case SUNLIGHT:
            light_info_cache[id] = _LightInfo(sun_list.size(), light.get());
            sun_list.emplace_back(*light.get(), proj_matrices[id]);
            break;
        case SPOTLIGHT:
            light_info_cache[id] = _LightInfo(spot_list.size(), light.get());
            spot_list.emplace_back(*light.get());
            break;
        case AREALIGHT:
            light_info_cache[id] = _LightInfo(area_list.size(), light.get());
            area_list.emplace_back(*light.get());
            break;
        default:
            assert(false && "Unknown Light Type");
            break;
        }
    }

    point_buffer.GenStorageBuffer(point_list);
    sun_buffer.GenStorageBuffer(sun_list);
    spot_buffer.GenStorageBuffer(spot_list);
    area_buffer.GenStorageBuffer(area_list);
    info.Update(GetSceneInfo());
}

void ShadowSystem::ParsePolygonLightData(const std::unordered_map<int, std::shared_ptr<PolygonLight>>& poly_light_list)
{
    poly_list.clear();
    poly_verts.clear();

    for (auto& al : poly_light_list)
    {
        auto& v = al.second->verts;

        poly_list.emplace_back(PolyStruct{
            al.second->light_color,

            al.second->light_power,
            (int)al.second->use_shadow,
            (int)v.size() / 2
            });

        al.second->ApplyTransform();
        for (size_t i = 0; i < v.size(); i += 2)
        {
            poly_verts.emplace_back(PolyVertStruct{
                glm::vec3(al.second->o_Transform * glm::vec4(v[i], v[i + 1], 0.0f, 1.0f))
                });
        }
    }

    poly_buffer.GenStorageBuffer(poly_list);
    poly_verts_buffer.GenStorageBuffer(poly_verts);
    info.Update(GetSceneInfo());
}

ShadowSystem::SceneInfo ShadowSystem::GetSceneInfo() const
{
    SceneInfo info{};

    info.point_count = point_list.size();
    info.sun_count = sun_list.size();
    info.spot_count = spot_list.size();
    info.area_count = area_list.size();
    info.poly_count = poly_list.size();
    info.poly_verts_count = poly_verts.size();

    return info;
}

GLsizei ShadowSystem::GetTotalCount() const
{
    return point_list.size() + sun_list.size() + spot_list.size() + area_list.size()/* + area_verts.size()*/;
}

GLuint ShadowSystem::GetSlotOffset(LightType _type) const
{
    switch (_type)
    {
    case POINTLIGHT:
        return 0;
    case SUNLIGHT:
        return GetSlotOffset(POINTLIGHT) + point_list.size();
    case SPOTLIGHT:
        return GetSlotOffset(SUNLIGHT)   + sun_list.size();
    case AREALIGHT:
        return GetSlotOffset(SPOTLIGHT)  + spot_list.size();
    default:
        assert(false && "Unknown Light Type");
        return 0u;
    }
}

void ShadowSystem::Resize(GLuint _w, GLuint _h)
{
    cache_w = _w;
    cache_h = _h;

    for (auto& [_, cache] : shadow_cache)
        cache.Resize(cache_w, cache_h);
}

void ShadowSystem::UpdateLight(Light* light)
{
    if (light_info_cache.find(light->GetObjectID()) == light_info_cache.end()) {
        return;
    }

    int id = light->GetObjectID();
    int loc = std::get<0>(light_info_cache[id]);
    UpdateProjMatrix(light);

    switch (light->light_type)
    {
    case POINTLIGHT:
        point_list[loc] = *light;
        point_buffer.GenStorageBuffer(point_list);
        break;
    case SUNLIGHT:
        sun_list[loc] = SunStruct(*light, proj_matrices[id]);
        sun_buffer.GenStorageBuffer(sun_list);
        break;
    case SPOTLIGHT:
        spot_list[loc] = *light;
        spot_buffer.GenStorageBuffer(spot_list);
        break;
    case AREALIGHT:
        area_list[loc] = *light;
        area_buffer.GenStorageBuffer(area_list);
        break;
    default:
        assert(false && "Unknown Light Type");
        break;
    }
}

void ShadowSystem::Update(int frame, RenderConfigs* config)
{
    if (!config->RequiresShadow()) {
        for (const auto& [id, _] : light_info_cache) {
            shadow_cache[id].FillColor({1,1,1,1});
        }
        return;
    }

    bool using_moment_shadow = config->RequiresMomentShadow();

    const Texture::TextureType flat_map = using_moment_shadow ? Texture::HDR_TEXTURE : Texture::DEPTH_TEXTURE;
    const Texture::TextureType cube_map = using_moment_shadow ? Texture::HDR_CUBE_TEXTURE : Texture::DEPTH_CUBE_TEXTURE;


    const bool is_incr_aver = config->r_sampling_average == RenderConfigs::SamplingType::IncrementAverage;

    const float point_ud_rate   = is_incr_aver ? 0.05f : 1.0f / frame;
    const float sun_ud_rate     = is_incr_aver ? 0.05f : 1.0f / frame;
    const float spot_ud_rate    = is_incr_aver ? 0.05f : 1.0f / frame;
    const float area_ud_rate    = is_incr_aver ? 0.01f : 1.0f / frame;

    const Input::RandomState random_state = Input::GetRandomState();
    const glm::vec3 random = glm::vec3(random_state.random_float1, random_state.random_float2, random_state.random_float3);

    for (const auto& [id, info] : light_info_cache) {
        const auto& [loc, light] = info;
        Texture& shadow_map = shadow_maps[id];
        const glm::mat4& proj = proj_matrices[id];
        const GLuint map_id = shadow_map.GetTexID();
        const LightType type = light->light_type;

        ComputeShader& shadow_shader = ComputeShader::ImportShader(ComputeShader::GetShadowShaderName(char(config->r_shadow_algorithm), type));

        shadow_shader.UseShader();
        shadow_shader.SetValue("offset", xdzm::map01_11(random));
        shadow_shader.SetValue("frame", frame);
        shadow_shader.SetValue("map_size", glm::vec2(shadow_map.GetW(), shadow_map.GetH()));

        shadow_cache[id].BindC(4);
        switch (type)
        {
        case POINTLIGHT:

            Texture::BindM(map_id, 31, cube_map);

            shadow_shader.SetValue("light_pos", light->o_position);
            shadow_shader.SetValue("light_far", Light::point_shaodow_far);
            shadow_shader.SetValue("update_rate", point_ud_rate);
            shadow_shader.SetValue("radius", light->light_radius);
            //shadow_shader.SetValue("offset", Light::point_blur_range);

            break;
        case SUNLIGHT:

            Texture::BindM(map_id, 31, flat_map);

            shadow_shader.SetValue("proj_trans", proj);
            shadow_shader.SetValue("dir", sun_list[loc].dir);
            shadow_shader.SetValue("radius", Light::point_blur_range);
            shadow_shader.SetValue("light_size", Light::sun_shaodow_field);
            shadow_shader.SetValue("update_rate", sun_ud_rate);

            break;
        case SPOTLIGHT:

            Texture::BindM(map_id, 31, cube_map);

            shadow_shader.SetValue("light_pos", light->o_position);
            shadow_shader.SetValue("light_dir", spot_list[loc].dir);
            shadow_shader.SetValue("outer_cutoff", light->spot_outer_cutoff);
            shadow_shader.SetValue("light_far", Light::spot_shaodow_far);
            shadow_shader.SetValue("radius", Light::spot_blur_range);
            shadow_shader.SetValue("update_rate", spot_ud_rate);

            break;
        case AREALIGHT:

            Texture::BindM(map_id, 31, cube_map);

            shadow_shader.SetValue("light_trans", light->o_Transform);
            shadow_shader.SetValue("U_UV", glm::vec2(random_state.random_float1, random_state.random_float2));
            shadow_shader.SetValue("ratio", light->area_ratio);
            shadow_shader.SetValue("light_far", Light::area_shaodow_far);
            shadow_shader.SetValue("radius", Light::area_blur_range);
            shadow_shader.SetValue("update_rate", area_ud_rate);

            break;
        default:
            assert(false && "Unknown Light Type");
            break;
        }

        shadow_shader.RunComputeShader(cache_w / 16, cache_h / 16);
    }
}

void ShadowSystem::BindShadowMap() const
{
    for (auto& [id, info] : light_info_cache) {
        const auto& [loc, light] = info;
        const LightType type = light->light_type;
        const GLuint slot = 31 - (GetSlotOffset(type) + loc);
        shadow_cache[id].Bind(slot);
    }
}

void ShadowSystem::InitShadowMap(Light* light, bool using_moment_shadow)
{
    assert(light->light_type != LightType::NONELIGHT);

    const int id = light->GetObjectID();
    const Texture::TextureType flat_map = using_moment_shadow ? Texture::HDR_TEXTURE : Texture::DEPTH_TEXTURE;
    const Texture::TextureType cube_map = using_moment_shadow ? Texture::HDR_CUBE_TEXTURE : Texture::DEPTH_CUBE_TEXTURE;

    switch (light->light_type)
    {
    case SUNLIGHT:
        shadow_maps[id] = Texture(1024, 1024, flat_map);
        break;
    case POINTLIGHT:
        shadow_maps[id] = Texture(1024, 1024, cube_map);
        break;
    case SPOTLIGHT:
        // TODO
        break;
    case AREALIGHT:
        shadow_maps[id] = Texture(1024, 1024, cube_map);
        break;
    default:
        assert(false && "Unknown Light Type");
        break;
    }
}

void ShadowSystem::UpdateProjMatrix(Light* light)
{
    const int id = light->GetObjectID();

    switch (light->light_type)
    {
    case POINTLIGHT:
        proj_matrices[id] = glm::perspective(
            glm::radians(90.0f),
            1.0f,
            Light::point_shaodow_near,
            Light::point_shaodow_far
        );
        break;
    case SUNLIGHT:
    {
        const glm::mat4 lightProjection = glm::ortho(
            -Light::sun_shaodow_field,
            Light::sun_shaodow_field,
            -Light::sun_shaodow_field,
            Light::sun_shaodow_field,
            Light::sun_shaodow_near,
            Light::sun_shaodow_far
        );
        const glm::mat4 lightView = glm::lookAt(glm::vec3(0), glm::cross(light->o_dir_up, light->o_dir_right), glm::vec3(0, 0, 1));
        proj_matrices[id] = lightProjection * lightView;
        break;
    }
    case SPOTLIGHT:
        proj_matrices[id] = glm::perspective(
            glm::radians(90.0f),
            1.0f,
            Light::spot_shaodow_near,
            Light::spot_shaodow_far
        );
        break;
    case AREALIGHT:
        proj_matrices[id] = glm::perspective(
            glm::radians(90.0f),
            1.0f,
            Light::spot_shaodow_near,
            Light::spot_shaodow_far
        );
        break;
    default:
        assert(false && "Unknown Light Type");
        break;
    }
}

void ShadowSystem::ConstructSAT(Light* light, const RenderConfigs* config)
{
    if (!config->RequiresMomentShadow())
        return;

    const int id = light->GetObjectID();
    Texture& shadow_map = shadow_maps[id];

    auto [_1, _2, _3, gl_type] = Texture::ParseFormat(shadow_map.tex_type);
    const int pass_count = config->r_shadow_algorithm == RenderConfigs::ShadowAlg::VSSM ? 2 : 4;

    if (gl_type == GL_TEXTURE_2D) {
        ComputeShader& SAT = ComputeShader::ImportShader("convert/SAT");

        static Texture light_shadow_temp = Texture(shadow_map.GetW(), shadow_map.GetH(), Texture::HDR_TEXTURE);

        shadow_map.BindC(0, GL_READ_ONLY);
        light_shadow_temp.BindC(1, GL_WRITE_ONLY);
        SAT.RunComputeShader({ shadow_map.GetW(), 1 });

        light_shadow_temp.BindC(0, GL_READ_ONLY);
        shadow_map.BindC(1, GL_WRITE_ONLY);
        SAT.RunComputeShader({ shadow_map.GetH(), 1 });
    }
    else if (gl_type == GL_TEXTURE_CUBE_MAP) {
        // ComputeShader& SAT_cube = ComputeShader::ImportShader("convert/SAT_Cube");
        // Skip for now, not necessary to use SAT filtering
    }
}

std::array<FrameBuffer, 4> ShadowSystem::_shadowmap_buffer = {};

std::array<ChainedShader, 4> ShadowSystem::_shadowmap_shader = {};

std::array<glm::mat4, 6> ShadowSystem::_point_6side = {
                glm::lookAt(glm::vec3(0), glm::vec3(1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)),
                glm::lookAt(glm::vec3(0), glm::vec3(-1.0,0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)),
                glm::lookAt(glm::vec3(0), glm::vec3(0.0, 1.0, 0.0), glm::vec3(0.0, 0.0, 1.0)),
                glm::lookAt(glm::vec3(0), glm::vec3(0.0,-1.0, 0.0), glm::vec3(0.0, 0.0,-1.0)),
                glm::lookAt(glm::vec3(0), glm::vec3(0.0, 0.0, 1.0), glm::vec3(0.0,-1.0, 0.0)),
                glm::lookAt(glm::vec3(0), glm::vec3(0.0, 0.0,-1.0), glm::vec3(0.0,-1.0, 0.0))};

void ShadowSystem::EnableShadowMap()
{
    _shadowmap_buffer[0] = FrameBuffer(Texture(1024, 1024, Texture::DEPTH_CUBE_TEXTURE));
    _shadowmap_buffer[1] = FrameBuffer(Texture(1024, 1024, Texture::DEPTH_TEXTURE));

    _shadowmap_shader[SUNLIGHT]   = ChainedShader::ImportShader("Depth_Rast.vert", "Empty.frag");
    _shadowmap_shader[POINTLIGHT] = ChainedShader::ImportShader("Empty.vert", "6sides_trans.geom", "Depth_Linear.frag");
    _shadowmap_shader[SPOTLIGHT]  = ChainedShader::ImportShader("Empty.vert", "6sides_trans.geom", "Depth_Linear.frag");
    _shadowmap_shader[AREALIGHT]  = ChainedShader::ImportShader("Empty_Rand.vert", "6sides_trans.geom", "Depth_Linear.frag");
}

void ShadowSystem::BindShadowMapBuffer(Light* light, Texture& shadow_map)
{
    _shadowmap_buffer[light->light_type].LinkTexture(shadow_map);
    _shadowmap_buffer[light->light_type].BindFrameBuffer();
}

void ShadowSystem::BindShadowMapShader(Light* light, const glm::mat4& proj)
{
    const LightType type = light->light_type;
    _shadowmap_shader[type].UseShader();
    const Input::RandomState random = Input::GetRandomState();
    switch (type)
    {
    case POINTLIGHT:
        _shadowmap_shader[type].SetValue("shadowMatrices", 6, _point_6side.data());
        _shadowmap_shader[type].SetValue("U_offset", light->o_position);
        _shadowmap_shader[type].SetValue("U_lightproj", proj);
        _shadowmap_shader[type].SetValue("far_plane", Light::point_shaodow_far);
        break;
    case SUNLIGHT:
        _shadowmap_shader[type].SetValue("U_lightproj", proj);
        break;
    case SPOTLIGHT:
        _shadowmap_shader[type].SetValue("shadowMatrices", 6, _point_6side.data());
        _shadowmap_shader[type].SetValue("U_offset", light->o_position);
        _shadowmap_shader[type].SetValue("U_lightproj", proj);
        _shadowmap_shader[type].SetValue("far_plane", Light::spot_shaodow_far);
        break;
    case AREALIGHT:
        _shadowmap_shader[type].SetValue("shadowMatrices", 6, _point_6side.data());
        _shadowmap_shader[type].SetValue("U_trans", light->o_Transform);
        _shadowmap_shader[type].SetValue("U_UV", glm::vec2(random.random_float1, random.random_float2));
        _shadowmap_shader[type].SetValue("ratio", light->area_ratio);
        _shadowmap_shader[type].SetValue("U_lightproj", proj);
        _shadowmap_shader[type].SetValue("far_plane", Light::area_shaodow_far);
        break;
    default:
        assert(false && "Unknown Light Type");
        break;
    }
}

void ShadowSystem::BindTargetTrans(Light* light, const glm::mat4& _trans)
{
    _shadowmap_shader[light->light_type].SetValue("U_model", _trans);
}