【技术美术图形部分】实时阴影:光栅化与光线追踪

1 实时阴影:光栅化

参考实时阴影技术总结

光栅化算法由于缺少全局信息,每个fragment不清楚全局光照的情况,因此实时阴影大部分是基于Shadow Map实现的。

需要预渲染储存深度信息,从光源渲染场景中每个点的深度值储存在深度纹理中,再从Camera视角出发开始渲染场景,与深度纹理一一比对以判断当前渲染fragment是否在阴影中。

简单的ShadowMap仅仅是硬阴影,实现软阴影就需要进一步对阴影边缘进行处理,按照最后的处理效果分类的话可以分为:

  • 一致性:PCF、CSM、VSM、ESM
  • 距离相关(半影):PCSS

按处理方式可分为,

  • 过滤(filtering)阴影贴图:PCF、PCSS
  • 预过滤(pre-filtering)阴影贴图:VSM、ESM
  • 屏幕空间模糊

我们已知基础ShadowMap的算法表达式:

f(z)=H(z-d)

H(x)=0(x\geq 0),1(x< 0)  

其中,z为渲染时物体在光源空间下的深度,d为物体在Shadowmap采样获得的深度(遮挡物位置深度)。

1.1 PCF

Percentage-Closer Filtering,即百分比渐进过滤,这是一种很朴素的思想。不是要实现阴影边缘模糊吗?好,直接buff拉最大——直接对多重采样,即对f(z)进行滤波处理,阴影的质量通过控制采样数量(sample num)和采样步长(stride)决定。

缺点就很明显了,

  • 硬上多重采样,算法效率低,场景复杂度和滤波核大小十分影响性能(当然如果是PC端的话用PCF就绰绰有余了,对性能不会有太大的限制)
  • 不支持预滤波处理——不能mipmap

但是,性能上的不足目前硬件越来越厉害了,其实是慢慢在优化的,其次PCF与之后提到的CSM、VSM那些相比不需要其他的纹理格式,至今还是最通用的软阴影解决方案。 

1.2 CSM

Convolution Shadow Mapping,即卷积阴影,注意此CSM非彼CSM(Cascaded SM)。

基础Shadowmap记录的深度值要么是0要么是1对吧?0到1的突变就是造成阴影“硬”的原因,实现软阴影本质上就是让0到1突变别那么明显。CSM就是对f(z)的公式做了个fft(快速傅里叶变换),重构了shadowmap(截图自实时渲染中的软阴影技术):

注意:截图中f(d,z)dz跟本文前面提到的H(z-d)中的dz意义是反过来的。

 M要选择大于4的数,且M越大整个曲线将越接近原始样子,效果越好。

CSM与PCF相比优点在于,

  • 由于只是对Shadowmap进行预处理,因此场景复杂度并不影响性能
  • 支持预滤波,即可以在CSM之前对depth map做一个预blur

不足在于,M越大,阴影质量越高,但同时效率也越低。

1.3 VSM

Variance Shadow Map,即结合深度概率分布和切比雪夫不等式的方差阴影贴图,将阴影遮挡看作概率问题,使用单边切比雪夫不等式求得概率作为阴影值。

单边切比雪夫不等式

参考切比雪夫不等式到底是个什么概念?问题中各路大神的回答,简单总结一下:

切比雪夫不等式是刻画事物偏离它本质的偏离程度的大小的概率,将随机变量的期望和方差联系在了一起,不需要知道变量的整个分布,只需要知道标准差就可以估计出结果,其基本形式为,

P(\left |X-E(X) \right |\geq \varepsilon )\leq \frac{D(X)}{\varepsilon ^{2}}

式中,方差

D(X)=\int_{-\infty }^{+\infty }(X-E(X))^{2}dF(X)

E(X)是X的期望,F(X)是X的分布函数。

(单边的切比雪夫不等式) 描述了大于某一常数\varepsilon的概率P

P(X-E(X) \geq \varepsilon )\leq \frac{D(X)}{\varepsilon ^{2}+D(X)}

VSM计算思路

  • Shadowmap中2个通道分别储存dd^{2}:跟ShadowMap思路相同,需要先从光源处做一次光栅化,但不同的是不仅需要储存深度d还要储存深度平方d^{2}
  • 对Shadowmap进行滤波:对dd^{^{2}}通道进行模糊处理(参考文章用的是box filter),获得期望深度和深度平方
  • 采样当前着色点p在Shadowmap中对应的期望深度E(d)和深度平方E(d^{2}),并计算方差, 

D(d)=E(d^{2})-E(d)

  •  求出光源空间下p的深度z:比较zE(d),如果z更小,那么无遮挡,直接返回1,如果z更大,进行下一步
  • 对应P(X-E(X) \geq \varepsilon )\leq \frac{D(X)}{\varepsilon ^{2}+D(X)}式中就是求出深度z偏移期望深度E(d)的概率,令\varepsilon =z-E(d),则

P(X \geq z)\leq \frac{D(X)}{(z-E(d)) ^{2}+D(X)}

优点:效率高

与PCF相比,但效率更高(通过方差避免暴力采样

与CSM相比,VSM开销更小

问题:漏光、内存大

关于这个漏光问题,参考影子传说——三种Shadowmap改进算法的原理与在Unity中的实现,解决方案是重映射,然而映射越厉害,阴影就越硬,相反漏光减少。

由于VSM需要真用更多的内存——需要MRT额外储存d^{2},需要两个float通道储存数据,移动端应该吃不消

补充:

1.4 ESM

Exponental Shadow Mapping,即用指数函数替换掉原表达式中简单的相减,实现方法如下(截图自参考):

与CSM相比ESM表达式更加简洁,效率也更高,如下图(截图自参考),该作者验证了32位float下ESM的c取80效果最佳,且生成阴影比M=16的CSM生成的阴影更好:

pre-filtering

基础的ESM仅仅解决了软硬问题,,而边缘锯齿依然存在!因此肯定要额外进行滤波的,那么到底是pre-filtering还是filtering?该参考文章的作者又进行如下证明:

对于使用指数表达式的ESM来说,ESM处理后再对结果进行filtering和pre-filtering完全是等价的,而pre-filtering(MSAA、高斯模糊效果都不错)当然比filtering要好得多!

优点

支持pre-filtering,与VSM相比ESM只需要一个float通道,无需MRT。

问题

ESM的不足在于,虽然锯齿问题完全可以pre-filtering解决,但软阴影质量极大受到c值影响:c值小了会漏光,c值太大“软”得不明显。PC端完全可以直接PCF,而移动端32位float精度又不够,具体可以看看这篇文章有带图片分析。

1.5 EVSM*

Exponential Variance Shadow Mapping,EVSM是对VSM的一种改进,具体可以看影子传说——三种Shadowmap改进算法的原理与在Unity中的实现

1.6 动态阴影:PCSS 

Percentage-Closer Soft Shadow,即百分比接近软阴影。这个与上面几种方法解决的问题不一样,这个解决的是阴影的一致性问题,即实现动态阴影

具体实现方法可以参考之前我的202作业记录:

GAMES202作业1-实现过程详细步骤,放上一张最后的效果:

2 实时阴影:光线追踪

2.1 ShadowMap的不足

我发现很多人在比较ShadowMap和光追阴影的时候总喜欢拿软阴影来说事,虽然传统的ShadowMap无法实现软阴影,但PCF、PCSS可以呀!感觉硬伤其实是在精度问题:无论用何种算法,只要是基于ShadowMap思想的一定逃不过需要图来储存深度信息(VSM甚至需要两张),而图,就一定会涉及到分辨率问题——低了看不清,高了性能上不去;还有抗锯齿问题。

随着场景越来越大,ShadowMap这种问题一定会越来越明显。

2.2 考虑遮挡

光线追踪算法的单一代码路径可以处理所有的阴影场景,只需在视线发出光线与场景中物体求交时,做一个距离上的对比,挡住则不计算光强,拿101光追框架看,光追代码中如何考虑遮挡:

实时光线追踪技术:业界发展近况与未来挑战文中的3.4就介绍了Ray Tracing Shadow。

关于rtShadow就先谈到这里,后面有机会会继续补充。

参考

 实时渲染中的软阴影技术 - 知乎 (zhihu.com)

 影子传说——三种Shadowmap改进算法的原理与在Unity中的实现 - 知乎 (zhihu.com)

【论文复现】Variance Shadow Maps - 知乎 (zhihu.com)

单边切比雪夫不等式的总结 - 知乎 (zhihu.com)

实时光线追踪技术:业界发展近况与未来挑战 - 知乎 (zhihu.com)

RayTracingShadow/ScreenSpaceDenoiser 实现概述 - 知乎 (zhihu.com)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
计算机图形学中,光栅化是将几何图形转换为像素的过程,其中三角形的光栅化是最常见的操作之一。下面是一个简单的C语言算法来实现三角形的光栅化: 1. 首先,我们需要定义一个表示像素的数据结构,可以使用一个二维数组来表示屏幕或画布。 ```c typedef struct { int r, g, b; // 像素的红、绿、蓝分量 } Pixel; // 定义屏幕大小 #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 // 定义屏幕像素数组 Pixel screen[SCREEN_HEIGHT][SCREEN_WIDTH]; ``` 2. 接下来,我们需要定义一个函数来绘制一个三角形。这个函数接受三个顶点的坐标作为参数,并使用扫描线算法来填充三角形内部的像素。 ```c void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { // 扫描线算法 // ... } ``` 3. 在`drawTriangle`函数中,我们需要实现扫描线算法来填充三角形内部的像素。具体步骤如下: a. 首先,找到三角形的最小和最大y坐标,确定需要遍历的扫描线范围。 b. 对于每一条扫描线,计算与三角形的交点。 c. 根据交点的x坐标,确定需要填充的像素范围。 d. 在像素范围内,根据插值计算每个像素的颜色,并将其设置为对应的屏幕像素。 ```c void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { // 找到最小和最大y坐标 int minY = min(y1, min(y2, y3)); int maxY = max(y1, max(y2, y3)); // 遍历每一条扫描线 for (int y = minY; y <= maxY; y++) { // 计算与三角形的交点 // ... // 确定需要填充的像素范围 int startX = min(x1, min(x2, x3)); int endX = max(x1, max(x2, x3)); // 在像素范围内填充颜色 for (int x = startX; x <= endX; x++) { // 根据插值计算每个像素的颜色 // ... // 设置屏幕像素颜色 screen[y][x].r = red; screen[y][x].g = green; screen[y][x].b = blue; } } } ``` 这是一个简单的三角形光栅化算法的C语言实现。你可以根据需要进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九九345

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值