在渲染场景的时候,一般来说分辨率和输出大小,也就是窗口大小相同。但在移动平台上,基本上没机会让你随便切换分辨率,都是只告诉你大小。在这时候,你如果想要用特定的分辨率渲染,就不可避免需要一次放缩。另一个常见的情况是,如果一个平台性能达不到需要的帧速率,也需要渲染较小的图之后,拉伸到指定分辨率。在XBox 360等console平台,有个专门的硬件放缩机制,只要打开就能自动把输入图像拉伸到720p或1080i/p(DX11.2才开始引入这样的机制)。但在PC和移动平台上,这个事情得在引擎里自己完成。所以在post process后端,加一个resizer就成了必然之选。
框架
这个放缩的框架很简单,把窗口大小填充给screen resolution,而渲染的分辨率则来自配置文件。如果两者不同,就加一个resizer拉伸一下。当然,如果两者连长宽比都不一样,就需要调整图像位置,在上下或者左右留边。
高质量放大
缩小比较容易,因为信息过剩。所以下文只讨论放大的问题。这里其实现在剩下的唯一问题就是,因为信息丢失,无论如何不能在放大后恢复出全部信息的。所以这里只能比较放大后的平滑程度,由此希望artifact尽量少。
质量比较
硬件本身能做的filter只有point和bilinear。如果需要更高质量的,可以用三阶的filter,比如bicubic。这里比较一下这几种filter对同样的输入会产生什么样的结果。原图来自于Windows的Playful Puppies主题的一张桌面墙纸,从1920×1200缩小到不同的分辨率,再upsample到1280×720,比较拉伸后的结果。bicubic的实现用的是wikipedia上的卷积公式,取A=-0.5。
896×504
首先试一下70%的大小。第一张是point filtering,已经有可察觉的锯齿(需要点开大图查看):
理论上只要>50%,bilinear已经能达到非常高的效果,更高阶的也不会比bilinear好太多。
确实,除了高频的地方略微清晰之外,其他地方都看不出bicubic和bilinear的区别。
640×360
50%的大小下,point已经有明显的锯齿:
bilinear总的来说还行,但能看出比较模糊了。
384×216
30%的时候,point已经没法看了。
bilinear也有比较多的间断点。
bicubic的优势体现出来了。
51×29
顺便测试一个更极端的,4%的大小。point下已经完全没意义。
bilinear也充满了十字形的artifact。
bicubic由于保证了一阶连续,虽然也看不出形状,但仍保持平滑。
速度比较
这里point和bilinear都是用直接一次采样的实现。bicubic用的是separable的优化,把原本需要的16次采样减少到8次。这些filter都是在pixel shader里完成的,还没有用到compute shader。测试的GPU是NV 4200M。
大小 | 类型 | 时间(ms) |
896×504 | point | 0.02 |
bilinear | 0.02 | |
bicubic | 0.79 | |
640×360 | point | 0.02 |
bilinear | 0.02 | |
bicubic | 0.56 | |
384×216 | point | 0.02 |
bilinear | 0.02 | |
bicubic | 0.30 |
从结果可以看出,point和bilinear在现代GPU上已经完全没区别。bicubic因为采样多了8倍,并有一定的计算,性能低了很多,不过也不算慢。
总结
总的来说,如果放大比率小于两倍,那么bilinear就已经足够。大于两倍的时候,bicubic是个不错的选择。以后有时间我会再测试其他的高质量filter,比如lanczos, sinc等。这个放缩机制以后有可能进一步扩展到dynamic resolution。
Comments