随着游戏质量提升,传统硬阴影逐渐的不再被人们接受,各种软阴影算法渐渐的出现在我们的视野当中,最为流行的几种软阴影方法为: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 rangefloat light_range = LightSize / LS_depth * (LS_depth - shadow_near);// pixel space rangefloat 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;elsereturn 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;elset_shadow += 1.0f;}return t_shadow / float(SAMPLE_NUMB);
}

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

柏松圆盘采样

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

其算法步骤如下:

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

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

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

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

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

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

void PCSS::calPoission(){//init gridint grid_num = cell_num * cell_num;grid.resize(grid_num, glm::vec2(out_of_size));// push first pointglm::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 neighborif(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结果如下:

Summed-Area Variance Soft Shadow Mapping(SAVSM):一相关推荐

  1. Summed-Area Variance Soft Shadow Mapping(SAVSM):二

    上文介绍的PCSS方法,其在平均遮挡物的计算和PCF需要大量的采样加权平均操作,如果半影如果半影区相当大,采样效率将相当低下.如果使用柏松圆盘进行固定步数采样,在较大的半影区情况下会出现明显的阴影分片 ...

  2. 渲染算法学习(一)-- Shadow Mapping

    目录 Introduction Shadow Mapping 2-Pass Algorithm Shadow Mapping Approximation In Shadow Mapping Perce ...

  3. Shadow mapping

    http://www.cnblogs.com/cxrs/archive/2009/10/17/1585038.html 1.什么是Shadow Maping?       Shadow Mapping ...

  4. 3DShader之阴影贴图(Shadow Mapping)

    好久没写shader了,一直被实验室要求作java开发,中间准备了一个月雅思,最近又被老师拉到东莞做Hadoop开发.马上面临毕业的问题了,突然人生苦短,趁有生之年多做点自己喜欢的事情吧,所以最近又开 ...

  5. 【GAMES-202实时渲染】1、软阴影01(Shadow Mapping、Peter Panning、PCSS原理超详细)

    Lecture3 Real-Time shadows1 1 Shadow Mapping回顾 2 Shadow Mapping缺点及解决方案 2.1 自遮挡现象 解决方案1 定义一个bias 解决方案 ...

  6. 【Shading】Shadow Mapping 阴影映射

    课程来源:GAMES101-现代计算机图形学入门-闫令琪 Lecture12 GAMES101 现代计算机图形学入门 主讲老师:闫令琪,UCSB 课程主页:https://sites.cs.ucsb. ...

  7. 高质量实时渲染课程笔记(三)— 实时阴影渲染1(Shadow Mapping、PCF、PCSS)

    文章目录 1 Shadow Mapping 阴影映射 1.1 概览 1.1.1 Shadow Mapping 是一个 2-Pass Algorithm(需要渲染两趟) 1.1.2 Shadow Map ...

  8. Shadow Mapping 的原理与实践 【转】

    Shadow Mapping 的原理与实践 [转] Shadow Mapping 的原理与实践 [转] posted on 2018-08-16 23:37 时空观察者9号 阅读(...) 评论(.. ...

  9. OpenGL shadow mapping 阴影贴图的实例

    OpenGL shadow mapping 阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #i ...

最新文章

  1. Spring-boot 启动完成时执行指定任务
  2. linux 下 用户与用户组
  3. C/C++ 读取配置(config)文件 开源库(libconfig)
  4. JAVA_OA(十四):SSM练手项目bug-Oracle分页web页面无法转到下一页
  5. 铺地毯pascal程序
  6. java自定义分页标签_自定义分页标签--仿javaeye分页效果
  7. php上传文件表单,php中关于普通表单多文件上传的处理方法
  8. DEBUG命令的应用
  9. linux简单命令3---帮助命令
  10. 110+优秀作品、20+热门领域曝光, 高通如何玩转“AI 的夏天”!
  11. java --微信支付2
  12. 列表转化成数组_30. 把数组排成最小的数
  13. csgo开发者控制台指令大全_csgo控制台指令大全 csgo控制台命令一览
  14. 蚂蚁金服 RPC 框架 Sofa-Bolt 结构分析
  15. 分词使用 jieba 、IKAnalyzer
  16. 细读《深入理解 Android 内核设计思想》(四)Binder 机制 [中]
  17. pandas 终极版1:创建和查看DataFrame数据 mysql读取数据
  18. 整机压力测试_app的压力测试怎么做呢?
  19. 期货黄金交易平台哪个最可靠?如何选择?
  20. Shell脚本学习指南(七)——产生脚本

热门文章

  1. Spark 配置指南
  2. 【Spark大数据处理】动手写WordCount
  3. day32 java的多线程(2)
  4. es6删除数组某项_es6删除数组元素或者对象元素的方法介绍(代码)
  5. xml格式怎么转换excel_pdf怎么转换成excel?这个转换技巧你值得拥有!
  6. android 开启线程关闭对话框,java – 从后台线程的PopUp对话框Android
  7. 实习日志_实习律师实习日志第十八篇(连载30篇)
  8. 哈工大威海算法设计与分析_【斯坦福算法分析和设计02】渐进分析
  9. webpack devserver配置_在webpack中使用babel
  10. html语言机标记椒用来调字体的,HTML语言 之 字体标记