4月24日,Epic Games 工程师李文磊参加Unreal Open Day活动中并进行了演讲。作为日常解决各个开发者问题,特别是美术问题的资深人士,其总结分享并回答了开发者遇到的十个典型美术问题。

以下是演讲内容,VRZINC进行了删减和整理:

透明乱序的问题

李文磊:透明的问题非常多,其中一个比较典型的是透明论序的问题。它的表现大家都应该遇到过,就是透明物体和透明物体之间它的渲染有的时候在前,有的时候在后,根据移动会不断的变化。

产生的原因很简单,透明物体它不会被渲染到深度,大家可以看到左边的三个灰色的模型是不透明的物体,右边的三个彩色的是透明的物体,右边是一个深度通道,透明物体是不会渲染到深度通道,没有深度就无法正确的排序,特别是像素级别的正确的排序。

UE4提供了一些简单的方式,可以在物体的层级进行强制的排序,比如左边是一个正常的排序方式,红的最前面,绿的中间,黄的最后面,在UE4可以简单的设置它的排序的方式,我可以强制把黄色的渲染到了最前面,绿色的中间,红色的后面。

但很多时候并没有那么简单,像素级别我们很难做这样的排序和操作。这个烟雾就是一个例子,是一片面,半片面在后面,半片面在前面。这时我们就需要用一种比较迂回的方式,首先我澄清透明的排序问题基本上是没有什么解决方法,我们只能通过一些迂回的方式去解决。

以水面上有雾效为例,这个经常可以遇到,我们最简单的方式就是让烟雾在水面以下不化。

首先先把这些雾渲染到所有的东西前面,这些面片就代表这些烟雾,平面就是水面,做了一个简单的shader,可以简单在这shader里面提取拿到水面的高度。

然后在shader中我们在交叉的地方做一些渐变,这样烟效果会更好一些,来达到一个模拟解决排序的问题,这是最终应用到实例上的演示。

很复杂的一些透明模型怎么样来做前后关系?一个简单的方式,我们可以用透明物体的customdepth和另一个比较来决定。

同样的道理我们把球里面用shader变成黑色,球外面的变成白色,通过这样一个mask做各种各样的效果。

实际可以应用到一个透明的人,左边的是穿透以后,面部会被画出来非常难看,我们可以用刚才的方式做成右边的那样。

透明材质渲染开销问题

透明的另外一个问题就是效率,就是重复采样效率的渲染开销。一个是shader的复杂度,还有一个是数量,数量越多越复杂。从这两个原因我们就可以很容易去判断解决方法,减少shader的复杂度以及减少重叠的几率。

半透明和光照模式的相差还是非常大的,所以对于一些蒸汽不需要光照的我们就让它自发光。此外通过提供的自动功能,能够把自动判断贴图的边缘,把面片尽量的包裹贴图的mask,这样使得面片的面积最小化,这样的作用就是让重叠的几率减小,可以减少开销。

另一种情况是在某些画面比较模糊或者说这种效果我们不需要太高分辨率的时候,可以把透明物体的分辨率降低。这里有一个例子,我把它降成一半,它的透明的开销大大的降低,但是画面来看只是稍微模糊了一点,还能够接受,有些时候你们可以用这样的方式大大减少透明物的开销。

自发光粒子特效和背景混合问题

透明物体和背景在Unreal中有多种的融合方式。首先是一个Additive的效果,经常用于一些增量的效果,比如闪电等等。但是我们经常遇到就是背景非常亮的情况,这些贴图的细节就会丢失,就像右边白色的地方,基本上没有细节了。黑色的部分与原来的贴图是非常接近的,灰色的地方就是一半一半。

半透明的混合模式可以很好的把需要透明的地方展现出来,但是半透明对于阿尔法通道的要求非常高,通道做得不好很有可能不是你需要的效果。此外白色的地方可能会产生黑边,黑色背景也可能会产生白边,就对通道的要求非常高。所以这里扣出来的效果不是很好,跟我们想象的不太一样。

还有一种是Alpha Composite混合模式,无非就是用透明通道来控制背景被add的量,它的好处是什么?大家可以看到,在亮度相对add是可以把握一定的细节,暗部也可以完全保留,另外就是不需要精确的阿尔法通道,只要黑白灰,用它的RGB的通道就可以产生非常好的效果。对于特效美术来说,有些时候可以考虑去使用这种混合模式来进行制作。

漏光问题

漏光产生的原因有很多。静态的很简单,大部分是精度不够高等等。对于动态漏光,这边举几个实例:

1.影子没有贴合投射物
2.距离远了以后没有阴影
3.动态的角色和静态的场景灯光不匹配
4.间接光的漏光,反射也是间接光的一种。

动态影子没有贴合投射物,主要是三个原因,一个是影子本身的精度低了,第二个是影子的投射面积太大了,第三是影子偏移太大。

具体解决方法,首先我们先把引擎里面影子的禁止调到最高,UI里就可以找到,有一个叫做SG的group,打成4,是电音品质,所有下面的参数就会用默认的这套参数,应用了这套参数以后我们还没有达到很好的效果,特别是对于影视公司制作非常高的品质。

接下来我们针对不同的投影的性质做一些调整来提高应用本身的精度,对于CSM Shadow主要就是阴影的距离和阴影的层级数。

大家可以看到默认情况下调整这两个参数以后的对比,里面还有一个perpixel Shadow,这个是不一样的,需要这些参数来进行调整,第一个主要是用来增加影子的精度的,它的意思就是Unreal里面对于动态影子有一个最小分辨率和最大分辨率,根据距离引擎会自动的调某一级的分辨率来显示。比如我在200米引擎调用256的精度,这个设高了以后引擎可以调用更高的精度,最小分辨率设高,就是强制使用更高的分辨率。

对于解决面积不同的光源有不同的方式。对于探照灯、聚光灯它的张角直接影响投射的面积,张角越小投射面积越小,所以同样的精度可以大大提高影子的精度。

对于点光源主要是一个距离,距离越近投射面积越小,精度越高。

除此之外还有一些其他的方式,比如说阴影的一些滤镜,类似于一些锐化的功能。

接下是刚才提到的距离拉远以后没有影子,这个默认情况下是引擎的一种优化行为,在很远的距离不需要渲染。但是有的时候会产生一个影射级别的渲染 需要很远的距离,所以在默认精度不够的情况我们可以通过一些方式调整。

首先影子精度设为影视级,这些参数是跟距离有关的,Shadow和vedio就是定位多小的距离可以被显示,比如我设成64,Shadow小于64的精度我就不显示了,所以需要更远的地方显示Shadow,就把数字设成16或者更小。

对于不同的应用类型我们用不同的方式来解决远距离无阴影的问题,这几个参数刚才解释了一下,我就不具体讲了,大家看一下就知道了。

另外一个经常碰到的就是动态物体和静态物体的用静态灯光不能很好的衔接的问题,参照上述方法也可以解决。

间接光漏光;反射漏光

反射也是间接光的一种。大家可以看到,这是一个场景,外面是一个太阳和一个天空,室内完全照不到太阳光,太阳没有直接照到这个物体上,自身和房间之间物体和房间之间都是没有任何的光线的遮蔽的,我们需要得到这样的效果。左边是自身的光线遮蔽,右边是这个物体和场景之间的光线的遮蔽,产生的原因很简单,就是引擎默认情况下为了效率问题我们是不会开启间接光的遮蔽。

在最新的引擎版本中我们就场景物体加了一个Distance Field Windirect Shadow,产生一个间接光的遮蔽。通过材质来把物体增加自阴影,间接光的遮蔽,对所有的光产生屏蔽,放在太阳光下不会产生作用,所以产生的结果不会所有的地方看上去都是灰灰的,而是只会在一些阴影中产生。

我们可以动态地在引擎里调整一些材质和阴影参数,对于角色来说也类似于场景的Distance Shadow去做,需要一些动态的投射物,比如简单的投射物。这个是非常漂亮的阴影,特别是在阴影的地方,有这些阴影人物节像占在地上一样,看上去很明显。

图像模糊和残影问题

残影也是非常常见的问题,因为在UE中我们默认使用的是一种TA的方式,它基本上就是在渲染的时候把每个做一些小的抖动,最后进行合成,细节量特别大就会产生瑕疵。贴图的Mipmap是为了自动的减少分辨率而产生贴图级别的抗拒,但是有些细节量太大也可能会产生这些问题。

知道原因就很简单了,我们可以把反向贴图强度适当的降低,Mipmap可以用不同的算法,比如用一些比较模糊的算法来减少。比如SSR反射以及SSAO这些其实在TA中都会加剧这种问题,所以一旦遇到这些问题,大家可以一个一个的分析,主要是我提到的前面三个,其他的大家可以察看一下。

动画模型的法线问题

模型动画的法线问题,特别是做面部动画的时候,因为法线在没有闭眼是朝上的,闭眼以后还是朝上的,这是引擎默认的行为。另外特别是在做前期动画的时候,如果骨骼带动了那些点,因为骨骼是移动的,而不是旋转的,它没有一个旋转的信息传达给法线,所以这些法线是不知道骨骼是旋转的,直接骨骼往下拉就会产生这些问题。

这是原理,左边是mipmap,直接移动骨骼,蓝色的法线是会更新的。右边是UE默认情况是不动的,所以产生了变黑的问题。

目前解决方法比较简单的就是我们可以使用SkinCache,把信息放到Skincache里面,默认是不打开的。我们引擎不是所有的功能都非常完善,大家提问题也是给了改进的机会,我相信以后放再一个UI里勾一个标签就可以了,目前还是需要做一些这样的工作。

当然使用一些其他方式也是可以的。比如用Morph Target,还有Alembic点缓存形式。还有做动画的时候,尽量的旋转,而不是直接拖拉骨骼。在做角色动画的时候因为大部分只是旋转而已,但是面部动画很多时候为了方便是拖动而已。

动画在某些时刻与外部软件不一样

接下来的问题是动画在外部软件里跟看到的不一样,比如max动画导入UE4里面不一样,尤其在动画速度非常快的时候,有几祯动画有一个非常大幅度的动作。

原因这里提到两点,一个就是差值与不差值会产生失祯,第二个就是关键数据丢失。

这边我在Max做一个动画,导入引擎以后所有的动画都以四分之一的速度播放,我们可以看到一个引擎的增速非常快,而且是不固定的。根据你机器不同,播放的时候会根据每个之间有一个差值,这个球是绕着这个转的,但是有一点偏。

解决这个问题知道原因以后很简单。首先我们不要差值,把这个差值关掉,就跟播放器里面的一样。对于输出动画的话,我可以简单的用固定的输出,比如30祯的Frame也会产生最终的不差值的效果,可以解决这个问题。

关键信息丢失的情况是这样。左边是Max里面60祯每秒,每个基数上移了一个单位,所以大家可以看到,转的时候还在跳动,导入UE这些就没有了,因为我用了30祯每秒的祯率,所有的基数祯就被丢掉了。比如我们在外部有更高的祯率,导入引擎用30祯,我们这些动画就要在30祯可以被采样的地方。

因此我们在导入处需要注意,导入的时候不要再采样一次,这些都是有固定的标签的,但是往往会被大家忽略导致一些问题。

反射球的消耗问题

我们用两种反射蓝,反射球的消耗就是反射球的边缘半透明的地方,我们可以理解成跟透明物体一样,全部不透明的地方其实消耗是固定的。为了证实这一点我们将很大的反射球笼罩到很小的地面上,这块地面所有的地方都是百分之百的反射,但是我放了28个反射球,只有一个反射球的开销,因为所有的反射都是全部透明,0.16的毫秒。如果我们将这28个移到旁边的地方,让半透明的地方进行混合,它就会大大增加开销。

理解这个原理以后这里有一个模拟的图大家可以知道这些反射球之间怎么样越亮的地方是开销越大的。

如何减少开销?减少个数有时候硬度不够高,所以怎么样避免边缘的地方的重叠?例如使用方形边缘的地方就比较容易避免重叠,因为方形容易控制边界,而圆形的比较难。此外我们可以先放一个大的,再放几个小的,总之知道原理以后可以根据实际情况避免。

动画序列输出问题

动画序列的输出,是现在很多的开发者特别是做影射遇到的一个问题。最终输出的画面需要合成,需要输出不同序列的祯,结果最终画面是是其他通道未抗锯齿的。

这个主要是因为左边的通道做了TAA,右边的通道默认的输出是在TAA之前的,所以导致输出Gbuffer是会抖动的。因为TAA之前Gbuffer都做的抖动,TAA之前的数据和之后的数据我们就合不到一块了。

解决方法就是我们将Gbuffer做一下TAA进行输出,使用后期的材质可以让Gbuffer做Tonemapping。

此外这些PP的材质我们可以应用到两个地方。其实引擎自带了一些Buffer,大家可以去那边找,新的版本里这些都已经做了TAA,之前是没有的,所以这些大家可以去参考一下甚至可以直接提出来用,输出的时候可以直接渲染而不会产生抖动,可以更好的合成。