您需要 登录 才可以下载或查看,没有账号?注册
x
此篇文章翻译自游戏《INSIDE》的开发商Playdead在GDC 2016上所做的演讲: Low Complexity, High Fidelity: The Rendering of INSIDE​youtu.be这个演讲详细讲解了INSIDE里使用的渲染技术和技巧,非常值得一看。
内容: - 雾效与体积渲染
- HDR辉光
- 色带与抖动
- 投影贴花
- 水体渲染
- 特效解析(障眼法)
首先开发者列出了目标:
艺术风格要尽可能简洁。 2.5D卷轴游戏,固定视角。这么做是为了保证玩家看到的内容和开发者看到的内容一致,可以精确调试每个像素。 团队规模较小,艺术家没有什么技术背景。 保证屏幕上不会出现分散注意力的东西,保证玩家的不会受到主线之外的干扰。 技术指标: 在当前(2016年)游戏主机上,达到1080p@60帧的效果,这里特指Xbox One。 定制化的Unity 5.0.x(开发者拿到了Unity源码,因为需要修改渲染pass,不得不对源码动刀)。 使用光照预通(Light Prepass)渲染,对应Unity里的旧版延迟渲染(Legacy deferred)选项。 定制化的光照预通渲染管线: 雾效与体积渲染
雾效对INSIDE的艺术风格影响很大,INSIDE中许多场景都是雾+剪影的效果。 上图是没有雾效的场景,环境就显得很简陋。 加上简单的线性雾,没有添加散射效果(scattering),雾的不透明度随距离线性递增,到达一定距离后不再增加(这样做是为了当远处有非常强的光源时,依然可以透过雾到达屏幕)。 雾+辉光(glare)效果,辉光的作用是增加大气散射,可以注意到整个场景都变得些许模糊了,尤其是远处的路牌和电线杆。这样做可以营造一种朦胧的氛围。 这种大范围的辉光效果(glow)的实现方法:使用降采样+多重kawase模糊,每一次模糊都增大模糊范围,最后可以得到很好的效果。kawase模糊是一种性能不错的模糊算法。 游戏里不止是雾效用了辉光。以上只是实现了一个辉光pass。 第二个辉光pass用来实现HDR辉光,或者说,渲染这些高亮度的光源,注意光源周围溢出的小范围光晕。 (HDR:高动态范围,可以简单的理解为让亮的物体看起来更亮,暗的物体看起来更暗,这样场景的明暗变化就有了更多的层次细节。) 开发者之前尝试将大范围的雾效辉光和小范围的HDR辉光放在同一个pass里渲染,但是两者互相干扰,不得不分开为两个独立的pass,而且分开后也可以对两者进行单独调整。 使用遮罩和透明通道对自发光(emissive)材质的物体单独处理。 更重要的是,光晕的强度要跟光源的强度匹配,左图LDR color看起来就很奇怪,光源本身并不亮,但周围的光晕甚至比光源本体还亮,这不符合直觉。右图HDR color就好很多。 后效阶段,可以看到两种辉光确实使用了两个独立pass。 色差效果,注意光源的彩色边缘,现实中强光源进入相机镜头会产生的色散。很像抖音logo。 这里简单的对RGB三个通道分别采样,加上径向偏移,然后使用线性插值做出平滑渐变。 调色,跟PS里拉曲线是一回事。 没调之前,游戏里的高光部分在大于阈值后会被截断(clamp),不管再怎么亮都是一片死白(类似摄影里的过曝)。这里降低了高光,注意图中的背景和手电,下图保留了更多亮部细节。 体积光效果,体积光用于模拟现实世界中光源照射介质产生的丁达尔效应。这个技术还可以用来模拟云雾效果。INSIDE游戏里有大量这样的效果,水下的光源,敌人手里的手电筒,潜水器的探照灯。
一般而言,进入体积光内部的物体会遮挡光源,让体积光变成各种各样的形状,而遮挡效果的实现是很繁琐的。 大家可以看到,蓝色箭头标出来的部分,因为小男孩的遮挡,体积光变弱了。 右边的示意图说明用了raymarching方法,也是光线追踪的一种。 对于每一个像素,由摄像机发射一条光线,穿过照射区,把这条光线上平分出多个点,再从每个点发射光线到光源,检测是否可见(是否被遮挡),最后统计被遮挡的点有多少,就可以算出该像素的体积光强度。 其实真正要算的是被遮挡的线段长度。这里用点数去算,其实是一个近似的积分方法,用有限的点数去逼近线段的长度。 如图,每像素128次采样,也就是每条光线分成128个点,做128次可见性测试。 可以看到效果非常棒,但是每帧渲染这个体积光就要花费22ms,要知道60帧的要求是每帧16.6ms,更别说还有游戏逻辑,物理计算,动画,其他渲染pass了。 于是需要优化。 降低到每像素24次采样,需要3.6毫秒。 依然很慢,而且看起来很丑。由于采样数降低,产生了非常奇怪的,明暗交错的伪像。 接着尝试给每根光线加一个侧向的随机抖动(Jitter)。采样点不在限制于射线上,而是分布在射线周围,这样试图让边界混合起来,有一个缓慢过渡的效果。 可以看到产生了噪点,但是确实消除了伪像。 因为添加了抖动,每帧需要4.9ms,更慢了。这个采样数显然也不切实际,开销太大了。 但是人类是很神奇的,人眼对于均匀变化的噪点并不敏感(人眼自带滤波器)。 接下来可以重点调试噪点。 降低到3次采样,可以看到噪点更多了,之前抖动使用的随机分布就是白噪声分布。 因为噪点分布过于随机,遮挡部分的明暗变化都被抹除了。 使用拜耳8*8阵列作为抖动的概率分布产生的效果。保留了明暗过渡,但产生了重复的pattern,缺少随机化,看起来很奇怪。 使用蓝噪声(blue noise)产生的效果。 可以看虎,同样的采样数下,蓝噪声即可以保留明暗变化,也能不产生重复的pattern。 还有没有其他优化方法? 从摄像机发射光线出去采样,显然不需要从摄像机到无限远都进行计算,只需要照明体内部的范围就够了。 这里除了照射范围,还加一个包围盒做剔除,可以大大降低计算压力。 显然,体积光是一种低频效果,高频信息很少,所以每像素逐一采样实在是太浪费了。 将分辨率减半,不需要每个像素都采样,降低计算量。 刚刚不是只渲染了一半像素吗?剩下的一半用深度作为权重混合已渲染的像素,进行上采样(其实就是模糊效果)。 噪点效果降低了许多。 最后加上时间性抗锯齿(TAA)来兜底,得到了丝滑的体积光遮挡效果。 TAA也是一个比较复杂的话题,INSIDE开发者也单独做了一个TAA的talk,非常详细。 TAA简单来说就是将当前帧的采样和前几帧的采样结合起来,这样每一帧都包含了好几帧的结果,采样率就高了,计算结果就稳定了,可以以很不错的性能做到抗锯齿和降噪。 最后使用6采样,一半分辨率,就可以做到很好的效果,而且此效果只花费0.75毫秒。 如何降低显存带宽的占用。 体积光渲染在所有pass中的位置,以及如何与透明度渲染配合。 色带与抖动
上面这张图看起来很普通,但是注意右边,展示了非常细腻的体积云/雾效果,过度非常自然细腻。 这离不开之前说到的抖动(Jittering)。 如果不使用抖动,看起来就会像这样: 屏幕右边产生了非常难看的色带。 不连续的过度效果非常惹眼,会分散玩家的注意力。 这是因为色彩精度/深度不够导致的,通常使用RGB渲染,每个通道只有8位,合起来24位。每个通道的强度只能用0-255一共256个整数表示。 渲染过程中产生的色彩是浮点数,最后要舍入到8位整数。丢失了小数部分,离散的整数就会导致色彩的不连续变化。 最简单的解决方案是将色彩精度提高到14位。但是这种办法会让渲染变慢。 右图黄线代表色彩的真实值,蓝线表示向下取整的值,红色表示向上取整的值。 这里在舍入之前添加一个0-1之间的随机浮点数(抖动),当有大量像素需要做舍入时,他们的值分布在图中的噪声带部分,这样就会让色彩的过渡变得更平滑。 其实这就是常说的抖色。 这种技术在印刷领域很常见。 来看这一张照片: 这是一张黑白照片,图片包含的色彩是由连续变化的黑-灰-白组成的,像这样: 如果你有一个打印机,只能打印黑和白两种颜色,中间的灰色是不能打印的。 那么打印出来可能就像这样: 图片中只有黑白两种颜色。失去了任何过渡效果。 有没有办法不使用其他颜色来打印出渐变过渡效果呢?有的: 这张图片是由一个个黑色的斑点组成的,没有灰色。但是通过斑点排布的密集程度就可以实现灰色渐变的效果。 放大看就是这样: 回到游戏。 第一行是原始信号(浮点数)的过渡效果,第二行是直接取整的过度效果。这就是色带产生的原因。 使用随机数抖动,效果不错,注意第二行的色彩并没有比上图多,原理上面讲过了。 这里还产生了四个不连续的噪点带。还需要进一步优化。 说明均匀的随机分布还是不行的(右上角)。 使用TPDF(三角噪声概率密度函数)可以获得不错的性能和效果(注意右上角)。高斯噪声也不错,但是计算更复杂。 看看结果(第二行),非常均匀。但产生的噪点还是很多,挺惹眼的。 使用蓝噪声的效果,高频噪声都被滤去了。 具体用,什么还是看实际情况。 抖动是一种非常有用的技术,所以还有哪些东西可以加入抖动? 点光源的随着照射距离产生的明暗变化,也会有色带,也需要抖动。 注意左边的render pass,先在光照pass中加入抖动。效果好了一些,但仔细看还是有一圈圈的色带,噪点分布并不均匀。 在final pass中也加入抖动,好多了。 在半透明渲染中也加入抖动,烟雾的色带也去除了。 辉光也可以加入抖动。 甚至连法线都可以加入抖动,注意左图地面,因为三角形的线性差值产生了不自然的过渡,本质是法线方向的不自然过渡,所以依然可以用抖动解决。 『好了好了,我们让所有东西都抖动了起来。。。』 定制的照明
没必要限制自己使用Unity内置的点光源,平行光源和聚光灯。 接下来讲讲三种定制化的光照: - 反射光(Bounced Lights)
- 环境光遮蔽贴花(AO Decals)
- 阴影贴花(Decals)
间接光(Bounced Lights),是指光线在场景中经过反射,到达其他表面,影响了其他物体外观的光照。间接光是全局光照(Global Illumination, GI)的核心。 这里的间接光实现方法很简单: 注意图片右边小球,在一个点光源的照射下,如果使用普通的兰伯特(lambertian)光照,小球的背面(左半边)显然接收不到光照,应该是全黑的,但是全黑看起来并不好,现实中的光照,经过多次反射,总会到达物体表面,很难有全黑的物体。 当然有其他算法实现间接光照,但是开销太大了。 图片里,可以看出物体的背光面也收到了光照,怎么实现的呢? float lDotN = dot(lightDir, normal);lDotN = lDotN * _Hardness + 1.0 - _Hardness;
第一行:lDotN 为光源方向和法线方向的点积。值的范围在-1.0到1.0之间。大于0表示迎光面,小于零表示背光面。 第二行:对lDotN追加了一个处理,_Hardness可以人为调整,取值范围0到1之间。 当_Hardness为1时,lDotN不变,与普通的兰伯特光照一样。 当_Hardness为0时,lDotN恒为1,此时物体表面所有点的法线都是正对着光源的,所有点受到的光照一样。看起来就像是标准光照模型中的环境光。
当_Hardness为0时,所有点受到的光照一样也就是说,通过调整_Hardness可以实现不同程度的渐变效果。 虽然不符合物理学,但看起来反倒更符合直觉(注意图中左边盒子的暗面)。 这样动态调整的好处就是,可以跟随场景的需要一起变化。 比如图中左下角的场景,窗户从完全关闭到升起,光线从窗外进来,出了地面上几块区域被直接光照外,其他的区域都是间接光照。 我们可以将_Hardness随着窗户的升起慢慢增大,这样场景中的间接光照物体,就会从完全关闭时的全黑,到慢慢有一定的亮度(而不是从头到尾的全黑)。看起来非常富有变化。 右上角和右下角展示了传统的全局光照方法,使用几个隐藏的点光源,使其亮度随着窗户升起慢慢增大。 对比起来,还是上文叙述的方法渲染效率更高,因为overdraw和overlaps更少。 环境光遮蔽贴花 环境光遮蔽贴花(AO Decals)。 首先要搞明白环境光遮蔽(Ambient Occlusion, AO)是什么。 以标准光照模型为例,给定一个物体和一个光源,那么物体表面一个点受到光照时的颜色,是以下四项相加: - 自发光(emissive),物体本身发光,即使没有任何光源照明,物体也是有亮度的。这里我们可以忽略,大部分物体没有这一项。
- 高光反射(specular),这个部分用于描述当光线从光源照到物体表面时,物体镜面反射产生的光。
- 漫反射(diffuse),这个部分是光线从光源照到物体表面时,物体向各个方向产生的光。
- 环境光(ambient),这个部分用来描述其他间接的光,其实就是一个给定的常数。
看不懂没关系。 1,2,3项都忽略。 第1项大部分物体都不是光源,所以是0。第2,3项的前提是物体能被光源直接照射到。如果不被照射到,这两项也就都是0。 上文说过,场景中有许多不被光源直接照射的物体,如果都是黑的,那看起来就太假了。 第4项环境光就是人为添加的项,他是开发者根据经验设置的常数,在物体不被任何光源直接照射时,可以有一个保底的亮度,能被玩家看见。 但这也会有问题。因为是人为添加的常数,暗部的亮度一样,就缺乏变化。 现实世界中,即使物体不被直接光照,也会有明暗变化,有的地方稍微亮一点有的地方稍微暗一点。 先看图片左边,没有环境光遮蔽。 光源从左边来,每一个球体都被分为能被直接照射的亮部和不能被直接照射的暗部,重点看暗部,因为使用常数的环境光来渲染,整个图片就显得很『平』。 右图才是更符合直觉的渲染,注意暗部的接缝和角落,它们会比其他部分更暗。因为现实世界的角落总是只能接受更少的光。 所以环境光遮蔽的原理用大白话来说,就是检测每一个点的可见性,看看它是不是角落,如果是角落,就减去环境光常数项一定值,让它看起来更暗。具体减去多少,由这个点的可见性决定。 环境光遮蔽有很多流行的实现方法,最流行的算法是屏幕空间环境光遮蔽(Screen Space Ambient Occlusion,SSAO),它首次用于游戏《孤岛危机》中。 SSAO是在游戏中实现全局光照的重要技术之一。 可以看到下面图的暗部比上面的图暗部更写实(注意图中人群,潜水器,箱子脚下的阴影)。 AO Decals可以实现高质量的局部环境光遮蔽。 INSIDE开发者并没有使用SSAO,这是因为SSAO对局部的控制很差,还会产生因为屏幕空间计算导致的伪影。 (未完待续)
|