| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 图形学基础|实时阴影渲染 -> 正文阅读 |
|
[数据结构与算法]图形学基础|实时阴影渲染 |
图形学基础|实时阴影渲染文章目录一、前言很早之前,在图形学基础 | 阴影技术 Shadow Map中,简单地介绍一下Shadow Map实现阴影的思路以及阴影存在的问题。 本文将再重新整理一下,并介绍PCF、PCSS、VSM等阴影算法,温故而知新。 二、Standard Shadow map2.1 基本原理在光栅化算法中,基于ShadowMap的实现的阴影较为常见。 由于光栅化的渲染管线相比基于光线追踪的实现方式缺少全局性信息,每个 fragment(片元) 在着色时并不清楚全局的光照情况,无法直接判断自己是否处于阴影中,因此需要额外的预渲染阶段。 第一次渲染中以光源位置作为视点:
第二次场景渲染时,以正常摄像机作为视点:
整体思路就是这样,针对不同的光源类型有一些额外的注意点。比如:
Pass1,以光源的视角进行渲染时,要注意投影光源的投影矩阵的计算!
可以通过物体的包围盒来计算!代码如下: Pass2,采样ShadowMap的UV必须经过ClipSpace、透视除法和映射到纹理空间(而非真实的Width和Height)。
由于笔者使用的是DirectX12,其屏幕空间的UV需要反转一下,如下左图:DX的ClipSpace,右图:DX的屏幕空间。 2.2 ShadowMap问题2.3.1 精度问题当物体到光源的距离过远时,使用RGBA 中任何一个分量存储深度值都会存在精度丢失问题,毕竟只有1byte。 合适的做法是:在 Shadow Fragment Shader 中充分利用四个分量也就是 4 bytes 存储。 2.3.2 深度偏移问题现象如Shadow Acne(完全受光的屏幕上回出现条纹状的自阴影)。 出现的原因如下: Shadow Map 的分辨率是离散、有限的,多个 fragment 会对应到同一个纹素;如上图,图片每个斜坡代表深度贴图一个单独的纹理像素。 特别说明:上面的图是错误的。黑色的部分其实应该是被照亮的地方,而下图才是笔者认为正确的,红色圈起来的地方是被错误变成阴影的地方。 常见的解决办法是:
方法2的示意图如下:
所以这个偏移值的选择十分重要,其中若采用一个固定值例如 0.005,当表面法线与光源方向夹角很大时还是会出现。 更好的做法是:根据法线方向和光线方向计算:当法线和光线夹角较小的时,深度偏移值取较小的值;
若 偏移值(Depth bias) 太大会造成Peter Pan现象(物体似乎飘在了空中),也叫漏光(light leaking),如下图所示。 解决的方法,当然就是不要使用太大的shadow bias。 2.3.3 走样问题锯齿状的痕迹也被称作走样(Aliasing),如下图所示。 原因:Shadow Map是离散的,分辨率有限。多个fragment对应同一个纹素了。 解决方法:反走样、抗锯齿。 2.3 实现基础阴影Shader代码:
得到了一个很难看的结果:出现了自阴影等现象。 采用2.3.2中提到的添加一个偏移值方法,并加入与光源方向相关的计算。 这样修改之后,就可以看到一个阴影的基础效果。
硬件上的一些与ShadowMap有关的参数:参考:D3D12_RASTERIZER_DESC 和 Depth Bias
其中,后三者用于给深度做一个Offset(硬件帮忙计算),可以进行设置。 三、PCF(Percentage-Closer Filtering)3.1 软阴影和硬阴影如下图所示,a点光源之间没有任何物体遮挡,因此是完全照亮(Lit)的。 而地面也就是接受者(receiver)上的 c 点被遮挡者(occluder)立方体遮挡,处于本影区(umbra)。 b 点处于被部分遮挡形成的半影区(penumbra)。 2.3 实现的基础阴影,其边缘与亮处的过渡非常锐利,这是硬阴影(hard shadows)。 但是现实中的光源毕竟本身有体积,会形成拥有半影区的软阴影(soft shadows)。 两者的关系不是简单地将硬阴影的边缘模糊化处理就能得到软阴影,根据我们日常生活中的经验,光源和接受者的距离越近,软阴影的边缘就越清晰(软度降低)。 3.2 PCF原理PCF(Percentage-Closer Filtering),是Shadow Map的扩展技术,用来提供一种人工伪造的软阴影。 相比于基本的ShadowMap算法,其主要的区别在于:
如下图:
PCF的方法不是基于物理的,即并非对光源进行采样,结果依赖于接收表面,且与遮挡物距离也不会影响最终结果。 因而,这只是一种对软阴影的近似方法,但也能在许多情况下提供一个可信结果。 PCF中在着色点周围如何采样会影响软阴影结果。如下图所示:
实时阴影总结提供了PCF多种采样方式(Poisson Disk、Stratified Poisson Disk、Rotated Poisson Disk)得到的效果对比。 PCF的优点:
PCF的缺点:
3.3 实现这里实现的一个5X5的PCF:
得到的效果如下:
近距离看会有一定的走样条纹,可采用上述提到的泊松采样图+随机旋转进行优化。 四、PCSS(Percentage-Closer Soft Shadows)PCF由于采样区域是固定的大小,因此会在所有地方展示同样形状的软阴影,但这样并不符合现实的现象。 如下图,左边使用了一个小区域采样的PCF,中间使用了一个较大区域的采样,其结果显然都不正确。 合理的软阴影会像最后一张这样,在遮挡物与地面靠近处阴影显得更硬,在较远处更加模糊。而这第三张图片正是通过PCF的改进算法PCSS计算得到的。 如3.1(软阴影与硬阴影)介绍的:现实生活中,光源都是有体积的,从阴影区到无阴影区的这一部分叫做半影。在半影区只有一部分光源被遮挡,并且半影的大小跟遮挡物的距离有关。 如下图, W L i g h t W_{Light} WLight?表示模拟的光源大小,Blocker为遮挡物,Receiver为我们接收光源的面。 W p e n u m b r a W_{penumbra} Wpenumbra?即为半影,也就是Filter的范围。 根据相似三角形的关系,可以求得半影区域 W p e n u m b r a W_{penumbra} Wpenumbra?的计算公式: W p e n u m b r a = ( d R e c e i v e r ? d B l o c k e r ) ? W L i g h t d B l o c k e r W_{penumbra} = \frac{(d_{Receiver}-d_{Blocker}) \cdot W_{Light}}{d_{Blocker}} Wpenumbra?=dBlocker?(dReceiver??dBlocker?)?WLight?? 其中
d R e c e i v e r d_{Receiver} dReceiver?是已知的,那么 d B l o c k e r d_{Blocker} dBlocker?(遮挡物的深度)如何确定呢? 阴影图中不就存储了深度吗? 是否能够采样shadow map的单个点作为遮挡物的深度呢?
PCSS算法选择平均的遮挡距离来代替! 具体方法是:在shadow map上采样该点周围取许多点来计算各自的遮挡距离后求平均。 这样又涉及到一个问题,采样的范围要如何确定呢? 一种方法是:采用固定的范围,例如 4 × 4 4 \times 4 4×4、 16 × 16 16 \times 16 16×16。 另一种更好的方法是:动态计算遮挡范围,如图所示。
同样的,根据相似三角形可以求出所需查找的 S e a r c h R a d i u s SearchRadius SearchRadius。 对PCSS算法进行一下小结,其步骤如下:
可以看出,PCSS本质上就是:求出阴影中需要做PCF的半影部分再使用PCF计算。 这样动态调节了半影范围,即动态设置了PCF的搜索范围,这样使得我们的硬阴影部分清晰,软阴影部分模糊,动态的实现了不错的软阴影效果。 但是第一步和第三步都涉及到了在Shadowmap上采样一个范围的深度,这样是非常慢的。 五、VSM(Variance soft shadow mapping)5.1 算法上面介绍的PCSS算法,虽然可以较为准确地计算出需要进行PCF过滤的区域大小,但是其效率是非常的低的。 第一步和第三步都要在采样某个区域的深度进行比,这会导致其效率比较低,比较耗时。 本小节介绍的 VSM(Variance soft shadow mapping) 算法,就是解决PCSS第一步和第三步慢的问题。 5.1.1 PCSS第三步问题第三步(PCF),即要和周围选择的采样点进行深度比较,得到有多少比当前深度浅或深。 但其实我们并不是想知道周围选择的采样点每个深度,而是想知道当前着色点的深度在周围深度中的比例。 基于此,可以把深度当作一个正态分布。而正态分布仅需要知道方差和均值即可。 问题:均值如何获得? 在ShadowMap中,一个区域的平均值,即均值。 问题:方差如何获得? 想要快速得到方差,概率论中有一个经典公式,即:一个随机变量的方差等于它平方的期望减去它的期望的平方。 V a r ( X ) = E ( X 2 ) ? E 2 ( X ) Var(X) = E(X^2) - E^2(X) Var(X)=E(X2)?E2(X) 其中:
有了均值和方差,就能得到正态分布。 接下来,就可以通过计算面积CDF(X),得到有多少texels的深度比当前深度要小!
对于通用的高斯的PDF,可以把积分的值打成表,积分的值就是误差函数(error function)。这个积分没有解析解,只有数值解。 VSM采用了切比雪夫不等式来解决这个计算问题。 任意分布,只要有均值和方差,取一个值 t t t,就能得到其右边的面积,不大于( ≤ \le ≤)求出的值。 右边的面积表示的是:随机变量大于x的概率。 公式如下图所示。 注意:使用这个公式的前提条件为: t t t必须大于均值。 Shader代码实现如下:
小结:
5.1.2 PCSS第一步问题PCSS第一步做的是:得到一个范围内遮挡物的平均深度(是小于当前深度的有效深度的平均),其选择了一个范围采样了所有的texels。 这样的运行速度是比较慢的。 VSM定义以下量,遮挡物的平均深度为 Z o c c Z_{occ} Zocc?,非遮挡物的平均深度为 Z u n o c c Z_{unocc} Zunocc?。 有公式如下: N 1 N Z u o c c + N 2 N Z o c c = Z a v g \frac{N_1}{N} Z_{uocc} + \frac{N_2}{N} Z_{occ} = Z_{avg} NN1??Zuocc?+NN2??Zocc?=Zavg? 即,遮挡物所占比例 * 遮挡物的平均深度 + 非遮挡为所占比例 * 非遮挡平均深度 = 平均深度。 其中:
通过上述的近似和假设,就可以求解得到PCSS第一步所需求解的遮挡物的平均深度。 5.1.3 漏光问题相比于PCSS,VSM有很多优点,但其有一个很大的问题,就是所谓的漏光问题。 VSM的漏光,实际来自于离光源最近的物体的软阴影边缘,如下图所示。 当计算C物体接受的阴影时,使用的是A和B相对光源的可见部分(红线和绿线)的深度期望和方差,但其实应该只基于物体B(蓝线和绿线)来计算。 于是 Δ x \Delta x Δx 和 Δ y \Delta y Δy 比值越大,漏光会越明显,因为阴影贴图中并没有存储蓝色信息,因此无法完全消除漏光。 GPU PRO2 阴影篇介绍了几个简单的trick来减弱漏光。 例如:切除尾部,简单的切掉pmax函数的尾部,即将pmax的结果减去一个固定值来使得阴影整体变暗从而使漏光区域不明显。 但当是 Δ x \Delta x Δx 和 Δ y \Delta y Δy 比值非常大时该方法难以奏效。 代码如下:
5.2 实现5.2.1 算法流程在5.1中,我们介绍了VSM对PCSS算法中步骤一和步骤三运行速度慢的解决方法,那么在这里我们先总结一下VSM算法执行过程。
5.2.2 示例代码
直接渲染阴影的效果如下:
看了下虚幻这边的代码:
修改Shader如下:
得到如下效果: 六、更多更多阴影算法,将在后续进行练习和补充。 TODO:
参考博文
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 1:38:50- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |