前言
过年前准备开一个趣味性强一些的系列,给没什么游戏开发基础的玩家科普一些豆知识,主要谈谈为什么游戏世界还无法完全模拟真实的物理世界,以及一些细节的现状和发展脉络。
这次是玩《波斯王子 失落的王冠》中,我偶然看到一些场景爬墙时会有自己的镜像反射,让我联想到了很多不同3D游戏中的镜子和镜面反射渲染的例子,产生了这个选题。
文中可能有些描述不够精确,但总的来说应该是没有技术认知上的错误。如有不对也欢迎纠正。
1 引擎的默认渲染下的镜子会是什么样
传统的渲染(对比光线追踪来说)方式,介绍到最简单的程度就是先确定三角面,后着色绘制(这里面有一些三角面的组织形式和各种优化,就不展开了)。
(图中展示了从三角面到像素的光栅化( Rasterization )过程,广义的光栅化也包含从顶点变换到着色的全部流程)
一个引擎的摄像机拍摄到的物体,在非递归的情况下(光线追踪就是一个递归方案),为其渲染提供信息的来源是其自身的一些属性定义——例如颜色、反光程度等等,可以提炼称为一个物体的渲染材质(Material)。颜色可以由贴图、自发光颜色、叠加颜色等信息来提供,反光程度常见的有粗糙度模型或金属度模型(处理大多数实心物体);后续其实还引入了一些其它复杂属性,但也没超出预定义的范畴,在当前课题下不展开了。
如果镜子在渲染时的呈现完全来自于这些定义(也是传统渲染方式中常规的情况),结合场景中设置的光源,把这些信息代入到一系列换算中,会得到一个最终的颜色——一个反光程度和明暗程度看着似乎对,但是不能反射其它物体的镜面。
又由于这个镜面很平,所以对于反射的光线,要么几乎看不到,要么就会非常亮。这就只取决于光源和镜子的夹角了。
(图中是《最终幻想7 重制版》中的克劳德临时房屋的镜子。图中的镜子和刀背就属于前面提到的光线反射情况,作为虚幻4引擎做的游戏这个场景做得略粗糙了)
2 只反射静态物的镜子
(图中的圆球展示了这种反射技术)
如果是固定角度看的镜子,大家可能会想到把需要照出来的物体“截图”下来作为贴图“贴”在镜子上是不是就行了。这种方案实际上的思路也确实如此,整体引入了一种叫预渲染的方案,在编辑时摆好场景后,先生成一个采样好的场景采样资源——常常是一个贴图映射多面体(CubeMap);在运行时,引擎支持镜子的渲染混合场景某一面的采样贴图,呈现反射出场景某个面的效果。这套方案及其扩展也被称为基于图像的照明技术(简称IBL) 。
(图中展示了一个CubeMap的例子)
这种方案除了也看不到动态物体外,其实能想到还有一些问题。例如:
1)正常不同角度看向镜子中的一些物体,其中物体对应的“透视”是会变化的;但是从采样贴图中看过去,由于其实是一张图换了角度看,其实会有些许别扭感,因为是“失真”的。尤其和镜子夹角越大,就越假。
2)光源如果会显著变化,这种采样也是失效的。采样结果的颜色只能均匀变化,可能从基础视觉上都无法反映应有的结果了。(例如加了一个点光源之类)
因为多少有些“不够真”,所以这个方案常常用来以较低的分辨率覆盖场景中各类需要反射的面积不大的材质,例如金属管、泥水坑等等。大场景还常常用表面粗糙、做旧等方案来模糊这个反射面,以减少失真感,同时尽量降低采样分辨率以提高性能。
3 反射动态物体的镜子
我是《赛博朋克2077》的PS4Pro版的早期受害玩家,除了各种流式加载慢导致的BUG外,对于游戏里的照镜子需要“读一下”记忆犹新——当然这个是性能上的Trade Off,是一种妥协后的设计;类似的设计在《死亡搁浅》等游戏里也能看到。(*Trade Off这种不太好翻译的词还是保留其原词,避免为其引入不必要的贬义)
具体来说就是游戏里的镜子,在不交互的状态下看过去表面是模糊的,点了按钮进行交互后才显示出自己照镜子的画面(此时自己距离镜子的距离也相对固定)。一方面原因是常规第一人称视角下自己的头是不显示的(有一个版本影子都没头,这个不展开了);另一方面就是交互后才开启了另一个渲染摄像机,这也是下面要提的第一种方案。
1)镜像摄像机
首先,这个方案是可以覆盖动态物体和光源变化的,显然比前一个方案要“真”很多。不能大规模的、常驻的这么弄的原因也很简单——每多一个额外的摄像机都是额外的性能消耗,即使降低精度都会大大影响其原本的画质空间(可以粗略的理解成要渲染的东西翻了倍)。所以如果镜子在画幅中占比很小的时候,虽然也可以常驻开一个摄像机做镜像,但是确实很浪费。
如果对镜像渲染的内容降低分辨率,是否能节省性能呢?答案是只能节省显存,不能减少渲染过程的性能消耗。
这个方案里还有一个优化奇才,就是Capcom——在《生化危机8》里有一段八尺夫人照镜子的过场动画,动画结束后夫人一怒之下打碎了梳妆台,导致镜子也碎了。这一段虽然是实时渲染的,但是镜子中的夫人动画是预渲染的视频(所以如果你的帧数不稳或很低,就能看到镜子内外不同步的“奇观”);之后主角进入场景,由于镜子也打碎了,也就不用耗性能去处理镜子的渲染了。
(图中展示了前面提到的这一段过场动画)
对于画幅比较大,视角更可控的一些场合,还有另一种镜像渲染的方案。
2)屏幕空间镜面反射
(图中的车底在反射面上看不到,其实是这个方案的一个问题)
一个经典的场合,假设画幅的大约一半是湖面,另一半是湖面上要反射的场景,这时可以不从湖底打一个摄像机来渲染反射的内容,而是直接用对应反射位置的场景渲染结果进行采样(加点噪声后处理之类)。
这种方案的优势是不用额外的摄像机就能搞定。先渲染场景,再渲染镜面反射的部分;镜面反射的内容是基于已渲染出像素的而不需要从头绘制,性能上优于额外开摄像机的方案。反射算法的核心主要基于一种叫做Ray Marching(光线步进 )的思路。
相应的,限制也很明显,例如反射画幅大于画面中被反射的场景物体的画幅时,就会丢失待反射信息,导致失真——遇到这种情况很多游戏里看着就是黑的不反射,最多加点模糊啥的就糊弄过去了。
在前光线追踪时代,这些渲染的方案往往被称为各种“Trick”;又因为不像物理法则能有普适性,在场景渲染上把这种一事一议的具体需求做好做对,就是渲染优化的重要方面了。(*Trick这种不太好翻译的词还是保留其原词,避免为其引入不必要的贬义)
更多基于实时渲染采样或Ray Marching的方案还有一些,这里就不展开了。
4 光线追踪
(图中展示了一个充满反射物体的环境的例子。其实光线追踪还能覆盖环境光遮蔽(AO)和软阴影等等课题,使其以更接近物理法则的方式计算出来)
经历了前几个话题,了解了反射渲染的一些案例后,就能明白为什么要在性能允许的情况下引入光线追踪了——整体思路上还是想以类似物理法则的方式,用一套泛用性更强的方法解决仿真渲染中的各种光传播问题。现在广义的光线追踪往往指包含了路径追踪(Path Tracing)的一系列解决更真实渲染的方案。
要简单的理解光线追踪,可以认为此时引擎渲染中的视线或光线是可以递归弹射的,这种弹射和传播是符合物理规律的(应有的折射、衍射、漫反射、能量衰减等)——此时以上所有案例中的反射都可以用统一的光线追踪方案解决了。
在不使用光线追踪的情况下,影子、室内光照、间接光照等等数不清的渲染课题都要一事一议的用具体的算法来单独制作和优化;在使用光线追踪时就是一套方案一视同仁了——越接近真实物理法则运行,渲染方案的泛用性就越强了,只需要一些Trade Off而不需要什么Trick了。
但是光线追踪和之前传统管线那些替代方案是不太能并存的,所以其实大型游戏开发既要合理使用光追又要对传统管线优化,至少近10年的渲染优化成本都是加倍的。
结语
实时光线追踪是一个不断高速发展的过程,技术细节上我也还在不断学习,这方面我也是落伍的;但是实际上还有大多数硬件是无法支持质量略高的实时光线追踪的,因此前面介绍的那些方案还会广泛并大量存在于3D电子游戏中,一事一议的来做反射可能会是5年内仍然会延续下去的一个开发课题。
下次更新估计是年后,会继续更这个系列。