ob电竞·(中国)电子竞技平台

ob电竞·(中国)电子竞技平台

ob电竞

ob电竞

腾讯天美技术美术分享:虚幻5卡通渲染

【ashkeling专稿,未经授权不得转载】

ashkeling报道/最近几年,二次元游戏早已经从小众品类成为了手游市场举足轻重的大作来源之一。随着人们对游戏品质的要求越来越高,这对开发者的二次元渲染管线提出了更高的要求。

今年4月份发布的Unreal Engine 5无疑是业内最先进的游戏引擎之一,最近,腾讯天美技术美术工程师YivanLee在Unreal Openday上分享了虚幻5卡通渲染管线的一些基础理论,还谈到了虚幻5卡通渲染管线的扩展升级。

以下是Gamelook整理的完整演讲内容:

YivanLee:

大家好,我是YivanLee,是一名技术美术工程师,很开心可以来到Unreal Openday,今天我带来的分享题目是“虚幻5卡通渲染级渲染管线拓展”。

正如分享题目所示,我的分享分为两个大的部分,第一部分是虚幻5卡通渲染的基础理论,第二部分是虚幻5卡通渲染具体程序上的一些实现。第一部分可以分为四个大模块:卡通勾线(ToonOutline)、卡通shading,第三部分是场景shading,最后还有一点点动画。卡通渲染管线的实现主要是指对虚幻5渲染管线的定制,包括Material Shader的拓展、Global Shader的拓展,包括引擎管线内部的一些深度改造。

第一部分可能会比较适合TA和美术同学,第二部分也许可以对图形程序或引擎TA提供一些帮助。

第一部分:UE5的卡通渲染

因为我个人比较喜欢动漫和二次元,所以今年年初的时候我准备在虚幻引擎里研发一套深度定制卡通渲染效果,而且风格化渲染更好和UE的主攻方向相反,所以我预判不会出现我刚做完,UE就研发一套类似且更好的方案。

所以我从动漫的发展史开始着手研究,把一些二次元作品按照抽象和写实的关系从左往右排列,我们可以把画面分为两种方向:写实和写意。我们把游戏作品、影视作品、动漫、艺术作品按照写实写意从左往右排列找他们的规律,我们发现对于图形渲染来说,越写意画面越简单。简单的色块、线条、光影,风格化会对整个画面进行降频、抽象等操作,越写意,颜色越明快、简单,甚至没有明显的颜色和光影,有的只是简单的结构。

一个游戏、影视作品的画面写实写意占比多少可以根据艺术家的风格来决定,对于上述坐标来说,就是在哪个坐标范围。我发现大家比较认可的二次元范围是红框圈起来的范围,所以可以用它作为我们的真实数据(ground truth)区间进行探索。

我们得到的这个坐标轴还有很多值得深入思考的点,例如,如果在日式动漫中加入更多的光影和反射,这和迪士尼的渲染方向和人物建模之外的区别,它还有什么不同?所以我并不认为这就是下一代日式卡通渲染的方向,于是我们带着这些问题继续探索。

第一个技术模块:卡通勾线

卡通勾线是卡通渲染里最重要的效果之一,艺术家用线条来抽象世界里各个物体的结构。目前用于廉价实时勾线渲染的方案可以分为这四类:几何勾线、资源勾线、屏幕空间勾线和光线追踪勾线。

几何勾线常用的是backface勾线,即把模型向外挤出,也可以使用geometry shader(GS勾线)等方式进行边缘检测来生成几何线条。我在Unreal Engine中尝试了这几种方法,发现都可以获得比较好的效果。但是最终我放弃了GS勾线方案,所以几何勾线剩下的就是使用backface勾线。

资源勾线比较好理解,就是在资源贴图上做文章,比较直接的方法就是直接在贴图上画线条,但这样会受到多方面因素的干扰,如贴图分辨率、Mipmap、锯齿等等,为了解决这个问题,业界诞生了很多方法,比如本村线或者SDF之类的方法,但是这些对于美术工作量来说,其实是比较大的负担。

屏幕空间勾线就是利用一些屏幕渲染的空间数据进行边缘检测,常用的就是对深度、法线、ID等数据进行边缘检测,这也是我卡通渲染方案里面大量使用的方法。

几何勾线中,backface勾线是性价比极高的一种方法,只需要把模型再绘制一遍,把模型沿着法线方向挤出即可。为了克服断边问题,我们可以将法线进行平滑以后存为第二套法线,在UE中可以在batch mesh的时候再塞一个mesh进行二次绘制,也可以拓展一个mesh command,我这里使用了一个Primitive component,在add batch mesh的时候塞一次模型的方法实现二次绘制效果。

使用geometry shader也可以很轻松地查找出边缘然后绘制出geometry的几何线条,不过这种方法需要上传临边buffer,所以出于性价比的考虑,我并没有使用geometry shader进行线条生成。关于geometry shader的一些方法可以参考我在知乎专栏里的“Mesh Material Geometry Shader in UnrealEngine”这篇文章来实现。

在贴图资产上绘制线条是最直接的方式,不过考虑到分辨率、mipmap、抗锯齿等因素,使用本村线或SDF优化可以大幅减轻资源勾线的一些问题。本村线主要是工作量太大,SDF勾线需要提供一套高精度的SDF贴图,我们可以使用Substance Designer来快速制作SDF贴图,不过这都需要生产一些额外的资产,对美术工作量是一个负担。

接下来是屏幕空间勾线,它主要是利用很多屏幕空间数据来查找边缘来得出,因为Unreal Engine是以延迟渲染为主的渲染管线,所以这里我们有非常多的屏幕空间数据可以拿出来利用,例如深度、法线以及各种ID,都可以作为作为屏幕空间勾线的数据源。

在我的方案中,我会使用depth、normal还有ID这些屏幕空间数据对边缘进行查找,把边缘生成出来,这些数据也可以进行互补,把一些不以查找的边缘也可以生成出来,得到比较好的综合结果。

对于屏幕空间勾线的法线,我使用了顶点法线,因为贴图法线可能会被用来修整光影,所以如果把贴图法线用来勾线可能会和修整光影这个功能冲突。最后我的最终效果是多种勾线方式的复合体,包括Normal、depth、ID和backface等勾线方案的复合。可以看到,这些方案各有优缺点,但是我们把它综合以后可以得到一个各取所长的效果。

屏幕空间边缘检测其实有很多方法,我最终选择了Laplacian和Sobel算子进行屏幕空间检测,可以看到在不同权重和设置情况下,它的勾线其实是互补的,laplacian和sobel其实在不同的情况下各有所长,所以我最终取的是它们的复合结果。

对于Sobel或Laplacian算子来说,需要控制的一个重要参数就是它们的阈值,如果不对阈值进行区分可能就会产生很多杂线,例如脸部可能需要比较高的阈值来过滤掉杂线,但是对于头发来说,我们可能就是需要比较低的阈值来产出这些凌乱的线条,因为头发本身可能就是个会有很多线条的结构。所以对于阈值的控制,我们需要根据人物不同的部分,甚至是材质的不同部分进行精细化控制,可以看到控制前和控制后有明显的提升。

虽然法线+屏幕空间勾线的方式可以勾勒出大部分线条,但我们对其可控性很低,所以我增加了ID勾线的方式对勾线方案进行补充。我们可以对不同区域进行ID绘制然后检测这些ID的边缘来生成线条,这样可以让屏幕空间勾线的可控性大大提升。我想要使用的ID数据是三维的,其实也可以使用一维或二维的ID来精确地指出边界,比normal勾线可控性高很多,但缺点是ID数据需要一定的美术工作量,不过的确可以和normal、depth勾线进行互补。

通过各种方案产生了线条以后,还需要对其进行一些精细的控制,比如线条粗细、哪里有线条哪里没有线条?我这里使用了两种方法,第一个是在贴图上绘制线条的粗细然后传到Gbuffer里面,然后在屏幕空间勾线的时候读取这个粗细值进行控制。还有一个方法就是将全局权重暴露在postprocess里面去读取这个数据,然后对线条进行全局控制。

比较具有代表性的就是下颌线的绘制,可以看到下颌线是有变化的,当我们旋转的时候,下颌线有时候是不连续的、有时候又是连在一起的,这其实就是通过刚才一系列方案达到的效果。

我们可以通过指认线条粗细的区域,加上backface勾线对脸部线条进行补充,来达到这样的效果。

因为backface勾线天生是不对内部进行勾线的,那我们就用backface对脸部线条进行勾线,下颌线再使用刚提到的线条粗细在模型表面的权重控制来达到这样的效果。

解决线条生成以及粗细控制以后,我们还需要对线条进行上色,我们可以渲染一个outlinecolor render target对画面区域进行染色,还需要对outlinecolor render target的颜色进行外扩,以保证最边缘的线条是被染了色的。当然,也可以使用贴图来控制某个区域线条的颜色,对这个材质指认一个颜色进行染色。

处理了线条粗细和颜色之后,还需要处理线条的远近变化,对于屏幕空间勾线来说,如果画面拉到极远,线条会变得非常密集,覆盖掉人物。以往的勾线思路是越远线条就越细,但对于屏幕空间勾线,这条就不特别适用,因为像素最细就一两格,再细就没有了,所以我们需要转换思路:越远就越淡,这时候线条拉的越远就越接近basecolor或scenecolor的颜色。

除此以外,我们线条的LOD方案也是复合的,所以当距离大于一定程度的时候,我们可以将一部分勾线算法去掉,例如画面拉到极远的时候我们将内描边去掉,只留外描边,这样就可以保留最基本的线条信息,又不会因为拉的极远让线条把画面覆盖掉。

完成勾线功能之后,我们需要对FOV模型进行校正来模拟卡通渲染的扁平感觉。左边的图是未经FOV校正的,右边是经过校正的,可以看到校正以后整体画面更加扁平、更二次元卡通。

ToonShading

因为我想要大范围兼容卡通渲染效果,既支持非常风格化的赛璐璐,也支持像新海城一样稍微写实一点的风格化,所以我对UE原有的渲染管线进行了拓展,这对渲染管线无疑是有一定冗余的。如果项目风格可以确定的话,我们在原有的UE上进行改造即可,这样可以省掉大量的Gbuffer开销。

卡通材质因为是被艺术抽象过的,所以我们只能在有限的几个维度做文章。从卡通材质的表面属性出发,可以分为高光、亮部颜色、阴影过度、阴影颜色这几个维度,并且这几个维度是需要我们严格控制的,因为卡通材质可以控制的维度就这么几个,我们对这几个维度控制就可以做出不同的卡通材质效果。

例如对皮肤材质而言,我们需要控制它的高光、暗部的颜色和两部分之间的过度。我们可以通过预积分的方式精准控制阴影过度。除了上面讲的几点以外,卡通材质的形态也是它的表现重点,建模本身也是控制材质表现的重要一环。

例如我们对高光做好了控制之后,就可以很容易对金属和布料材质进行区分,这里演示一下动态效果:

可以看到金属质感已经接近手绘感觉,但是它又是动态的。

同样一个例子是头发的高光,如果我们对头发的高光做比较好的控制,也可以做好头发的质感。头发的高光需要特别说一下, 其实它有三种表现形式:第一种是比较传统的天使环,第二种是新海城动画里常用的圆圈高光,第三种是我们需要精确控制形状的高光,这里我们看一下它的动态效果:

精确控制形状的高光是通过贴图控制的,它的好处是可以控制形状,缺点是没办法随着视角一起移动,但是我们可以把一些光影遮蔽写进去,然后得到类似的效果。对于另外两种,天使环和点状的高光,其实就是各向异性和各向同性,各向同性只需要你把模型做的圆润一点,然后使用标准的圆圈高光就可以做出来;对于天使高光,我们需要渲染它的tangent,用Kaijiya模型计算天使环高光就可以。

阴影

第一个部分是自阴影(SelfShadow),因为我们需要兼容虚幻引擎5的多光源,所以只能选择纠正法线的方法来纠正模型表面的自阴影。模型表面自阴影纠正方法常用的有三种,第一种是模型手动卡线布线、卡顶点,第二种是代理模型烘焙法线,第三种是手绘法线贴图来纠正自阴影。

处理完自阴影以后,我们下一步就需要处理阴影的另一个部分:间接阴影,即来自其他物体或模型其他部分带来的投影。常用的技术就是shadowmap技术,虚幻引擎5中提供了一个新的shadowmap技术,也就是VirtualShadowMap,我把它和CascadeShadowmap的效果进行了对比,可以看到VirtualShadowmap其实更适合卡通渲染,因为卡通渲染就需要我们对阴影进行精确描述。

当然,我们也不能无脑使用VirtualShadowMap,以为打开VirtualShadowMap就能万事大吉了,我们还需要在虚幻引擎原有的VirtualShadowMap基础上做一些改进才可以得到我们想要的效果,至于如何改进,我会在第二大部分进行详细阐述。

除了自阴影和ShadowMap之外,我们还需要增加一些控制阴影的手段,我这里增加了一个CustomShadowMap的方法来对阴影进行高自由度控制,艺术家可以在SubstancePainter或其他绘图软件里对阴影进行绘制,我的卡通渲染管线会对CustomShadowMap进行读取,然后把它整合到整个渲染管线当中。CustomShadowMap可以和SelfShadow以及VirtualShadowMap进行完美兼容,这里可以看一下效果:

当我们建立起一套科学的Shadow方案以后,很多效果就可以水到渠成,不需要我们想一些trick的方法来拟合,比较典型的例子就是额发阴影,使用VirtualShadowMap之后,就可以让我们的渲染管线天生支持额发渲染,甚至多光源情况下的额发渲染也不在话下。

完成了阴影生成以后,还需要考虑阴影颜色,前面我们提到卡通材质的阴影颜色也是非常重要的一环,所以我们要对材质的阴影颜色进行严格控制,为此我渲染了一个单独的RT来存储模型表面各个部分的阴影颜色。

阴影颜色可以通过艺术家绘制贴图来控制,也可以指认一个颜色常量来控制整个材质的阴影颜色。可以看一下动态的效果:

各个阴影成分在写实渲染里是相加的关系,但在卡通渲染里是相乘的、合并的,所以我们的卡通渲染需要将多光源的阴影进行合并,这样才能统一控制阴影颜色。

这里可以看到写实多光源阴影和我的卡通阴影对比的区别。

当我们建立一个科学的多光源光照体系以后,很多效果就可以很自然的出现,不需要我们再去trick。之前举了一个额发阴影的例子,现在我们处理完多光源以后,边缘光同样也适用这个情况,我总结了边缘光的几种类型,第一种是多光源造成的边缘光,第二种是GI反弹造成的边缘光,第三类是光线投射产生的边缘光。我们的卡通渲染管线现在支持多光源,所以如果我想要边缘光,只需要多打一盏灯就可以实现。

同样,StepLight也是和刚才RimLight一样的原理,只要多打两盏光,StepLight自然就会形成,不需要多做BRDF上的trick。不过,如果非要在BRDF上多做一层其实也是可以的,这就会带来很高的灵活性,视项目具体需求而定。

间接光在真实渲染中是非常重要的部分,在卡通渲染中也同样很重要,我觉得这也是下一个世代卡通渲染的标配。这里我将在UE5的Lumen系统上进行改造,得到一个比较适合卡通渲染的ToonLumen。

ToonLumen的思路其实很简单,就是在UE5官方Lumen基础上进行降频、饱和度改变等操作,卡通渲染只需要非常低频的GI信息即可。除此以外我还暴露了一个全局参数,对卡通GI的强度进行调节,艺术家可以根据自己氛围的需求对卡通Lumen的权重进行调整。

下面是卡通Lumen的一个完整效果演示,我对GI颜色饱和度、GI信息、频率等进行调整,得到了ToonLumen,当然未来还有很多需要攻克的方向。

这是关闭了直接光,只有ToonLumen时候的效果。

这是关闭了ToonLumen的效果,它会慢慢变暗,因为它是分帧处理的。

然后打开ToonLumen,重新打开直接光。

我的ToonLumen只是对卡通材质的人物部分进行了处理,场景里面其他如果是引擎官方自带的材质,会走引擎官方原来的Lumen效果,这和新海城的思路比较类似。

对于卡通材质来说,亮部颜色是需要严格控制的,但是引擎的后处理其实会破坏掉我们卡通材质的颜色准确性,因为渲染的最后几步会进行曝光等一系列操作,这会破坏掉我们的颜色,所以我需要对后处理操作进行一些反计算,以抵消掉后处理的计算,我把它称之为InversePP。

可以看到InversePP之前和之后的效果对比,特别是对于皮肤等材质来说,效果是提升巨大的。下面我们看一段动态视频:

可以看到经过InversePP以后,皮肤上的很多细节都会保留,没经过InversePP的皮肤上很多细节全部爆掉了。

因为Unreal Engine的渲染管线是存在曝光处理的,所以我们的卡通渲染管线也需要进行曝光校正。如果卡通材质和引擎原来的材质曝光不一致,就可能导致卡通材质过曝等问题。

Bloom对卡通材质渲染最后的质感有非常大的作用,日式卡通里一般有一层柔光效果,这个柔光其实就是Bloom。

完成了渲染以后,我们还需要让我们的人物动起来,于是我们来到了动画部分。这部分我基本上没有对UE进行改造,使用的大部分都是UE自带的Sequencer、动画蓝图来计算人物裙子的动态效果。

我这里做了一个演示,没有过多的意境,只想朴实无华地展示一下技术部分,至于美术方面其实还有很多需要打磨、精进的点:

卡通场景渲染

完成人物渲染以后,我们进入场景渲染部分,制作日式卡通场景常用的方式是使用水彩,所以我们需要对渲染的场景进行水彩化处理。

我这里使用Kuwakara算法,对场景进行全局水彩过滤,可以看到Kuwakara算法其实是对物体内部进行色块化,但它的边缘还是保持清晰的,所以整体画面不会因为Kuwakara的过滤而变的模糊,它只会水彩化内部的细节。

可以看到这个水彩化的效果还是可以的,不过权重是需要调整的,并不是全局都使用同样的权重。

这里我们需要注意的是,人物是分开处理的,人物和场景使用的是不同的Kuwakara权重,场景可能要强一点,并且场景的Kuwakara强度会根据深度变化而变化。人物的话,我们直接在材质上指定一个固定Kuwakara权重。

对于植被,处理卡通植物除了平滑法线这样常规的手段以外,我们还需要同样对植被进行Kuwakara算法,不过植被的Kuwakara需要一个directional的算子对植被进行水彩化过滤。这里可以看到前后对比。

在植被渲染中,草地是比较特殊的,它和其他植被渲染是相反的,因为草地有很多alphatest信息,所以草地这时候就不太适合用Kuwakara算法。这时候草地反而需要降低Kuwakara权重,同时也需要降低法线以及阴影信息来达到比较柔和的水彩草地的效果。

可以看到这个草地的Kuwakara强度非常低。

第二部分:UE5卡通渲染管线的拓展

下面是分享的第二部分,也就是虚幻引擎5卡通渲染管线拓展,开始这部分之前我想要强调几点:

第一,我们在拓展或改造虚幻引擎渲染管线的时候,需要去遵循虚幻引擎的设计哲学,需要符合虚幻的习惯、规则来进行拓展或者改写,这样做的目的是为了以后可以兼容虚幻引擎的更新。

例如,我的卡通渲染管线,是从UE4.27升到UE5的。在这个过程中我就深刻感觉到了这么做的好处,我的管线升级过程十分简单顺畅,一个人只需要几天时间就可以顺利将UE4.27的卡通渲染管线升级到UE5.0。

第二,由于我的卡通渲染管线需要兼容UE5的默认PBR管线和卡通渲染需求,所以在设计上有一定冗余。如果在风格确定的时候,这些冗余是可以节省掉的。

第三,这部分工作其实应该属于图形工程师的能力范畴,我只是顺便绘一点图形,TA的话其实需要将主要精力放在这次分享的第一部分即可。

第一步,我们需要拓展出我们需要的材质ID模型,因为我们需要兼容Unreal Engine 5自身的渲染管线,所以我们在原来基础上进行拓展,而不是覆盖原来的材质模型。

材质ID有很多作用,第一可以在渲染管线当中决定一些条件分支和mission short commander的生成逻辑;第二,材质ID会被塞到basepass中填充shadingmodel ID,这一步网上有很多教程,包括在我的虚幻5渲染编程知乎专栏里也有很多相关的文章,这里就不作赘述了。

完成了shadingmodel的拓展以后,我们需要拓展一些MaterialPin,这样才能将材质中的一些数据传到我们的渲染管线中。同时,我们需要给这些Pin一些默认值,例如图中这个pin默认值就是0.5,所以我们也需要给我们的MaterialPin一个合理的默认值。

在准备完成Material的数据和shadingmodel以后,我们就来到了卡通渲染管线需要改写的第一个部分,Base Pass,这里我们需要对Base Pass进行一些改造,例如写入卡通着色模型的材质ID,和对base color的InversePP等操作。

完成Base Pass以后,我们的卡通模型如果需要各向异性的话,需要让改材质的渲染Anisotropy Pass开启,以此来得到tangent buffer。

对于虚幻的Base Pass而言,已经被原有的光照模型占满了数据,所以我们的卡通渲染管线需要增加一个ToonGBuffer,来存储卡通渲染管线需要的GBuffer数据。我的卡通渲染管线当中,ToonGBuffer由四张GBuffer组成:ToonDataA存储世界模型的顶点法线,ToonDataB在R通道存储阴影偏移、G通道存储Kuwakara权重、B通道存储CustomShadowMap、A通道存储边缘检测的勾边阈值。

ToonDataC存储线条的颜色和线条的Mask遮罩,ToonDataD,RGB三个通道存储勾线ID、Alpha通道存储线条的粗细。

当然我想再次强调的是,因为我的渲染管线目前是个全兼容的管线,所以从设计上就有一定的冗余,如果我们的项目风格确定或者精度要求不是那么高,可以将这个ToonGBuffer的很多数据节省。

我们的ToonGBuffer是使用MRT技术渲染,UE里面想要新增一个Material Mesh drawlist需要做一下几个事情:第一,新增一个MeshProcesser实现add batch mesh等操作;第二,新增一个mesh pass type,即对应的meshdraw command list,并且要把dynamic draw command和static draw command塞进自己新的mesh draw commandlist当中;第三,新增Mesh Material Shader、Vertex Shader和Pixel Shader;第四,新增绘制一个Pass就可以了。

完成了卡通渲染的基础绘制准备后,我们就可以着手处理这些数据了。

第一个需要处理的是卡通勾边的颜色,我们需要对卡通勾边颜色里面RT像素进行外扩,以此保证最边缘线条能正确染色。

下一步就是进行NormalDepth的边缘检测,这里使用第一部分讲到的Sobel或者Laplacian算子进行边缘检测即可。

接下来还需要完成ID边缘检测,就是我们给每个材质的不同部分上了不同的ID,然后去检测这些ID的边缘,就可以得到ID边缘检测线。

得到了NormalDepth检测线和ID边缘线之后,就可以把这两部分的线条合并到BaseColor上。

接下来就是绘制ToonLumenIndirectDiffusePass,我们只需要在Lumen Pass的结尾对Lumen的结果进行降频和一些操作以后,就可以得到我们想要的ToonLumen的结果。

再下一步,我们来处理SelfShadow和VirtualShadowMap的合并,我们只需要在灯光(lighting pass)的最后把shadowmap提取出来,然后再和SelfShadow进行合并即可。

这是我合并的位置,可以看到它就是在Lighting的最后面。

接下来这一步是ShadowColorPass,我们在多光源计算完成以后,就可以得到一个合并后的全局ShadowMask,这个合并后的ShadowMask是由SelfShadow、VirtualShadowMap等全部合并在一起的阴影遮罩信息。我们可以根据这个遮罩信息对这些遮罩的区域进行阴影染色。

完成上述各个Pass之后,我们就可以进行水彩滤镜的过滤,Kuwakara Pass。

最后,我们还可以将Outlinecolor合并到SceneColor上,以减少之前各个Pass对Outline结构上的破坏。

接着,整个渲染流程走Unreal Engine默认的渲染管线即可。以上就是今天这次分享的全部内容。

最后要特别感谢《ob电竞》的开发者对我的帮助,我在B站看到他们的将游戏模型,不管是设计上还是制作上都是一流的。不管做卡通渲染还是写实渲染,一开始的渲染资源如果能到位的话,其实是能够极大加快研究进度和速度的。

如果资源都非常丑,那你写再好的渲染管线都是白搭,而且,很多渲染问题只有在正确的资源基础上才能显现。

其次要感谢B站虚拟主播尹音音不吃鱼和B站用户汪呜汪呜汪他们的模型。

因为整个卡通渲染管线有非常多的细节,所以这次也很难做到把所有细节一一阐述,并且我刚才分享的卡通渲染管线是2.0版本,目前我最新开发的进度已经到了3.0,它在2.0的管线基础上做了很多算法优化,也追加了很多功能,所以想要深入了解技术细节的朋友,可以浏览我发布到知乎上的这篇记录文档获取更多信息。

如若转载,请注明出处:http://www.ashkeling.com/2022/12/504850