11月16日,主题为“创新思,享未来”的中国Unity线上技术大会正式拉开帷幕。在19日晚的游戏专场中,鹰角网络CEO黄一峰及《明日方舟》制作人海猫以“初创团队的曲折开发前行之路:《明日方舟》中3D和2D结合方案”为主题发表了演讲。
昨天我们已经分享了海猫讲述《明日方舟》从0到1的故事,本文紧接上半部分,鹰角网络CEO黄一峰详述了《明日方舟》3D和2D的结合方案,并总结了游戏在开发过程中遇到的一系列问题。
以下为演讲实录(有节选):
大家好!我是黄一峰,是《明日方舟》在前期开发当中的技术负责人,也是上海鹰角网络的联合创始人之一。
刚刚我的同事海猫从他的视角讲述了《明日方舟》从0到1的故事,接下来我将从我的视角,从更偏技术的角度来讲述为什么我们会选择3D和2D结合的这样一个画面表现方案,我们做出了哪些工程和技术上的取舍,我会尽量以合适的力度讲解,希望能给类似选型的项目一些提示,避免重复踩坑。
接下来是一个大致的大纲。首先我会介绍我们为什么会选择3D和2D结合的方案?而这个角度可能和刚刚海猫讲的不太一样。第二,我会说一下我们方案的具体内容,包括场景和单位两个维度。第三,我会详细说明我们在开发过程中遇到的三个我们认为比较有典型意义的问题,希望能够给大家带来一些提示。
为什么是“3D+2D”?
首先,相信不是所有在座的同学都了解或者玩过《明日方舟》,所以说我会首先播放一段15秒的战斗视频,这段视频来自我们今年夏季的活动“密林悍将归来”。
大家可以看到整个画面的风格和这感觉是偏于2D画面的,无论是角色、特效、弹道以及敌人包括视角是固定的,是一个2D为主的游戏。但是比较特别的是我们在场景的方案上选用了3D的模型和偏写实的渲染。我们整个场景的建模、打光、渲染都是用偏写实化的方式实现的,包括制作和渲染都是用标准的PBR为工作流。
这个结合相对比较罕见,也是今天我们想描述的重点。整个战斗画面的摄像机是非正焦的,就是我们传统所说的透视相机,单位和场景的空间关系实际上是比较特别的,接下来我也会在遇到问题的环节详细地进行描述。
为什么我们会使用3D和2D结合这样一个方案呢?我这边认为比较简单,3D写实风格可以较好地匹配《明日方舟》相对严肃的世界观和我们比较特别的美术风格。而2D的Q版小人可以较好地展现角色的魅力,避免top-down视角的局限性。
大家可以回忆一下top-down的游戏,尤其是像我们这种基本是从俯视角看战斗场景的游戏,它的角色绝大部分都被它的头顶给遮住,所以你看到角色实际上不太能够看到它的动画细节包括身上的服饰细节,你看到的大部分都是个大头。我们为了避免这个问题,决定使用2D Q版小人。
当然最重要的问题是我们作为一个初创团队,我们希望能够尽量地降低风险,所以说我们希望能选择市面上相对比较成熟的2D动画方案,去避免当时我们认为还没有特别好参考的3D小人的方案。当然,现在有很多友商已经做得很好了。
同时,我们想实践3D开发的流程,积累一些3D开发的管线和工作经验,所以我们最后选择了这样一个特殊的选型。现在这个选型有很多现实因素,但是最后实践下来我们认为它是别具一番风味的,是相当特别的。
好,这张概念刚刚展示过,实际上是由海猫在立项初期创作的一张图,这张图创作的时候我们一行代码都还没有开始写。这张图是为了给整个团队的成员一个统一的认识,我们这个游戏究竟会长成什么样。我们可以从繁琐或者抽象的策划案中具体地落到到这样一个视觉的概念上。
可以看到这张图中的很多要素和我们现在的选型是一致的,包括3D的场景、2D的小人这里面模糊掉了,包括整个摄像机的透视的方向其实和我们现在的选型都高度相似的,这也是我们比较幸运的。
大家可以看到比较特别的是左下角有个小人是垂直站在场景上的,这个垂直站在场景上的行为符合场景和角色的关系,但是却不符合我们这样一个视角的比较好看的关系,像纸片。这个问题也是为之后的开发埋下了一些伏笔。
这张图是我们早期的一些设计和参考图。左边这张图是早期的Q版小人,我们一共有8个小人,而Q版小人上我们的选型和当时主流的头身比小的主题不同,我们选择了相对它的腿和身子会更长一些,头身比相对更大一些,而这个方向我们融合了一些美式Q版小人的风格。
右边这张参考图是ArtStation一位艺术家的作品,但是我们看到,我们认为它非常符合我们对3D场景包括对写实场景的一个审美。
为什么?首先它的作画风格是偏风格化的写实,其次,它整个构图是以网格为核心单元的,它并非是以我们最后选用的四边形或者是正方形的往格,而是使用了正六边形的网格。但是总体来说这样一个视觉感觉是非常符合我们预期的,所以我们也把它作为我们创作的参考。
明日方舟的视觉方案
然后是视觉方案的一个详细的描述。我们分为两部分。
场景
第一部分是场景,场景包括3D的网格状地图,地图周边的那些摆件,地图背后的远景,以及地图上可交付的或者是一些装饰性的小物件,所有3D的模型都是使用标准的PBR工作流制作的,在美术同学的雕刻上会特意做一些画面化的风格化的雕刻,但不会追求较低面线的方式实现一个卡通的效果,我们的面数相对还是比较高的。
第二,在贴图绘制上我们会做一些功能化的尝试,但总体来说我们并非做一个比较常见的日式RPG或者是类似游戏的卡通化的风格,我们希望用更写实的风格做到一些差异化。
在Shader上,我们采用的光照模型是基于Unity Standard Shader修改得到的PBR Shader。之所以不使用Unity默认的Standard Shader是因为我们对它进行了针对性的优化,同时还添加了一些我们所需要的功能。
单位
第二部分,单位。单位包括角色、敌人、第三方单位以及很多可交户的2D物件等等。所有的2D动画我们都是使用Spine骨骼动画软件进行制作的。在着色上我们使用了经过特殊处理的一个Unlit的Shader,它不会受到任何的动态光照和动态阴影的影响,之所以选择这样的方式并非完全源于我们的技术限制,同时也是因为我们经过实验发现如果我们做的更加激进一点的话,整个场景的角色和敌人的辨识度会变得更差,玩家在玩的时候可能不太能分辨出来哪些是他需要关注的东西。
这部分也是接下来我们进一步推进画面表现所需要处理的问题,如何平衡game play和视觉效果。
我们场景的视觉方案会有这三张图作为最直观的展现。接下来三张图都是我们实际的渲染图。第一张图刚才大家也看到了密林这样的场景,可以看到场景细节和地块。
而第二个场景是一个熔岩洞窟的场景。第三图是我们故事发生的主要地点龙门夜晚的场景。大家可以看到这三张图都是在建模式上面我们使用了高度抽象化的以格点为核心的构架的方式,整体的光照细节、模型贴图细节以及一些周边的装饰系,像一些建筑物和一些管道都是非常写实的。
我们的整个视觉方案如下,首先我们的场景是使用Unity PBR渲染模型的,在Shader上我们采用的是略微优化和修改后的BRDF2,在性能上进行了一些调优,并且添加了我们可着色的阴影,在PBR Shader层面实现了高度物,也适配我们某些场景一些特殊的效果。
在Lightmap上面,我们在Shader里面对Lightmap的编码和解码进行了一些特殊处理,使它在所有平台上能达到一致性更好的效果。这点我之后会在问题阶段详细描述它的细节。
我们每个场景都会使用Unity静态烘焙的方案,会烘出两张图,一张Lightmap,一张Shadowmap,所以说会有比较细腻的光影和阴影的表现。在打光方案上会有一盏混合动态光,这相当于我们在运行时只有一盏动态光会配很多静态烘焙光的方式来做打光方案,并且在特殊的情况下我们会添加更多的探针做一些静态的反射,为某些场景添加一些必要的视觉效果。
除了整个模型之外,我们在场景上会添加一些Unity提供的添加方案所添加的产生的滤镜特效和模型特效。大家可以看到刚刚的视频中是有一个瀑布的,就是用特效实现的,会增加一些风味。
接下来是角色的视觉方案。我们绝大部分的角色在右边这张图中可以看到,我们的SPINE工程当中实际是用了正反两个面做每个角色的。
为什么这么做?我们参考了类似的游戏,很多2D游戏实际上只有正面一面,但是在动作上会刻意地避开像冲刺、穿刺这样方向性很强的动作,而选择画面中类似德克萨斯使用的半圆的圆弧斩的方式,这样能模糊方向性,但是这样的话会极大地限制我们美术工作者对于动作的创作包括武器的创作,所以在权衡之后,我们还是选择了把绝大部分角色做两面,做一个动作的拆分,而其他的角色,不太重要的第三方单位,我们就会使用单面的方式。
而整个这样一个方式可以看到我们在正面和反面的转变过程中我们是做了一个类似于纸片人的转变的动画。这个动画的灵感来自任天堂一款著名的游戏系列《纸片马里奥》里一个类似的画面,对比看一下,我们游戏的效果和这个是比较接近的,可以看到灵感来源是这里。
《明日方舟》开发过程中遇到的3大问题
好,接下来我会详细地描述几个在开发过程中遇到的问题。这些问题其实都是源自于我们融合3D场景2D角色做一个结合。因为这个方案比较少见,所以遇到问题也比较少见,希望给大家带来一些提示。
空间关系错误
首先第一个是空间关系错误,刚刚我也已经提到过,我们场景和摄像机,摄像机并不是垂直于场景,它实际上和场景的X、Y平面呈一个60度夹角的关系,而角色,因为我们要展现角色,肯定是对着场景,对着摄像机,它是垂直于场景的方向的,通过简单的几个关系可以得出场景的X、Y平面和角色的夹角实际上是30度,也就是说我们的角色并不是站在场景上,而是斜躺在场景上,或者说我们是躺在场景上,它实际上是一个斜插的东西,它的结果可能是极易穿模,这里说可能不太直观,大家可以看一下这张图。
最左边的图就是我刚刚描述的合理的空间关系,大家由于角色后面的地块它的模型非常高,所以说它的头是插在模型当中的,出现了一个穿模。
为了解决这个问题,有的同学可能会说把这个角色给调整到垂直场景不就解决问题了吗?那么我们最右边这张图就是把角色调整到垂直于场景,这个时候它和场景的关系对了,深度关系也对了,但是我们看起来非常奇怪,像纸片人。
这种情况下,如果我们调整一下摄像机的角度,比如像中间这张图一样,调整到能够正常地平视这个角色,那整个空间关系看起来是正确的,只是这样一来我们的游戏是没法玩的,因为摄像机只能以这个角度去看的话玩家是没法操作的。
为了解决这个问题,我们最后达到的效果是希望像左边这样,但是能解决穿模的问题。这其中曾经经历了一个错误的思路,也就是说我们渲染的时候关闭了一些test,同时Z-write也不大,我们把整个场景中的模型、摆件和角色都通过一个深度混排的方式,我们手动对它排序,类似于传统2D游戏的处理方式,我们手动排序之后手动去归置,这样确实能达到相对比较正确的结果,但是会带来两个主要的问题。
第一个就是我们会破坏这个合批,打破它静态的一个batching,为什么?因为我们整个场景是一个静态,包括它的光照贴图和阴影贴图,我们在渲染的时候可以用相当低的DrawCall做到所有的场景,绝大部分场景的DrawCall都在个位数。如果需要混排,需要打乱角色前、角色后的顺序,我们会增加非常多的DrawCall。
其次,我们会有一些小摆件,比如场景上的一些树木是跨两个格点的,这些小摆件我们大家对它进行排序就很难知道它到底在哪一层。
最后我们解决的思路是让角色需要看起来垂直于摄像机,但实际上它是垂直于场景的。这个听起来有点抽象,待会儿我们会展示图片。那么我们解决方案实际上是把深度进行一个调整,在Unity当中百位或者是在场景当中的位置和着色系它的角色都是垂直于摄像机的,也就是躺在场景上的。而在Vertex Shader最后我们会把深度从刚刚那个空间变换到垂直于场景的空间,这样以来对于深度测试和深度写入来说就好像角色是垂直于场景上的。
左边这张图就是一个正常的透视关系,也就是说角色是躺在场景上,会穿模的情况,实际上最后做深度测试的时候,我会把它角色的深度从刚刚左边这个图变换到右边这个深度空间当中,你可以看到整个角色现在是垂直于场景,并且被抽起来的,它变得更拉长了。
为什么?这张草稿能说明问题,首先这个camera是我们的视角方向,它和场景是有一个夹角的,角色垂直于这个camera的方向,它的夹角和场景夹角是30度,这个时候我们可以做这样的一个三角形,而这个三角形的斜边就是我们箭头指的方向,就是我们在深度测试的时候实际上所在的位置,可以发现这上面线上的每个点和原本角色上的每个点是一一对应的。通过几个简单的关系,这是一个直角的三角体,而且是一个30度夹角的,斜边和对边的比例是1:2,所以说实际上这个边的长度是它的2倍。
这里我不会深入一些处理的细节,因为我相信这个比较容易的,通过大家的计算都可以得到。只需要说一点,做这个变换只需要在Vertex Shader在最后做一个深度变换可以,这需要注意的是这个时候Vertex Shader最后的位置它实际上是处于透视除法之间骑士空间当中的,所以此时如果要做一个Z轴的变化在骑士空间中需要考虑到第四位W参与运算,这里是个简单的代码。
好,那么问题解决了吗?其实并没有。我们在整个视觉方案当中还遇到了很多别的问题,刚刚提到的是其中最重要的点而已。
这里图中说明的是另外一个问题,我们的干员杜宾的攻击方式比较特别,她的攻击方式是往下挥鞭的时候纵深会特别长,所以说会直接穿到这个模型里面,如果我们把它的角色深度拉伸到垂直于场景,它依然会穿进去,所以此时我们做了一个特殊处理,当它的高度大于等于0的部分我们使用刚刚说的处理方式,而高度小的部分我们使用另外一套处理方式,使这个鞭子被旋转到平行于这个场景的X、Y平面,这样才能在同样正确并且一致的条件下正确地绘制出来。
除了图纸之外,还有空中,它其实处理方式也是不太一样的。所以,这里是有很多细节的,如果大家感兴趣,接下来我们再详细探究。
Lightmap不一致
第二个问题是Lightmap不一致的问题,而这个问题很多友商包括很多网友在网上也提到过,我也看到很多文章,他们会发现编辑器在实机,在移动平台上可能最后Lightmap的结果都不太一样,但是我们调研得到的结果是如果抛开Unity版本谈这个问题是没什么意义的,因为每个Unity版本对于Lightmap编码解码的细节都有一定的调整。所以我们这里所有的结论是2017 LTS版本上。
这个问题的原因是因为光照贴图存储的并不是颜色,它和通常的图是不一样的,它存储的是一个间接光的,范围是在0和1之外,我们无法用一个常规的LDR的贴图格式把它存下来,它原本是一些APBR的信息。所以说我们需要把它进行编码,才能重造一些LBR的贴图格式。那么这个编码的方式在不同平台上是不一样的,这个原因也导致了最后不同平台上的表现结果不一致。
大家可以看这两张图,可能不太容易看清,在明暗交界的地方实际上那个阴影会泛蓝紫,这个蓝紫就是阴影的细节,在左边这张图是被吞掉了,这个细节是我们需要处理的。
这里我不会详细地描述RGBM和WLDR编码,大家如果感兴趣,网上稍微查一查应该比较容易了解。笼统来说RGBM会将额外的信息存储到第四个通道alpha当中,而WLDR的方式比较暴力,它直接把RGB的原始信息除以2乘下来,所以它丢失的信息比较多。
接下来是一个具体的问题,当Lightmap光照贴图被烘焙出来之后,它的TextureType默认是设置到Lightmap,那么这种情况下Unity就会根据当前所选择的平台来编码这个Lightmap,影响采样的结果。如果在PC上我们把Lightmap Quality成为High的话,它存储的是一个原生的HDR的信息,如果把它作为Normal Quality,它会把HDR信息通过RGBM进行编码,存在一张LDR的贴图里面去,如果是iOS的安卓,也就是移动平台,它会比较暴力地把所有这样的信息都给编码成double LDR的方式。而double LDR的方式下,alpha会直接丢弃,这才是我们想通过做一些操作把一些信息存到alpha通道当中,你读不出来的是读不出来的,读出来的是个常量。
接下来是我们做测试的一些细节。这些细节今天我不会展开,大家如果感兴趣可以会后通过PPT的方式查看。
结论来说,如果我们继续保持Unity的默认选择 TextureType-Lightmap,它就已经存储为double DR,LDR的格式了,那double DR的格式只能存储0到2的亮度范围,亮度2以上的范围都被截断和丢失了,为了我们达到更好的效果,在不同的平台上使用RGBM编码,我们必须采取一个default的格式。
具体的方案是我们把烘焙出来的光照贴图的TextureType从Lightmap改为default,同时我们把RGBM的alpha通道这个信息从Lightmap当中抽离出来,放入shadowmask的green chanel,这个原因是因为shadowmask实际上使用了它的red chanel通道,其他通道是没有被使用的。我们为了让两张贴图都可以不存alpha通道,最大化地利用内存空间,我们把alpha做了一个trick的操作,这样以来我们就可以用任意我们喜欢的压缩 格式,包括STC、ETC2和PVRTC对这两张shadowmask和lightmap进行处理。
我们会修改GBR的shader,用RGBM来解码这个lightmap,并使用从shadowmask中读取的信息,也就是刚才说的green chanel的信息来辅助这个解码。在PC上当然也是需要相应地修改兼容,使所有平台上达到一致的效果。
最后这张图就是我们经过这样一个处理方式之后在所有平台上包括编辑器,包括安卓、iOS都达到的细节的光照贴图的效果。
恰当地融合3D+2D的渲染表现
好,最后一点,我们如何融合3D场景2D角色的渲染表现?这个问题实际上是一个比较复杂的问题,我们现在也在探索。我们总结了几点:
首先第一个是我们会对每个主题的场景定义一套作用于所有单位、所有角色的tint color,使其着色和场景氛围更接近。也就是说我们每个场景里面角色的渲染其实略微有些区别,而这个区别的tint color是由我们美术工作人员做配置的;第二点,通过美术处理能够调整3D场景、2D角色,包括整个特效的整体氛围和色调,使它更加融洽。
除了刚才的tint color之外,我们也会提供一些后处理的选项,包括color grading和Bloom等等的让美术工作人员能通过他们的努力使我们整个场景3D和2D更加协调。未来我们也会探索更多的方式融合这一特殊的画面表现。
总结
最后做一个总结。《明日方舟》3D和2D结合的方案我们首先使用了PBR的渲染和工作流去处理这个3D模型的场景。然后对于2D的角色,我们使用Spinn的骨骼化软件,使用unlit的shader去处理的。
第二点,为了解决3D场景、2D场景空间透视关系错误的不协调的问题,我们对shader进行修改,在深度上做了一个变换,使它看起来和实际深度当中的表现是不一致的。
第三点,我们自定义了Lightmap的编码和解码流程,使其在不同的平台上都能得到一个一致且更好的效果。
第四点,我们使用了一些定制化的tint color和一些后处理的选项,让3D场景和2D角色能融合得更好。
最后,我们提供了这么多的选项是让我们的美术工作人,也就是说我们的艺术家们来做最后的处理的。因为我相信技术人员只是提供工具,最后的整个画面效果肯定还是需要以更加感性的认识去调整的。
今天的分享就到此结束。谢谢大家。再见。
元宇宙数字产业服务平台
下载「陀螺科技」APP,获取前沿深度元宇宙讯息
110777025(手游交流群)
108587679(求职招聘群)
228523944(手游运营群)
128609517(手游发行群)