Summed-Area Variance Soft Shadow Mapping(SAVSM):一

随着游戏质量提升,传统硬阴影逐渐的不再被人们接受,各种软阴影算法渐渐的出现在我们的视野当中,最为流行的几种软阴影方法为:Variance Shadow Map(VSM),Convolution Shadow Maps(CSM),Exponential Shadow Maps(ESM),Moments Shadow Map(MSM)以及Percentage Closer Soft Shadows(PCSS)。

VSM,CSM,ESM都可以和PCSS结合使用,因此我们首先学习PCSS。

PCSS

PCSS是具有动态采样半径的PCF方法。其根据光源半径,以及光源,遮挡物与接收面的位置来决定PCF的采样半径。该方法分为三个步骤:

1. 计算遮挡物的平均深度。

2. 计算半影区域。

3. 执行PCF。

1. 计算遮挡物的平均深度

我们可以人为定义一个遮挡物搜索半径,将片段位置转化为光空间,采样搜索半径内的shadow map,累加所有深度小于当前深度的值,对其求平均即得到我们的平均遮挡物深度。下图假设了当前深度为7所找到的遮挡物深度(蓝色标记)。

然而我们可以使用一个更好的方法计算遮挡物的搜索半径。我们可以根据三角形相似的原理,来获得灯光覆盖的像素区域,如下图所示:

片段距离光源越远,则搜索半径越大,我们计算遮挡物半径的代码如下:

#define SHADOW_HEIGHT_WORLD (2.0f * tan_fov * shadow_near)

float LinearizeDepth(float depth){
    float z=depth * 2.0f - 1.0f;
    float denominator = (shadow_far + shadow_near - (shadow_far - shadow_near) * z);
    return (2.0f * shadow_near * shadow_far) / denominator;
}

float calBlockSearchRange(vec3 ndc_texCoord){
    float LS_depth = LinearizeDepth(ndc_texCoord.z);
    // world space range
    float light_range = LightSize / LS_depth * (LS_depth - shadow_near);
    // pixel space range
    float range_pixel = light_range / SHADOW_HEIGHT_WORLD;
    return range_pixel;
}

计算平均遮挡深度只需要对遮挡物搜索半径内的像素进行对比,并累加平均深度小于当前深度值即可。但是,如果计算得出的搜索半径很大,则会严重降低运行效率,因此我们使用柏松圆盘采样,使每次采样个数是一个固定值。代码如下:

float calBlockDepth(float pixel_range, vec3 ndc_texCoord){
    float ave_depth = 0.0f;
    int block_num = 0;
    float current_depth = ndc_texCoord.z;
    for (int i = 0; i < SAMPLE_NUMB; i++) {
        float t_depth = texture(depth_tex, ndc_texCoord.xy + pixel_range * PoissonDisk[i]).r;
        if(t_depth < current_depth - shadow_bias){
            ave_depth += LinearizeDepth(t_depth);
            block_num++;
        }
    }
    if(block_num < 1)
        return -1.0;
    else
        return ave_depth / float(block_num);
}

2. 计算半影区域

我们假定光源总是平行于渲染的片段,根据三角形相似的原理,计算半影区,原理如下图:

值得注意的是,遮挡物搜索中采用的相似为光源的半径,而这里的相似采用光源的直径。我们还需要保证深度为线性,若shadow map为正交投影得出,则深度就是线性的。若使用透视投影得出,我们需要手动将其转为线性深度。计算半影区代码如下:

float calPenumbra(float depth,float block_dep){
    float linear_depth = LinearizeDepth(depth);
    float w_penumbra_world = (linear_depth - block_dep) * 2.0 * LightSize / block_dep;
    float w_penumbra_pixel = w_penumbra_world * shadow_near / (SHADOW_HEIGHT_WORLD * linear_depth);
    return w_penumbra_pixel;
}

3. 执行PCF

计算得到像素空间的半影区将作为PCF的采样半径,我们的代码如下:

float calPCF(float w_penumbra, vec3 ndc_texCoord){
    float t_shadow = 0.0f;
    float current_depth = ndc_texCoord.z;
    for (int i = 0; i < SAMPLE_NUMB; i++) {
        float sample_depth = texture(depth_tex, ndc_texCoord.xy + w_penumbra * PoissonDisk[i]).r;
        if(current_depth > 1.0f || current_depth - shadow_bias < sample_depth)
            t_shadow += 0.0f;
        else
            t_shadow += 1.0f;
    }
    return t_shadow / float(SAMPLE_NUMB);
}

同样使用柏松圆盘采样法进行采样。这里我们的柏松圆盘通过预计算的方式,硬编为采样数组。

柏松圆盘采样

柏松圆盘采样点的特点是任意一个采样点,距离其周围最近的采样点的距离始终在(Dmin,Dmax)之内。

其算法步骤如下:

1. 在n维空间中生成均匀网格(本文为2维),网格大小为r / \sqrt{n},r为最小距离Dmin。保证每个网格内最多存在一个采样点。

2. 创建两个队列,一个为操作队列,用来保存待操作的点。另一个为结果队列,保存所有满足条件的点。在空间内随机生成一个种子点,并加入操作队列与结果队列。

3. 从操作队列内pop一个点,如果队列为空,则算法结束。否则在取到点周围的(Dmin,Dmax)圆环半径内随机生成一个检测点b。

4. 判断检测点b的Dmin范围内是否存在其他点,如果不存在,则添加点b进操作队列和结果队列。如果存在其他点,则重新生成检测点b。如果重新生成超过k次,仍然在其周围存在其他点,我们则重新执行步骤3.。

生成点和领居判定如下图所示。

我们柏松圆盘采样代码如下:

void PCSS::calPoission(){
    
    //init grid
    int grid_num = cell_num * cell_num;
    grid.resize(grid_num, glm::vec2(out_of_size));
    
    // push first point
    glm::vec2 firstPoint = glm::vec2(drand48(), drand48());
    processList.push(firstPoint);
    poissonDisk.push_back(firstPoint);
    
    glm::ivec2 grid_index = imageToGrid(firstPoint);
    grid[grid_index.y * cell_num + grid_index.x] = firstPoint;
    
    while (!processList.empty()) {
        glm::vec2 process_point = processList.front();
        processList.pop();
        for (int i = 0; i < pois_k; i++) {
            glm::vec2 new_point = calAround(process_point);
            
            //check that point is in the image region and have no neighbor
            if(calCheckPoint(new_point)){
                processList.push(new_point);
                poissonDisk.push_back(new_point);
                glm::ivec2 new_grid_id = imageToGrid(new_point);
                grid[new_grid_id.y * cell_num + new_grid_id.x] = new_point;
            }
        }
    }
    
}

根据需要我们用柏松圆盘计算得到固定的采样点硬编至PCSS的片段着色器内:

const vec2 PoissonDisk[SAMPLE_NUMB] = vec2[](
                                             vec2(-0.20707,0.680971),
                                             vec2(-0.568494,0.807044),
                                             vec2(0.0749105,0.436834),
                                             vec2(-0.455151,0.536164),
                                             vec2(0.238882,0.853848),
                                             vec2(-0.224585,0.315561),
                                             vec2(-0.49309,0.212044),
                                             vec2(-0.806367,0.336245),
                                             vec2(-0.96812,0.75486),
                                             vec2(0.373125,0.2635),
                                             vec2(-0.102639,-0.0619002),
                                             vec2(0.524274,0.574806),
                                             vec2(0.634103,0.967932),
                                             vec2(-0.385516,-0.314919),
                                             vec2(-0.714502,-0.160928),
                                             vec2(0.609902,-0.201941),
                                             vec2(0.21492,-0.15106),
                                             vec2(0.699132,0.190124),
                                             vec2(0.846728,0.451957),
                                             vec2(-0.0187435,-0.360953),
                                             vec2(0.923692,0.907769),
                                             vec2(-0.592013,-0.673695),
                                             vec2(0.0229522,-0.68713),
                                             vec2(-0.896051,-0.381052),
                                             vec2(-0.244224,-0.579614),
                                             vec2(-0.898701,-0.665722),
                                             vec2(0.475186,-0.734744),
                                             vec2(0.389989,-0.382208),
                                             vec2(0.946732,0.0353365),
                                             vec2(0.965636,-0.426523),
                                             vec2(-0.768483,-0.933271),
                                             vec2(-0.135119,-0.96675),
                                             vec2(0.156406,-0.959331),
                                             vec2(-0.480685,-0.946449),
                                             vec2(0.647589,-0.507713),
                                             vec2(0.975397,-0.769224),
                                             vec2(0.667647,-0.971078)
);

最后我们得到的PCSS结果如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值