联级阴影贴图CSM(Cascaded shadow map)原理与实现

联级阴影贴图CSM(Cascaded shadow map)原理与实现

 

CSM是利用分层的ShadowMap技术,实现大场景的阴影算法。示意图如下图:

我们通过给眼视锥分片,为每个分片生成一个相同分辨率的光空间深度图。利用眼睛看到的片段,根据其所在分片位置,转换为相应光空间深度,和光空间深度图比对,若深度大于深度图内的深度,则处于阴影。

该算法分为4个步骤:

  1. 将眼视锥分成多个深度分片。
  2. 将光的视锥体分成多个较小的视锥体,每个视锥体(也可以称为包围盒)都覆盖一个分片。
  3. 为每个光视锥体渲染一个阴影贴图。
  4. 渲染整个场景的阴影。

根据以上步骤,我们首先构建类:

#define CSM_MAX_SPLITS 4

class ShadowMap{
public:
    ShadowMap();
    ~ShadowMap();
    void init(Camera* camera);
    //生成CSM所需的资源(分片光空间变换矩阵,眼空间到光空间变换矩阵)
    void pre_depth_write(Camera* camera, const glm::vec3& lightdir);
    
    glm::mat4 crop_matrix(int t_split_index);
    glm::mat4 projection_matrix(int t_split_index);
    glm::mat4 modelview_matrix();
    
    int num_splits() const;
    int depth_tex_size() const;
    
    GLuint fbo() const;
    GLuint texture() const;
    
    glm::vec4 far_bounds();
    float* texture_matrices();
   
private:
    void create_fbo();
    void create_texture();
    
    void update_split_distances(Camera* camera);
    //计算世界空间中的相机视锥切片边界点
    void update_split_frustum_points(Camera* camera);
    
    //生成光视锥切片的光空间变换矩阵
    void generate_crop_matrices(const glm::mat4& t_modelview);
    
    //更新每个分片far边界
    void update_far_bounds(const glm::mat4& projection);
    void update_texture_matrices(const glm::mat4& projection, const glm::mat4& view_inverse);
    
    GLuint m_fbo;//CSM的fbo
    GLuint m_texture_array;//texture数组
    int m_num_splits;//视锥分片数
    int m_depth_tex_size;//深度图尺寸
    float m_split_weight;//划分权重(lambda)
    float m_far_bounds[CSM_MAX_SPLITS];//相机空间的深度切面
    
    Frustum m_frustums[CSM_MAX_SPLITS];
    glm::mat4 m_bias;
    glm::mat4 m_modelview;//光空间view矩阵
    glm::mat4 m_crop_matrices[CSM_MAX_SPLITS];//分片光空间变换矩阵(投影*观察)
    glm::mat4 m_projection_matrices[CSM_MAX_SPLITS];//光空间投影矩阵
    glm::mat4 m_texture_matrices[CSM_MAX_SPLITS];//眼空间转换为光空间矩阵
};

初始化函数,非配摄像机要素,以及为眼视锥分片:

void ShadowMap::init(Camera* camera) {
    float camera_fov = camera->Zoom;
    float width = camera->Width;
    float height = camera->Height;
    float ratio = width / height;

    //略微添加偏移(这里为0.2),避免在边框处有伪像
    for(int i = 0 ; i < m_num_splits ; i++) {
        m_frustums[i].fov(camera_fov + 0.2f);
        m_frustums[i].ratio(ratio);
    }

    //光空间的深度需要m_bias将[-1,1]变换为[0,1],从而在texture中使用。
    m_bias = glm::mat4(
        0.5f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.5f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.5f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f
    );

    //计算摄像机视线空间中每个平截头体切片的近距离和远距离
    update_split_distances(camera);

    create_fbo();
    create_texture();
}

 

眼视锥分片

“Parallel-Split Shadow Maps for Large-scale Virtual Environments”这篇文章给出了一个非常好的眼视锥分片方法,称为PSSM,即平行分片阴影贴图。为了理解其公式由来,我们看下图:

我们的关键问题是如何指定分割位置,即计算Ci。在上图中,从表面(橙色线段)投射到屏幕上的观察光束dp的大小约为ndy/z。φ和θ分别表示曲面法线与屏幕和阴影贴图平面之间的角度。由于dy =dzcosφ/cosθ,因此阴影图锯齿误差dp/ds(阴影贴图的尺寸为ds×ds)可以表示为:

当dp大于屏幕的像素大小时,会出现阴影贴图欠采样,这可能在透视误差dz/zds或投影误差cosφ/cosθ变大时发生。文章中提出了三种分割方案,即计算Ci的方案。

 

对数拆分方案

理想情况下,透视误差的最佳分布可使dp/ds在整个深度范围内保持恒定。即dz/zds =ρ(忽略投影误差),其中ρ为常数。有:

在这里,有一个重要的注意事项:阴影贴图s∈[0,1]的局部参数化意味着一个假设“阴影贴图准确地覆盖了视锥,并且在场景的不可见部分上没有浪费任何分

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值