粒子效果在游戏中无处不在。大颗粒系统常见于烟雾、火灾、爆炸、灰尘和雾。如果这些效果填满屏幕,overdraw会非常严重,帧率会掉得很快,即使是在技术成熟的 3A 游戏中也是如此。以至于来半透明渲染的优化问题一直都是难题。
这里的解决方案是将昂贵的粒子渲染到屏幕外渲染目标,其大小是帧缓冲区大小的一小部分。但是这里的解决方案做了一个假设,认为许多用粒子建模的效果都是模糊且柔和的。更专业地说,烟和雾的图像只有低频,没有锐利的边缘。它们可以用少量样本来表示,而不会损失视觉质量。因此,低分辨率帧缓冲区就足够了。如果是具有高图像频率的粒子,例如飞行的岩石碎片。这里的方案可能就不适用了。
离屏渲染
粒子需要被渲染到离屏渲染目标,大小一般要设置主分辨率的一半以上。起码尺度要是二的幂。
离屏深度测试
要正确遮挡场景中其他的粒子,需要深度测试和深度缓冲区,其大小与我们的离屏渲染目标相匹配。我们通过对主 z 缓冲区进行下采样来获得这一点。基本算法是这样的:
- 正常渲染场景中的所有实体对象,并进行深度写入。
- 对生成的深度缓冲区进行downsample。
- 将粒子渲染到屏幕外渲染目标(也就是额外的rendertarget),并做深度测试。
- 将粒子渲染目标合成回主帧缓冲区,进行upsample。
获取深度
获取 DirectX 9 中的深度
在 DirectX 9 下,无法直接访问主 z 缓冲区。必须使用三种方法之一显式创建深度。
可以使用多个渲染目标(MRT):颜色信息写入第一个目标;深度写入第二个。这种方法有一些缺点。所有目标必须具有相同的位深度。后台缓冲区将为 32 位(或更多),但更少的缓冲区可能足以满足深度。更严重的是,MRT 与 DirectX 9 中的多重采样抗锯齿 (MSAA) 不兼容。
或者,可以通过在单独的通道中写入单个渲染目标来创建深度。这有一个明显的缺点,即需要对场景的大部分几何体或至少与粒子效果相交的部分进行额外的传递。从好的方面来说,深度可以直接以所需的离屏分辨率写入,无需downsample操作。
如果尚未使用 Alpha,则还可以将深度写入 RGBA 目标的 Alpha 通道。A8R8G8B8可能没有足够的精度。
获取 DirectX 10 中的深度
DirectX 10 允许直接访问深度缓冲区中的值。具体来说,您可以为深度表面绑定着色器资源视图(SRV)。毫不奇怪,您必须首先将其解除绑定为渲染目标。否则,它是相当简单的。
还有一个问题。无法为多重采样深度缓冲区绑定 SRV。原因还是比较深的。MSAA 解析操作应如何应用于深度值?与解析颜色值不同,平均值是不正确的;点采样也是错误的。其实没有正确答案。对于我们对粒子的应用,点采样可能是可以接受的。
MRT 可与 MSAA 配合使用,并且 MRT 目标可以具有不同的位深度。对于浮点表面格式的 MSAA 的使用也没有限制。
downsample深度
无论我们如何获取深度值,深度表面都需要被downsample到离屏渲染目标的较小尺寸。但是没有普遍正确的深度downsample方法。
点采样深度
简单的点采样深度会产生明显的伪影。
绿色三角形是前景对象,靠近视点,具有较低的深度值。白色背景区域具有最大深度。虚线表示的 16 个像素被下采样为 1 个离屏像素。A 点是我们进行深度采样的地方。它不能代表 16 个输入像素中的大部分;只有像素 B 具有相似的深度。
如果绿色三角形是前景对象,后面有一个粒子,则深度测试将遮挡低分辨率像素和所有 16 个高分辨率像素的粒子。结果可能是前景物体周围出现光晕。如下图b所示
最大深度样本
可以通过进行多个单点采样来改善光晕。上图中的c显示了downsample时应用最大函数的结果。从全分辨率深度数据中对四个深度值进行采样,并取最大的一个。这具有缩小粒子屏幕外图像中的对象轮廓的效果。但这绝对没有物理或理论依据。它只是看起来比光环更好。这是一个权宜之计。这就是深度downsample函数必须由应用程序定义的原因。这些瑕疵足够小,当低分辨率粒子图像添加回主帧缓冲区时,它们很大程度上被线性混合隐藏。
深度测试和软粒子
深度测试是通过像素着色器中的比较来实现的。downsample深度缓冲区绑定为纹理(DirectX 10 中的 SRV)。正在渲染的粒子的深度在顶点着色器中计算并向下传递到像素着色器。
在像素着色器中实现的深度测试
float4 particlePS(VS_OUT vIn): COLOR { float myDepth = vIn.depth; float sceneDepth = tex2D(depthSampler, vIn.screenUV).x; if (myDepth > sceneDepth) discard; // Compute color, etc. . . . }
软粒子效果会在粒子接近与实体场景对象相交处时淡化粒子 alpha。从而避免了恶劣、尖锐的交叉路口。视觉效果非常好。
软粒子比二元深度测试更好
float4 particlePS(VS_OUT vIn): COLOR { float myDepth = vIn.depth; float sceneDepth = tex2D(depthSampler, vIn.screenUV).x; float zFade = saturate(scale * (myDepth - sceneDepth)); // Compute (r,g,b,a), etc. . . . return float4(r,g,b, a * zFade); }
Alpha 混合
部分透明的粒子效果通常混合到帧缓冲区中。设置 Alpha 混合渲染状态以创建混合方程:
p 1 = d ( 1 - 1 ) + s 1 1 , |
其中p 1是最终颜色,d是帧缓冲区中已有的目标颜色,s 1和 1是像素着色器输出的传入源颜色和 Alpha。如果随后在p 1上混合更多粒子,结果将变为
p 2 = ( p 1 )(1 - 2 ) + s 2 2 = ( d (1 - 1 ) + s 1 1 )(1 - 2 ) + s 2 2 , |
p 3 = ( p 2 )(1 - 3 ) + s 3 3 = (( d (1 - 1 ) + s 1 1 )(1 - 2 ) + s 2 2 (1 - 3 ) + s 3 3 , |
将p 3 的方程重新排列为组d和s项给出
p 3 = d (1 - 1 )(1 - 2 )(1 - 3 ) + s 1 1 (1 - 2 )(1 - 3 ) + s 2 2 (1 - 3 ) + s 3 3。 |
将除帧缓冲区之外的所有内容存储在离屏渲染目标中。最终的组合操作需要生成p 3,或更一般地说p n。
先取d项——这样更简单。它乘以每个混合的 alpha 值的倒数。因此,我们将这些乘法累加到渲染目标的 Alpha 通道中。
接下来,检查对p 3起作用的s项。请注意,如果目标初始化为零,则它们采用与p 2、p 3等的传统 alpha 混合方程相同的形式:
0(1 - 1 ) + s 1 1 |
0(1 - 1 )(1 - 2 ) + s 1 1 (1 - 2 ) + s 2 2 |
0(1 - 1 )(1 - 2 )(1 - 3 ) + s 1 1 (1 - 2 )(1 - 3 ) + s 2 2 (1 - 3 )+ s 3 3 |
用于渲染屏幕外粒子的 Alpha 混合状态
AlphaBlendEnable = true; SrcBlend = SrcAlpha; DestBlend = InvSrcAlpha; SeparateAlphaBlendEnable = true; SrcBlendAlpha = Zero; DestBlendAlpha = InvSrcAlpha;
将粒子添加到帧缓冲区中以实现火焰等发射效果也很常见。处理这种情况比前面的 alpha 混合情况简单得多。值会累加到离屏目标中,然后添加到帧缓冲区中。
混合分辨率渲染
使用最大深度值对深度进行下采样可减少光晕。然而,屏幕外的粒子图像仍然可能是不可接受的效果。
当图像动画化时,这些伪影会闪烁并弹出。这在快速移动的动画中可能并不明显。然而,缓慢且连续地移动就会有这种效果。
我们可以通过边缘检测后然后对边缘做全分辨率处理来解决这个问题。
边缘检测
检查渲染目标中的离屏颜色和 Alpha 通道
使用标准 Sobel 滤波器可以轻松实现边缘检测。结果存储在另一个离屏渲染目标中。
使用模板进行排版
边缘检测滤波器选择一小部分块状像素。可以通过仅在出现边缘的地方以全帧缓冲区分辨率渲染粒子来修复像素化区域。
模板缓冲区用于有效屏蔽帧缓冲区的区域。当低分辨率、离屏粒子被组合回主帧缓冲区时,我们同时写入模板缓冲区。模板值无法由像素着色器显式设置。所以需要为所有像素启用模板写入,然后像素着色器可以调用丢弃关键字来禁止输出。这可以防止该像素的模板写入,并且像素着色器可以有效地控制模板值,从而创建蒙版。
然后对粒子进行第二次渲染。模板状态的设置使得像素仅写入先前合成通道未触及的区域 。这里显示下结果。
结果
上图将全分辨率粒子与混合分辨率结果进行了比较,其中屏幕外表面按 4x4 缩放。混合分辨率结果非常接近高分辨率版本。但是要注意低分辨率表面无法充分表示此细节,也要注意高频的图像是无法这样处理的,并且可能会导致画面模糊。
结论
结果表明,低分辨率离屏渲染可以带来显着的性能提升。它有助于使粒子的成本更具可扩展性,以便在过度绘制的最坏情况下(粒子系统填满屏幕)帧速率不会急剧下降。在真实的游戏中,这将转化为更一致的帧速率体验。
从视觉上看,结果永远不可能完全没有伪影,因为没有完美的方法来对深度值进行downsample。所以总会存在一些采样不足的问题。然而,在游戏中,诸如爆炸之类的粒子通常会快速移动,因此小的伪影可能是可以接受的。