邹春毅:我是来自字节跳动PICO团队的邹春毅,现任PICO视频引擎团队负责人。严格来讲,PICO视频并不是一款游戏,它是VR平台的一款应用,我本人在加入PICO之前也是多年的游戏开发者。
首先简单介绍PICO视频长什么样子。它和游戏比较接近,玩家在虚拟3D空间里可以自由移动,也可以和多人进行交友、互动,一起看电影、看各种虚拟和现实混合的3DOF演唱会或纯虚拟的6DOF演唱会,也有可交互的互动剧、剧本杀等等,所以在技术栈上它和游戏有很大的重叠部分。
下面简单介绍PICO视频渲染上的技术栈。完整的流­­程是比较复杂的,汇集了字节跳动多个技术中台的技术支持。开播端有全向实时3D渲染,有基于6DOF的动作捕捉;云端有云渲染、云端超分、8K视频编码,以及基于AI的深度信息获取;Pico视频App有安卓端的渲染,处理和视频相关的内容;在Unity端有很多和游戏相似的3D类的渲染技术;Unity端和安卓端共享同一个由Unity创建的渲染上下文进行混合渲染;Unity端渲染结果以Eye Buffer这样的共享纹理给到OS的最后一个Runtime去处理最后一次渲染、合成、插帧、锐化、超分等等。
我们引擎组负责Unity端和安卓端的渲染内容,借这张图说下VR渲染和普通游戏渲染方案的重要差别,就是Unity端到runtime的过程。游戏或手游通常会把最终结果渲染到back buffer中,back buffer是由Unity引擎所创建的,渲染到back buffer之后基本不会再过多二次处理。但是在VR中,最终的结果是渲染到eye buffer,这个是由OS创建,并不是Unity创建,渲染完eyebuffer后OS还需要做很多额外处理。所以在完整的流程中,Unity大概控制了70%左右,runtime控制了30%左右。
下面简单介绍目前主流的硬件基数,做渲染很大程度是在做优化,优化的上限是对硬件资源的最合理使用,了解硬件也有助于知道渲染的瓶颈。Pico 4是我们公司去年发布的一款产品,硬件参数是物超所值的。右上角是关于它的芯片,是骁龙XR2Gen1芯片,这款芯片虽然是VR的专用芯片,但它是2019年发布,基于骁龙865的改版,骁龙865目前算是中端芯片。
往下是它的分辨率, Pico 4能够支持单眼2160×2160,双眼4320×2160,达到4K+的分辨率。渲染分辨率通常最低在1504×1504,Pico视频为了体现更好的清晰度以及抗锯齿效果,渲染最低是在1804×1804,如果乘上双眼渲染的像素,大概是1080p的1.57倍。
往上是关于这个设备的电池容量,是5300毫安。因为PICO视频是PICO上的一方应用,OS对我们提出比较高的功耗要求,希望我们续航达到2.5小时,这样换算下来意味着每小时平常功耗控制在2100毫安,是整个芯片算力的80%左右。
这里再提及刷新率、帧率,手游一般达到30帧就比较理想,但是VR通常达到72帧才能获得比较好的延时感,那就意味着我们要在骁龙865的级别上渲染1080p 1.57倍的像素,同时达到72帧,所以对做渲染非常有挑战性。经过我们团队包括OS的共同努力,目前已经达到这个要求了。最近meta发布了了新的quest3,用的是骁龙新的芯片,苹果的VisionPro使用了M2这样一个目前地表最强移动芯片,所以意味着后面的XR从业者比较幸福一点,至少渲染有很大的发挥空间。
今天主要介绍我们在Unity端的几个技术点。第一个是关于颜色空间转换。手游2019年以前立项会有从Gamma转到线性的问题,后面绝大多数都使用线性空间流,不会有这个问题。但是PICO视频立项时因为处理视频流存在一些问题,所以它使用了Gamma。去年我加入团队之后做的第一件事情是把颜色空间从Gamma转到线性,提升美术的可发挥空间。
在处理颜色空间转换时,最麻烦的一点是关于半透明的混合叠加。下图左边是两个半透明物体在Gamma空间下的混合结果,右边是在Linear空间下的。左右两图存在差异的原因比较简单,Gamma和线性空间混合公式两者存在差别,不可能用简单的公式转变。
手游遇到这种情况一种解决方式是彻底把所有的资源在Linear空间重新做一遍,但是这对于PICO视频是不太现实的;另一种方式是进行一次转换,首先渲染Linear空间下的不透明物体、天空盒、半透明物体等,之后做一些转换,用Blit把它转到Gamma空间,去渲染UI,做完之后再转到线性,这个方案是没有问题的,手游也比较成熟。缺点是需要申请额外的RenderTexture,另外有多次的Blit、load store操作,对于PICO这种性能比较吃紧的设备来讲成本也是不能接受的。
所以后面我们想到用OpenGL下的一个FrameBufferFecth扩展去解决。一旦开启这个扩展,Fragment会从只写变成可读写,这个可读写的效率比较高,意味着可以模拟,先把当前的像素转到Gamma空间,然后去做一个混合,混合转到线性,实现局部的转换,这也是我们目前在线上版本的解决方式。
这里有一个细节就是它和MSAA的兼容。目前在VR设备上MSAA是理想的抗锯齿解决方案,对于开启MSAA的情况下,FrameBufferFetch获取的Buffer值不需要手动去resolve处理。但是现在面临一个新的挑战,就是目前PICO视频正在从OpenGL转到Vulkan,FrameFecthFetch只有在OpenGL下才支持扩展,意味着要使用新的方式解决。
方案上要回退到之前说的手游的方案,但是我们又不想做过多性能牺牲,所以采用了subPass解决。subPass是Vulkan非常重要的一个特性,它可以把某个Render Pass拆成多个subPass,每个subPass的输出可以作为下一个subPass的输入,整个操作是基于Tile-memory,所以它的效率比较高。
很多人认为subPass是FrameFecthfetch的平行替代版本,其实两者存在一定差异。首先,FrameFecthfetch是隐性实现当前的像素可读写,开启这个特性就可以立刻使用,而subPass需要自己组织管线,是显性的控制。另外,FrameFecthfetch每个render渲染时获得的buffer值是之前的渲染结果,但是subPass需要自己显式去指定。假如组织一个流程,subPass3、4、5所获取的输入值都是subPass1的渲染结果。
那么有没有一个FrameFecthfetch平替版本?我查了一下,2022年新增这样一个扩展,可以实现FrameFecthfetch的平替,但是后面咨询了高通,目前这个在XR芯片上是无法去做支持的。
这是我们基于subPass之后的渲染流程,我们组织了三个subPass,subPass1 会渲染linear空间下的不透明物体、天空盒、半透明物体等; 之后它的输出会作为subPass2的输入,转成Gamma空间,去渲染UI;它的输出作为subPass3的输入,转到linear空间;subPass3的渲染后会做一次store,这个store是reserve之后的结结果到 system memory。底下是截图,每个surface都是完整的渲染流程,这个渲染流程不需要申请额外的RenderTexture,可以直接渲染到EyeBuffer上。
这里是subPass和MSAA的兼容。在整个subPass之间数据传的时候它不会自动resovle,如果subPass2想去保留MSAA数据,需要手动resove,否则会丢掉这个MSAA的数据。
但是针对最后一个需要回传到systme memory的subPass可以自动relove,只需要调用ConfigtureResolveTarget,这个数据保存的是resolve的结果。中间渲染的MSAA,系统会给它生成memoryless的形式,但如果配置的是ConfigtureTarget,它保存下来就是MSAA的结果。如果想在VR上使用subPass,需要对引擎原码进行修改,做比较好的适配。
下面再介绍最后一个比较重要的支持,就是VRS(可变速率着色),我们内部叫注视点渲染。
首先简单介绍什么是VRS,通常RenderTexture像素密度是相同的,渲染任何一个位置,它的密度是一模一样的。但是假如能够让某个2x2区域只渲染其中1个像素,剩下3个像素用第一个像素结果直接填充,就形成不同的像素密度,这种技术叫“可变速率着色”。而VRS的核心要点是有选择的控制RT不同区域像素密度,进而达到降低性能开销的目的。
什么是注视点渲染(FR)?人眼有一个特性,我们视线的聚焦点始终是清晰的,余光的位置是相对模糊的。假如利用人眼的特性构造这样的像素密度图,它的VRS实例技术就叫“注视点渲染”。假如始终固定RT中心区域是高清的,四周是降低分辨率的,这样的技术叫“静态注视点渲染”(FFR)。假如利用ET眼动追踪,保持视线聚焦点始终清晰,这叫“眼动追踪注视点渲染”(ETFR)。因为ETFR可以保持视线聚焦点是清晰的,也就意味着可以使用更为激进的像素密度,进而获得性能优势。
接下来介绍OpenGLES版本的实现。这里使用高通的扩展支持。在C#端开放两个接口,一个是为某个rendertexture绑定注视点渲染,另一个接口可以绑定参数,这个参数构造一个像素密度图。C++端的核心是通过CommandBuffer为rendertexture去绑定,到GLSL里具体绑定扩展来实现。
我们会支持四种像素密度图:Low、Medium、High、TopHigh,右边是场景的实测,最后会有10帧帧率提升。线上绝大多数场景开启FFR Medium获得10%的性能提升。
接下来介绍注视点渲染第二个重要扩展,叫“Subsampled Layout”。左上是正常的FR渲染,左边是Tile Rendering情况,左上角是实际渲染的,剩下的区域是没有渲染的。回传到System Memory时是把Tile的数据全部回传,而中间没有渲染的是用渲染结果填充。
假如回传过程中只回传真正渲染的像素,它的带宽就进一步降低。到Final frame时,我们再用一个上采样把它还原成一个完整的结果,这样的技术就叫“SubsampledLayout”,它是用带宽的消耗来换性能的技术。
右边是一个截图,右侧没有使用“SubsampledLayout”,可以看到完整图像,左边是使用了这个技术,可以看到实际有图像的位置是真正渲染的,剩下的是没有渲染的。我们有个上采样过程,意味着可以加个滤波,天然获得抗锯齿效果。目前线上正在逐步使用这个技术点,开启的话平均会有3-5%的性能提升。
下面再介绍最后一个比较重要的扩展方案,叫“ShadingRate”。相较于FR针对某个RT进行采样,ShadingRate可以针对某个commandbuffer提交的内容无差别的降采样,可以指定提交的降采样的级别是什么样的。
因为现在很多手游都做大世界,好像不做大世界就不是MMO,做大世界通常自己接管它的场景管理,就可以在体验过程中明显知道哪些是远处的物体、近处的物体,可以为CommendBuffer提交绑定这样的扩展,进而获得性能上的提升。这个技术点又不分屏幕像素的位置,所以我认为这个技术点对手游来讲有比较大的可发展空间。因为PICO视频目前场景规模比较小,它的可扩展空间相对比较有限。
下面简单说下FR的整体性能价值。它的性能价值完全取决于屏幕像素压力,像素压力越大,它对性能提升越大。我们自己出了一个极端测试,提高渲染分辨率来提升它的渲染压力,什么都不开启的话大概有36帧,如果只是开启FFR medium可以提高到43帧,假如开启SubsampledLayout,可以提高到50帧,如果再开启ETFR,可以提高到58帧。
因为现在芯片算力不断提升,后面的场景渲染压力会越来越大,FR的性能优势会越来越明显。VRS不是VR的专利,现在很多芯片都逐步支持这方面的扩展,所以手游有很大的可发挥空间,比如景深等都可以尝试使用FR的扩展。
我的分享就是这些,谢谢大家。
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐