(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)

前面两篇文章我们介绍了PBS实现的数学和理论基础,和standard shader的原理和实现,还有一些其他的渲染相关的unity技术。其中有些概念和技术没有讲的很详细,现在对这些重要的概念进行更深入地解释。

一、什么是全局光照

全局光照对得到真实的渲染结果有着举足轻重的作用,全局光照指的就是模拟光线是如何在场景中传播的。他不仅会考虑那些直接光照的结果,还会计算光线被不同的物体表面反射产生的间接光照。在使用基于物理的着色技术时,当渲染表面上一点时,我们需要计算该点的半球范围内所有会反射到观察方向的入射光线的光照结果,这些入射光线中就包含了直接光照和间接光照。

通常来讲这些间接光照的计算是非常耗时间的,通常不会用在实时渲染中一个传统的方法是使用光线追踪,来追踪场景中每一条重要的光线的传播路径。使用光线追踪能得到非常出色的画面效果,因此被大量应用在电影制作中。但是这种方法往往需要大量的时间才能得到一帧,并不能满足实时的要求。

Unity采用了Enlighten解决方案来让全局光照能够在各种平台有不错的性能表现。事实上,Enlighten也已经被集成在虚幻引擎中,它已经在很多3A大作中展现了自身强大的渲染能力。总体来讲,Unity使用了实时+预计算的方法来模拟场景中的光照。其中,实时光照用于计算那些直接光源对场景的影响,当物体移动时,光照也会随之发生变化。但正如我们之前所说,实时光照无法模拟光线被多次反射的效果。为了得到更加真实的渲染效果,unity引入了预计算光照的方法,使得全局光照甚至在一些高端的移动设备上也可以达到实时的要求。

预计算包含了我们常见的光照烘焙,也就是指我们把光源对场景中静态物体的光照效果提前烘焙到一张光照纹理中,然后把这张光照纹理直接贴在这些物体的表面,来得到光照效果这些纹理不仅存储了这些光照效果,还包含了那些由物体反射得到的间接光照。但是这些光照纹理无法在游戏运行时不断更新,也就是说他们是静态的。不过这种方法的确为移动平台的复杂光照模拟提供了一个有效途径。

由于静态的光照烘焙无法在光照条件改变时更新物体的光照效果。因此unity使用了预计算实时全局光照(Precomputed Realtime GI)为我们提供了一个解决途径,来动态地为场景实时更新复杂的光照结果。使用这种技术我们可以让场景中的物体包含丰富的全局光照效果,例如多次反射等,并且这些计算都是实时的,可以随着光源和物体的移动而发生变化,这是使用之前的实时光照或烘焙光照所无法实现的。

这些是如何实现的?它们实际上都利用了一个事实——一旦物体和光源的位置被固定了,这些物体对光线的反弹路径以及漫反射光照(我们假设漫反射光照在各个方向的分布是相同的)也是固定的,也就是说和摄像机无关。因此我们使用预计算方法来把这些物体之间的关系提前计算出来,而在实时运行时,只要光源的位置(光源的颜色可以实时变化的)不变,即便改变了光源的颜色和强度、物体材质属性(漫反射和自发光相关属性),这些信息就一直有效,不需要实时更新。在预计算阶段中,Enlighten会在由所有静态物体组成的场景上,进行简化的“光线追踪”过程,这个过程中Enlighten会自动把场景分割成很多个子系统,他并不是为了得到精确的光照效果,而是为了得到场景中物体之间的关系。需要注意这些预计算都是在静态物体上进行的,因此为了利用上述预计算方法,至少要把一个物体标识为Static(至少把Lightmap Static勾选上)。一个例外是物体的高光反射,这是和摄像机位置相关的,unity的解决方案是使用反射探针对于动态移动的物体来说,我们可以使用光照探针来模拟它的光照环境。因此在实时运行时,unity会利用预计算得到的信息来计算光照信息,并把它们存储在额外的光照纹理、光照探针或Cubemap中,再和物体材质进行必要的光照计算,得到最后的渲染结果。

unity全新的全局光照解决方案可以大大提高一些基于PC/游戏机等平台的大型游戏的画面质量,但如果需要在移动平台上使用仍需要非常小心它的性能,一些低端手机仍然不适合这种复杂的基于物理的渲染,不过后面会慢慢改善的。

二、什么是伽马校正

我们之前说过,想要渲染出更符合真实光照环境的场景就需要使用线性空间。而unity默认的空间是伽马空间,在伽马空间下进行渲染会导致很多非线性空间下的计算,从而引入了一些误差,而要把伽马空间转换到线性空间,就需要进行伽马校正。

伽马校正中的伽马一词来源于伽马曲线,通常伽马曲线的表达式如下:

 

其中指数部分的发音就是伽马。最开始的时候人们使用伽马曲线来对拍摄的图像进行伽马编码。摄像机的原理可以简化为,把进入到镜头内的光线亮度编码成图像(例如一张JEPG)中的像素。如果采集到的亮度是0,像素就是0亮度是1,像素就是1亮度0.5,像素就是0.5.如果我们只用8位空间来存储像素的每个通道的话,这意味着0-1区间可以对应256种不同的亮度值,但是后面发现人眼对光的灵敏度在不同亮度上是不一样的,正常光照条件了人眼对较暗区域的变换更加敏感。如下图:

(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)_第1张图片

这说明亮度上的线性变换对人眼感知来说是非均匀的。如果使用8位空间来存储每个通道的话,我们仍然把0.5亮度编码成值的0.5的像素,那么暗部和亮部区域我们都使用了128种颜色来表示。但实际上,对亮部区域使用这么多颜色是种存储浪费。一种更好的方法是,我们应该把更多的空间来存储更多的暗部区域,这样存储空间就可以被充分利用起来了。摄影设备如果使用了8位空间来存储照片的话,会使用大约为0.45的编码伽马来对输入的亮度进行编码,得到一张编码后的图像。因此图像中0.5像素值对应的亮度其实并不是0.5,而大约为0.22,这是因为:

0.5≈pow(0.22,0.45)

如上所见,对拍摄图像使用伽马编码使得我们可以充分利用图像的存储空间。但当把图片放到显示器里显示时,我们应该对图像再进行一次解码操作,使得屏幕输出的亮度和捕捉到的亮度是符合线性的。

由于游戏界长期以来都忽视了伽马校正的问题,造成了我们渲染出来的游戏总是暗沉沉的,总和真实世界不像。由于编码伽马和显示伽马的存在,我们一不小心就可能在非线性空间下进行计算,或是使得输出的图像是非线性的。

对于输出来说,我们直接输出渲染结果而不进行任何处理,在经过显示器的显示伽马处理后,会导致图像整体偏暗,出现失真的情况,如下图的对比:

伽马空间:

(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)_第2张图片

线性空间:

(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)_第3张图片

我们放置了一个球体,并把场景中环境光照设为全黑,平行光方向设置从上方直接射到球体表面,球体使用的材质为内置的漫反射材质,上图显示了在伽马空间和线性空间下的渲染结果。

可以看出,伽马空间的渲染结果整体偏暗,此时屏幕输出的亮度和球面的光照结果并不是线性的,图一没有进行伽马校正,因此由于显示器存在显示伽马就引入了非线性关系,如果A点的输出值是(0.5,0.5,0.5),B的输出是(1,1,1),也就是说A的亮度值并不是B的一半,而是约为他的1/4。而图二我们使用了线性空间,unity会把像素写入颜色缓冲前进行一次伽马校正,来抵消屏幕的显示伽马的作用,此时得到屏幕亮度才是真正跟像素值成正比的。

伽马的存储还会对混合造成影响,如下图:

(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)_第4张图片

在左图所示的伽马空间下,可以看到在绿色和红色的混合边界处出现了不正常的蓝色渐变。而正常的结果应该如下图所示的从绿色到红色的渐变:

(二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)_第5张图片

除此之外,图一的交叉边界似乎都变暗了,这是因为在混合后进行输出时,显示器的伽马导致接缝处颜色变暗。

实际上,渲染中非线性输入最有可能的来源就是纹理为了充分利用存储空间,大多数图像文件都进行了提前校正,即已经使用了一个编码伽马对像素值编码但这意味着它们是非线性的,如果在shader中直接采样就会造成非线性空间的计算,使得结果和真实世界的结果不一致。我们在使用多级渐远纹理(mipmap)时也需要注意,如果纹理存储在非线性空间中,那么在计算多级渐远纹理时就会在非线性空间里计算。由于多级渐远纹理的计算是种线性计算——即采样的过程,需要对某个方形区域内像素取平均值,这样就会得到错误的结果。正确的做法是,我们要把非线性的纹理转换到线性空间在计算多级渐远纹理

如上所说,伽马的存在使得我们很容易得到非线性空间下的渲染结果。在游戏渲染中,我们应该保证所有输入都被转换到了线性空间下,并在线性空间下进行各种光照计算,最后再输出前通过一个编码伽马进行伽马校正后再输出到颜色缓冲中。unity的颜色空间设置就可以满足我们的需求,当选择伽马空间时,实际上就是放任模式,不会对shader的输入进行任何处理,即输入可能是非线性的;也不会对输出像素进行任何处理,这意味着输出的像素经过显示器的显示伽马转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。当选择线性空间时,unity会把输入纹理设置为sRGB模式,在这种模式下,硬件对纹理进行采样时自动将其转换到线性空间中,这取决于当前的渲染配置。如果我们开启了HDR的话,渲染就会使用一个浮点精度缓冲。这些缓冲有足够的精度不需要我们进行任何伽马校正,此时所有的混合和屏幕后处理都是在线性空间下进行的。当渲染完成要写入显示设备的后备缓冲区时,再进行一次最后的伽马校正。如果没有使用HDR,unity就会把缓冲设置成sRGB格式,这种格式的缓冲就像一个普通纹理一样,在写入缓冲前需要进行伽马校正,在读取缓冲时需要再进行一次解码操作。如果此时开启了混合,在每次混合时,硬件会首先把之前颜色缓冲中存储的颜色值转换回线性空间中,然后再与当前的颜色进行混合,完成后再进行伽马校正,最后把校正后的混合结果写入颜色缓冲中,这里需要注意,透明通道是不会参与伽马校正的

然而unity的线性空间并不是所有平台都支持的,例如移动平台就无法使用线性空间此时我们要在自己shader中进行伽马校正,对非线性输入纹理的校正代码通常如下:

float3 diffuseCol=pow(tex2D(diffTex,texCoord),2.2);

早最后输出前,对输出像素值的校正代码通常如下面这样:

fragColor.rgb=pow(fragColor.rgb,1.0/2.2);
return fragColor;

但是,手工对输出像素进行伽马校正会在使用混合时出现问题,因为校正会导入写入颜色缓冲内的颜色是非线性的,这样混合就发生在非线性空间中,一种解决方法是,在中间计算时不要对输出颜色值进行伽马校正,但在最后需要进行一个屏幕后处理操作来对最后的输出进行伽马校正,也就是说我们需要保证伽马校正发生在渲染的最后一步中,但可能会造成一定的性能损耗

既然伽马这么麻烦,什么时候可以舍弃?如果有一天我们对图像的存储空间能够大大提升,通用格式不再是8位时,例如32位,伽马也许就会消失,因为我们有足够多的颜色空间可以利用,不需要为了充分利用存储空间进行伽马编码的工作了,这就是我们后面要说的HDR。

三、什么是HDR

在使用基于物理的渲染时,经常会听到HDR,HDR是High Dynamic Range的缩写,即高动态范围,与之相对的是低动态范围(Low Dynamic Range,LDR)。那么这个动态范围指的是什么?通俗来讲,动态范围指的就是最高的和最低的亮度值之间的比值。在真实世界中,一个场景中最亮和最暗区域的范围可以非常大,例如太阳发出的光可能要比场景中某个影子上的点的亮度要高出几万倍,这些范围远远超过图像或显示器能够显示的范围。通常在显示设备使用的颜色缓冲中每个通道的精度为8位,意味着我们能用这256种不同的亮度来表示真实世界中所有的亮度,因此这个过程中一定会存在一定的精度损失。HDR的出现给我们带来了新的希望,它使用远远高于8位的精度(如32位)来记录亮度信息。使得我们可以表示超过0-1内的亮度值,从而可以更加精确地反映真实的光照环境。尽管我们最后还是需要把信息转换到显示设备使用的LDR内,但中间的计算可以让我们得到更加真实可信的效果。使用HDR渲染,让亮的物体可以非常亮,暗的物体可以非常暗,同时又可以看到两者之间的细节。

使用HDR来存储的图像被称为高动态范围图像(HDRI),这种图像作为场景的Skybox可以更加真实地反映物体周围的环境,从而得到更加真实的反射效果。不仅如此,HDR对与光照叠加叠加也有非常重要的作用,如果我们的场景中有很多光源或是光源强度很大,那么一个物体在经过多次光照渲染叠加后最终得到的光照亮度很可能超过1.如果没有使用HDR,那么超过1的部分全部会截取到1,使得场景丢失了很多亮度区域的细节。如果开启了HDR,就可以保留这些超过范围的光照结果,尽管最后我们仍然需要把他们转换到LDR进行显示,但我们可以使用色调映射(tonemapping)技术来控制这个转换的过程,从而允许我们最大限度地保留需要的亮度细节。

HDR的使用可以允许我们在屏幕后处理中拥有更多的控制权。例如我们常常同时使用HDR和Bloom效果,Bloom效果需要检测屏幕中亮度大于某个阈值的像素,把它们提取出来后进行模糊,再叠加到原图像中。但是如果不使用HDR的话,我们只能使用小于1的阈值来提取需要的像素,但很多时候我们实际上是需要提取那些非常亮的区域,例如车窗上对太阳的强烈反光。由于没有使用HDR,这些值实际上很可能和街上一些颜色偏白的区域几乎一样,造成不希望的区域也会出现泛光的效果。如果我们使用HDR,这些就都可以解决了,我们只需要使用超过1的阈值来提取那些非常亮的区域即可。

总体来说,使用HDR可以让我们不会丢失高亮度区域的颜色值,提供了更真实的光照效果,并为一些屏幕后处理提供了更多的控制能力。但HDR也有自身的缺点,首先由于使用了浮点缓冲来存储高精度图像,不仅需要更大的显存空间,渲染速度会变慢,除此之外,一些硬件并不支持HDR,而且一旦使用了HDR,我们无法再利用硬件的抗锯齿功能,事实上,在unity中如果我们同时打开了硬件的抗锯齿(Editor->ProjectSettings->quality->Anti Aliasing中打开)和摄像机的HDR,unity会发出警告来提示我们由于开启了抗锯齿,因此无法使用HDR缓冲。尽管如此,我们可以使用基于屏幕后处理的抗锯齿操作来弥补这点

在unity中使用HDR很简单,可以在Camera组件面板中打开HDR选项即可。此时场景就会被渲染到一个HDR的图像缓冲中,这个缓冲的精度范围远远超过0-1。最后可以在使用一个色调映射的屏幕后处理脚本把HDR图像转换到LDR图像进行显示

1.4 PBS适合什么样的游戏

把PBS引入当前游戏项目需要权衡一些优缺点,PBS并不意味着游戏画面需要追求和照片一样的真实效果。事实上很多游戏都不需要刻意追求与照片一样的真实感,玩家眼中的真实感大多也并不是如此。PBS的优点在于,我们只需要一个万能的shader就可以渲染相当一大部分类型的材质,而不是使用传统的做法为每种材质写一个特定shader。同时PBS可以保证在各种光照条件下,材质都可以自然的和光源进行交互,而不需要我们反复地调整材质参数。

然而在使用PBS时我们也需要考虑它带来的代价。PBS往往需要更复杂的光照配合,例如大量使用光照探针和反射探针等,而且PBS也需要开启HDR以及必不可少的屏幕特效,如抗锯齿,Bloom和色调映射。如果这些屏幕特效对当前游戏来说需要消耗过多的性能,那么PBS就不适合当前的游戏,我们应该使用传统的shader来渲染游戏。使用PBS对美工人员来说同样是个挑战。美术资源制作过程和传统的shader有很大不同,普通的法线纹理+高光反射纹理的组合不在适用,我们需要创建更细腻复杂的纹理集,包括金属值纹理、高光反射纹理、粗糙度纹理、遮挡纹理,有些还需要使用额外的细节纹理来给材质添加更多的细节表面。除了使用图片扫描的传统辅助方法外,这些纹理的制作通常还需要更专业的工具来绘制,例如Allegorithmic Substance Painter和Quixel Suite。

你可能感兴趣的:(游戏开发,unity,Shader,基于物理的渲染技术)