转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3716

2011年,我曾经写过一篇《不争气的geometry shader》,里面比较了在当年的多个平台上跑多种 render to cubemap的方法。经过帧数的对比,得出的结论是geometry shader会造成严重性能问题,甚至还不如渲染6遍的结果。2012年,同样的测试在GTX 580和GTX 680上重新跑了一遍,发现GTX 680上geometry shader终于比渲染6遍更快。

到了2019年,情况又会有什么变化呢?我们再来跑一次。说是2019年,其实我的电脑是2015年配的,Intel i7-4790的CPU,NV GTX 960的GPU。系统是Win10 19H1。但因为GTX 960和D3D 11.4支持在GS之前的stage输出SV_RenderTargetArrayIndex,我们其实不需要GS也能render to cubemap或者render to texture array。所以我们需要把这个因素也考虑进去。

NV GTX 960
6-pass Cubemap198.89
Dual Paraboloid430.67
1-pass Cubemap481.03
1-pass Cubemap w/ DrawInstanced480.03
1-pass Cubemap w/ instance GS501.52
1-pass Cubemap w/ DrawInstanced and RtArrayIndex from VS523.79

6种做法

这里有必要再解释一下上表的6种做法。

  • 6-pass Cubemap是个基准。用最传统的做法,在光源的位置向6个方向各渲一遍场景,得到cubemap。
  • Dual Paraboloid来自于2008年的一个方法。这需要渲染2 pass,正反面各一次,把经过tessellation的场景参数化到抛物面坐标系上。这种方法看似不错,但实际上省不了多少drawcall(因为在高层就做了视锥剪裁),而且因为需要tessellation,反而需要shader model 5,对硬件要求和渲染开销都不低。
  • 1-pass Cubemap的做法是,把每个顶点在VS里面分别乘上6个model view projection,得到了6个position全都传给GS。在GS里把每个三角形根据不同的position生成6份,跟视锥做一次裁剪,把看得到的三角形通过SV_RenderTargetArrayIndex传到texture array的不同index上,一个pass完成render to texture array。
  • 1-pass Cubemap w/ DrawInstanced的做法是用DrawInstanced来在IA的时候就生成6倍的顶点。这样在VS里面只需要根据SV_InstanceID选择乘上哪一个model view projection。在GS里就只要根据SV_InstanceID输出到不同的SV_RenderTargetArrayIndex就行,不需要产生新的三角形。
  • 1-pass Cubemap w/ instance GS用到了D3D11新增的instance GS功能,让GS自己instance多个,而不用IA来进行instance的操作。
  • 1-pass Cubemap w/ DrawInstanced and RtArrayIndex from VS则是前面提到的新方法了。在CPU端和之前的 1-pass Cubemap w/ DrawInstanced一样,通过IA产生6倍的顶点。但得益于新的软硬件,可以在VS里直接输出 SV_RenderTargetArrayIndex,而完全不需通过GS。

分析

通过分析这张表格,我们可以得到一些结论。

  • 1-pass render to cubemap在现在的GPU上,已经不是问题。单单这种从一个三角形生成多个三角形的操作,性能和DrawInstanced基本一样。也就是说,让GS去生成三角形和用IA生成三角形没什么区别。
  • 用了instance GS这个方法能比不用instance GS稍微快一点,代码也简单。
  • Dual Paraboloid已经成了废柴。性能、硬件要求、效果、使用方便程度,皆不如1-pass Cubemap等方法。可以让它退休了。
  • 从VS输出SV_RenderTargetArrayIndex,毫无疑问地成了冠军。性能最高,代码最简单。

结论

我们不再需要Dual Paraboloid。GS仍然不争气。如果检测到硬件支持从GS之前输出SV_RenderTargetArrayIndex,就尽量用它。检测的代码如下:

D3D11_FEATURE_DATA_D3D11_OPTIONS3 d3d11_feature{};
if (SUCCEEDED(d3d_device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &d3d11_feature, sizeof(d3d11_feature))))
{
vp_rt_index_support = d3d11_feature.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer ? true : false;
}
else
{
vp_rt_index = false;
}