Xenoblade3 渲染研究

Posted by FlowingCrescent on 2022-08-14
Estimated Reading Time 14 Minutes
Words 3.9k In Total
Viewed Times

本文初发布于zhihu
https://zhuanlan.zhihu.com/p/553209338

0. 前言

自两年前通关XBDE与XB2以来,笔者就对这个游戏系列非常着迷,不久前发售的XB3更是系列集大成之作,而它相比前两作也有长足进步,不禁让人感叹“这竟然是NS游戏?”;而笔者作为一名游戏开发者,当然也想更加深入了解其使用的技术方案,在研究之时觉得其内容还挺值得分享,因此便有了这篇粗浅之文。

本文大致目录

  1. 渲染管线总览
  2. 角色渲染相关
  3. 结语与其他

渲染管线总览

最终渲染图(1920x1080)

PreZ

平常只包括地形、石头与草,无角色

但特写镜头会有角色

大概是因为平常角色占屏幕比例较小,PreZ收益低,但人物特写的时候开启收益较大。
通常而言草与各种植物需要有PreZ以防止其Alpha Test破坏硬件EarlyZ机制,而石头与人物依情况开启。


GBuffer Pass

GBuffer 0(R8G8B8A8)


Albedo(RGB)960x544

笔触感(A)


Albedo(RGB) 1280x720


笔触感(A)
画脸的时候还特意加了一个类似笔触感的贴图
![[5[@J4PJ{EO6Z%X7NUIEZH.png



脸部延迟Pass结果
效果确实是有……但高桥似乎忘了自己是在开发NS游戏

该值为1

该值为0
总之看上去大概是个Ramp Offset或者扰动法线的什么东西。


在SD里验证了下这笔触图甚至不是四方连续的,高桥,你到底想不想好好做啊!?


GBuffer 1(R8G8B8A8)


960x544


R 猜测为金属度


G 猜测为光滑度


B
对草而言大概是“某值的相加/乘程度”,总之增大之后显得像自发光一样。

原值

原值*100

对角色而言应当是用于决定Ramp图中Y轴取值,可能也趁此区分了不同部位:

B
image.jpg
原值渲染结果
image.jpg
原值*2渲染结果


A 不明确,只能猜测
草的延迟Pass根本没有取A通道,还画个值上去干啥呢,可能又在哪个角落里用到了吧。

A

原值渲染结果

原值x10

原值x100

原值x1000
有些类似“值越大则越倾向于原Albedo值”,但似乎也不完全如此。

对头发而言似乎是高光强度。

原值

Gbuffer1 A通道为0


GBuffer 2(R10G10B10A2)


乍一看好像是个比较普通的屏幕空间法线Gbuffer,其实不然。
它B通道存的是AO

而A通道内容较不明确。
由于法线是一个三维单位向量,有其三维中其中两个值以及另一个值的符号值(正或负)即可还原出法线值;但譬如Spheremap Transform之类的法线编码算法其实是不需要额外存符号位的,xb3这算法可能是将半个球面进行投影,而剩下半球用符号位表示,提升一点精度?笔者对这方面内容实在算不上熟悉。

A作为一个符号位存储

A为原值


A为1 - 原值

A为原值

A为1 - 原值(这一张效果还是比较明显的)


GBuffer 3(R8G8B8A8)


RGB 960x544
乍看可说是极其诡异的一个GBuffer。

R

G

B
这张GBuffer的RGB通道单独看都不知道是什么,笔者一位不愿透露姓名的友人调试后发现这rgb三个通道为编码后的线性深度,也就是-PosVS.z,还原后如下图:
_H}NPV55I[]N7@P18%R1M5M.png
或许是被映射到0-1的非线性深度无法满足雾、后处理等算法的精度要求,才需要出此下策……这就是超远视距的后果吗?
float和rgb24转换的类似算法可参考这篇ShaderToy:
https://www.shadertoy.com/view/WsVGzm

那么接下来是A通道:

A

人身上大多数区域的值为0.5

A为0(还有部分亮部应当是边缘光)延迟Pass输出结果

A为1时延迟Pass输出结果
基本可敲定对角色而言便是影响Ramp的取值,可以理解为类似AO,为0就必为暗部,而为1就更容易为亮部(但不一定为亮部,大概只是把halfLamber的值给乘2了而已)。

于草而言,似乎也是相加了某值,

原值

值为0

值为1
感觉变化实在不明显,或许草也就是顺便蹭一下这个通道而已,不用白不用;比较重要的还是角色那个类似AO的用法。


GBuffer 4 (R11G11B10)


Emissive 960x544


Velocity Buffer(R10G10B10A2)

之后用于TAA的Velocity Buffer。

RGB 960x544

RG调整映射范围后


B
又在标记头发和脸,或许是让脸和头发受TAA影响小些?但为何不同人的值不一样呢,姑且懒得计较了。


A
应当与法线类似同样是记录正负符号位,用两个通道分别给R与G通道记录了符号。
虽说算上这个符号位,每个通道也就11位……作为Velocity Buffer勉强能用。


Specular Mask(R8G8B8A8)

在头发的GBuffer Pass时直接输出到一张RT上

960x544

1280x720
似乎对于卡通渲染中角色的风格化高光必不可少,在蓝色协议的技术分享中也有见到类似的GBuffer:

大伙真是拼了老命也要让角色走延迟。
540p也确实是有理由的。


Depth/Stencil(D24/S8)


经过重新映射后的深度值

同时也用模板值区分各个不同的部位,如普通PBR场景物件、角色身体、头发等。


GI Pass

根据之前的GBuffer以及反射探针计算GI

其中tex3即为反射探针集,共192面,而根据一个反射探针有6面计算可得一共用了32个反射探针……不过每个的分辨率都极小,单面64x64而已。

会输出两张RT,看上去一个是Diffuse GI,一个是Specular GI

Diffuse GI

Specular GI


Shadow Caster Pass

执行近处动态物体(角色和怪物)的Shadow Caster Pass

1024x1024
没错,只有近处动态物体(人物)执行了Shadow Caster。


Screen Space Shadow Pass

经 @GuardHei 点拨,XB3的阴影方案应当与XBX所使用的相差无几,Monolith将其称之为“Shadowmap Cache化”。
用简单而不太准确的话概括,就是将Shadowmap每级联都划分为3x3的9份,当触发shadow更新时才会更新其中一小块。
当然其中实现起来有许多的细节要考虑,看上去也是很吃显存。
在此只展示XBX在cedec2015的其中部分分享:

Shadowmap Cache化

以当前位置为中心渲染Cache Buffer

划分为9块

生成另外两个级联

每个CacheBuffer使用的相机高度(Y)相同,仅移动XZ

从cache中取用部分作为Shadowmap

那么xbx的阴影内容先告一段落,我们回到xb3

输入了3张场景级联ShadowMap,然后再结合上一步角色的ShadowMap进行阴影判断。



三张预烘焙的Shadowmap
三张Shadowmap均为1024x1024,格式为D16


阴影的RT格式是R8G8B8A8,但A通道没有内容.
R通道放的是角色以及场景的阴影结果,G通道放的是只有角色而无场景的阴影结果,B放的是只有场景无角色的阴影结果,大概之后也会对不同的阴影做什么操作吧,不过感觉也无关紧要就是了。
这也解释了为什么场景阴影不会随着时间变化而变化,因为NS的机能无法支持高桥给场景搞实时ShadowCaster,说到底这种超大场景还实时级联阴影的话开销极其爆炸。
而各个大时间段之间不切阴影方向大概也是为了不卡顿而考虑。


AO Pass

下一个Pass应当是AO Pass,在此之前似乎根据ddx以及ddy算了个未平滑的的屏幕空间法线,但这RT也并没有出现在后续的AO Pass Input中,不知是不是截帧有问题。

于是它Input只有深度图,而输出如下图

240x136
看Shader中只采样了5次深度图,第一反应是这采样数也太少了,还有这四分之一分辨率输出也太糊了。
具体是HBAO抑或GTAO,因为真不想看汇编shader,不得而知 。


之后会做模糊后再与前一帧的AO进行混合去噪,最后输出480x272。


Deferred Lighting Pass

于是终于来到了延迟Pass,把之前计算所得的各个GBuffer、阴影RT、AO RT、GI RT等全部输入。
带宽芜湖起飞。


PBR Pass



人物Pass(不包括头发)



植被Pass



头发Pass
于是头发刚画完,又抠出来加个后处理:


应该是用了类似SNN的算法以模拟“水彩”效果,xb3中还有兰兹大衣的绒毛也经过了该处理,在破晓传说中类似这种后处理更是被推广至整个游戏场景,被宣传为“Atmos Shader”,或许也值得其他游戏借鉴学习。
SNN算法可参考:
www.shadertoy.com/view/MlyfWd


Skybox/Cloud Pass

于是之后开始绘制天空盒、体积云等

普通的面片云


1/4分辨率体积云RT(240x136),似乎是作为最远景

并入Frame Buffer后


另一张体积云RT(240x136)

并入Frame Buffer后


Transparent/Forward Pass

这个队列不仅渲染半透明物体,甚至远景的不透明物体也在这时候绘制,如远景的天涯巨剑,大概跟Additional Light无关的玩意都可以扔这里。

Instance的半透明发光面片


天涯大剑,输入Albedo、法线后直接前向绘制到FrameBuffer上

顺便看了下这个大剑的PosCS输出,其中w便是深度.
我们仍未知道那天xb3所使用的相机视距数值。
当然,这种独特的远景物体,大概也可以重新设置相机视距后再提交渲染。

在另一个截帧中可发现半透明粒子在半分辨率的另一张RT上渲染,倒也是个比较常见的优化overdraw的手段。


TAA/Post Process

之后便是TAA以及各种后处理,其中涉及多个RT的创建与Blit等,截帧的内容也开始愈发混乱起来,比如其中某个看似是LUT调色的Input与Output:

总之,笔者难以保证之后的内容的准确度,只能尽量从正常的截帧中选取内容。

TAA


当前帧

历史帧

混合帧
当前帧清晰但是锯齿也清晰,而混合帧整体显模糊但也切实消去了锯齿……

DOF 景深


一个半分辨率且模糊后的RT


CoC Buffer,但是其中黑色区域没有存负值,也就是没有区分“前后景”。不知是截帧问题还是本就没做区分?


DOF结果

Bloom



提取亮部后进行数次Downsample

之后截帧的内容极其混乱,几乎只能看出做了个LUT。

总之最终本帧输出如下:


TAA及后处理前

后处理后,绘制UI前
同时这个草原的TAA会将本帧进行升采样,也就是TAAU,而播片时的mio截帧中TAA似乎并没有做升采样这一步,因此草原帧输出为1080p,mio那一帧为720p。也是笔者感到非常迷惑的一处,也无法排除是截帧问题……
总之,渲染管线至此终于是大致过了一遍。
其实有些内容此次并没有看出来,如笔者在游玩时发现似乎存在自动曝光以及体积光。


角色渲染相关

相信各位玩家都看得出来,此次xb3相比xb2与xbde,进步最为明显的就是它的角色渲染。

示例图







较有趣的部分

就个人而言,截帧前最关心的几个细节是脸部的明暗计算方法、“厚涂”风的头发、脸上的高光点、刘海阴影。
关于头发的独特风格实现在前文已有提及,是做了类似SNN的后处理,当然实际上是不是SNN并不清楚。

一开始以为脸部的明暗是类似sdf或者赛马娘的那种trick方法,但想了想这是延迟渲染,想太trick也有点难,一截帧发现它硬是整了张法线贴图……

脸部使用的法线贴图

法线输出结果
倒也能理解为什么感觉游戏中角色很容易就“阴阳脸”了,毕竟法线图能力有限,做不了很平滑的过渡。至于是烘的还是画的,反正国内应该没人用这种方案,姑且不关心了。


角色渲染使用的ramp图(256x256),可以说是相当复杂
应当是为了兼容各种身体部件、不同角色、各个场景的早、午、暮、夜四个时间段,甚至不同天气,最终导致ramp图如此复杂,也算是延迟渲染卡渲之痛吧。
而国内目前流行的角色走前向渲染就避免了这一点。


xb2时的ramp图就已经趋向复杂,没想到xb3更是夸张(图出自CGWORLD2018年8月号)

角色的衣服、首饰等应当是魔改PBR,让漫反射经过了Ramp图映射,其他大概与标准PBR相差不多,不过这也只是没看代码的纯猜测,可信度不高。


刘海阴影是用面片做的,在GBufferPass之后作为半透明物体前向绘制:


上一次看到这种做法好像还是在MMD的模型里,果然还是工匠克算法。


而在人物延迟光照Pass之后,又加了一些半透明物体以“画龙点睛”

延迟光照结束时的人物效果


增添脸部高光点
直接用半透明面片点了几个高光点,个人感觉它还会根据视角做一点偏移:

比如从左看脸时高光也会偏左,下嘴唇高光位置变化尤其明显。

这一张侧脸特写中,鼻尖高光的偏移与“浮空”十分明显



增添虹膜细节



增加反射

而这个反射真的使用了反射探针,虽然笔者在游玩期间根本没看出这个反射的变化……


最后点上眼睛高光

那么至此也大致将本作角色渲染中比较独特有趣的部分聊过了。

结语与其他

客观而言,这些技术方案实在不像一款NS游戏应该拥有的:超远视距、完整的延迟渲染管线、常增加管线负担的风格化角色渲染,xb3这实在是戴着脚镣跳舞;同样作为一名游戏开发者,我不禁心生敬意。

一开始本是单纯想研究一下角色渲染才截帧,结果因为其他玩家朋友强烈要求让笔者分析一下整个游戏的渲染(顺便问问到底为什么画质这么糊),索性就趁此机会学习一下这个最不像NS游戏的NS游戏所使用的渲染方案,但笔者既非图形程序也非引擎开发,只是一个写写破shader的初出茅庐TA罢了,个人能力所限,能分析的技术只是这个游戏的冰山一角,而文中许多基于猜测的内容很可能错误甚至牛头不对马嘴,还请多包涵。
也希望各位不会被笔者误导,可以亲自看看xb3的渲染流程,因此将截帧分享出来(使用yuzu模拟器OpenGL模式,Xenoblade3 1.00 ver):
链接:https://pan.baidu.com/s/18dbrPCAXGrg_fsZF9FiF8g
提取码:xbnb

参考资料:

  1. Hallucinations re: the rendering of Cyberpunk 2077:http://c0de517e.blogspot.com/2020/12/hallucinations-re-rendering-of.html
  2. CGWORLD (シージーワールド) 2018年 08月号
  3. float to RGB to float :https://www.shadertoy.com/view/WsVGzm
  4. SNN/Kuwahara Filter :https://www.shadertoy.com/view/MlyfWd

感谢您阅读完本文,若您认为本文对您有所帮助,可以将其分享给其他人;若您发现文章对您的利益产生侵害,请联系作者进行删除。