Unity_Shader扩展篇_18_Unity Shader入门精要

第18章 基于物理的渲染
在之前的章节中,我们学习了Lambert光照模型、Phong光照模型和Blinn-Phong光照模型。但这些光照模型的缺点在于,它们都是经验模型。如果我们需要渲染更高质量的画面,这些经验模型就显得不再能满足我们的要求了。
近年来,基于物理的渲染技术(Physical Based Shading,PBS)被逐渐应用于实时渲染中。总体来说,PBS是为了对光和材质之间的行为进行更加真实的建模。PBS早已被广泛应用到电影行业中,但游戏中的PBS是近年来才逐渐流行起来的。Unity最早在2012年的《蝴蝶效应》(Butterfly Effect)的demo中大量使用了PBS,并在Unity5中正式将PBS引入到引擎渲染中。Unity5引入了一个名为Standard Shader的可在不同材质之间通用的着色器,而该着色器就是使用了基于物理的光照模型。需要注意的是,PBS并不意味着渲染出来的画面一定是像照片一样真实的,例如,Pixar和Disney尽管长期使用PBS渲染电影画面,但他们得到的风格是非常有特色的艺术风格。
在本章中,我们首先会了解PBS的基本原理,来了解渲染方式有那些不同。
18.1 PBS的理论和数学基础
在了解如何实现PBS之前,我们非常有必要来了解基于物理的渲染所基于的理论和数学基础。本节主要参考了Naty Hoffman在SIGGRAPH2013上做的名为Background:Physics and Math of Shading的演讲。
Hoffman N.Background:physics and math of shading[C]//Fourth International Conference and Exhibition on Computer Graphics and Interactive Techniques,Anaheim,USA.2013:21-25。

18.2 Unity5 的Standard Shader
当我们在unity5中创建一个模型或是新创建一个材质时,其默认使用的着色器都是一个名为Standard的着色器。这个Standard Shader就使用了基于物理的渲染。
Unity支持两种流行的基于物理的工作流程:金属工作流(Metallic workflow)高光反射工作流(Specular workflow)。其中,金属工作流是默认的工作流程,对应的Shader为Standard Shader。而如果想要使用高光反射工作流,就需要在材质的Shader下拉框中选择Standard(Specular setup)。需要注意的是,通常来讲,使用不同的工作流可以实现相同的效果,只是他们使用的参数不同而已。金属工作流也不意味着它只能模拟金属类型的材质,金属工作流的名字来源于它定义了材质表面的金属值(是金属类型的还是非金属类型的)。高关反射工作流的名字来源于它可以直接指定表面的高关反射颜色(有很强的高光反射还是很弱的高光反射)等,我们可以选择自己更偏好的工作流来制作场景,这更能多的是个人喜好的问题。当然也可以同时混用两种工作流。
Unity提供的Standard Shader允许让我们只使用这一种shader来为场景中所有的物体进行着色,而不需要考虑它们是否是金属材料还是塑料材质等,从而大大减少我们不断调整材质参数所花费的时间。
18.2.1 如何实现
Standard和Standard(Specular setup)的Shader源代码可以在Unity内置的builtin_shaders-5.x/DefaultResourcesExtreme文件夹中找到。这些shader依赖于builtin_shaders-5.x/DefaultResourcesExtreme文件夹中定义的一些头文件。这些相关的头文件的名称大多类似于UnityStandardXXX.cginc,其中定义了和PBS相关的各个函数、结构体和宏等。下表列除了这些头文件的名称以及它们的主要用处。
这里写图片描述
这里写图片描述
18.2.2 如何使用Standard Shader
已知Unity5的Standard Shader 适用于各种材质的物体。
我们知道,材质和光的交互可以分成漫反射和高光反射两个部分,其中漫反射对应了次表面散射的结果,而高光反射则对应了表面反射的结果。通过对金属材质和非金属材质的分析,我们可以得到它们的漫反射和高光反射的一些特点。
这里写图片描述
1.金属材质
·几乎没有漫反射,因为所有被吸收的光都会被自由电子立刻转化为其他形式的能量;
·有非常强烈的高关反射;
·高光反射通常是有颜色的,例如金子的反光颜色为金色。
2.非金属材质
·大多数角度高光反射的强度比较弱,但在掠射角时高关反射强度反而会增强,即菲涅耳现象;
·高光反射的颜色比较单一;
·漫反射的颜色多种多样。
但真实的材质大多数混合了上面的这些特征,Unity提供的工作流就是为了更加方便地让我们针对以上特性来调整材质效果。在Unity官方提供的示例项目Shader Calibration Scene(http://www.assetstore.unity3d.com/en/#!/content/25422)中,unity提供了两个非常有参考价值的校准表格,如下面两图所示,他们分别对应了金属工作流高光反射工作流使用的参考属性值,来方便我们针对不同类型的材质来调整参数。
这里写图片描述
这里写图片描述
我们以第一张图,即金属工作流使用的校准表格为例,来解释如何使用张校准表格来指导我们调整材质。在本书资源的场景文件Scene_18_2中,我们提供了一个简单的场景来展示不同材质的结果。下图显示了场景结果。
这里写图片描述
需要注意的是,我们需要在Edit>Project Settings>Player>Other Settings>Color Space中选择Linear才可以得到和上图中相同的效果,这是因为基于物理的渲染需要使用线性空间(18.3.4)来进行相关计算。
在金属工作流中,材质面板中的Albedo定义了物体的整体颜色,它通常就是我们视觉上认为的物体颜色。从亮度来看,非金属材质的亮度范围通常在50~243之间,而金属材质的亮度一般在186~255之间。Unity给的校准表格中还给出了一些非金属和金属材质使用的示例Albedo属性值,我们可以直接使用这些示例来作为材质属性。当然,也可以直接使用一种纹理作为材质的Albedo值。材质面板下一个属性是Metallic,它定义了该物体表面看起来是否更像金属或非金属。同样,我们也可以使用一张纹理来采样得到表面的Metallic值,此时该纹理中的R通道值将对应了Metallic值。在我们的例子中,我们把金属材质的Metallic值设为1,表明该物体几乎完全是一个金属材质,同时把塑料材质的Metallic值设为0,表明该物体几乎没有任何金属特性。最后一个重要的材质属性是Smoothness,它是上一个属性Metallic的附属值,定义了从视觉上来看该表面的光滑程度。如果我们在设置Metallic属性时使用的是一张纹理,那么这张纹理的A通道就对应了表面的Smoothness值(此时纹理的GB通道则被忽略)。
这里写图片描述
高光反射工作流使用的面板和上述金属工作流使用的基本相同。
上述材质属性都属于材质面板中的Main Maps部分,除了上述提到的属性外,Main Maps还包含了其他材质属性,例如,切线空间下的法线纹理、遮挡纹理、自发光纹理等。Main Maps部分的下面还有一个Secondary Maps的属性部分,这个部分的属性是用来定义额外的细节信息,这些细节通常会直接绘制在Main Maps的属性部分,这个部分的属性是用来定义额外的细节信息,这些细节通常会直接绘制在Main Maps的上面,来为材质提供更多的微表面或细节表现。
除了上述属性,我们还可以为Standard Shader选择它使用的渲染模式,即材质面板上的Renderer Mode选项。Standard Shader支持四种渲染模式,分别是Opaque、Cutout、Fade和Transparent。其中,Opaque用于渲染最常见的不透明物体,这也是默认的渲染模式。对于像玻璃这样的材质,我们可以选择Transparent模式,在这个渲染模式下,Albedo属性中纹理的A通道会成为一个掩码纹理,而它的子属性Alpha Cutoff 将是透明度测试时使用的阀值。Fade模式和Transparent模式是类似的,不同的是,在Transparent模式下,当材质的透明值不断降低时,它的反射仍然能被保留,而在Fade模式下,该材质的所有渲染效果都会从屏幕上淡出。
需要注意的是,尽管Standard Shader的材质面板有很多可调节的属性,但我们不用担心由于没有使用一些属性而会对性能有所影响。Unity在背后已经进行了高度优化,在我们生成可执行程序时,Unity会检查哪些属性没有被使用到,同时也会针对目标平台进行相应的优化。
从上面的内容可以看出,想要得到可信度更高的渲染结果,我们需要对不同材质使用合适的属性值,尤其是一些重要的属性值,例如Albedo、Metallic和Specular。当然,想要让整个场景的渲染结果令人满意,尤其包含了复杂光照的场景,仅仅有这些使用了PBS的材质是不够的,我们需要使用Unity提供的其他一些重要的技术,例如HDR格式的Skybox、全局光照、反射探针、光照探针、HDR和屏幕后处理等。
18.3 一个更加复杂的例子
在场景Scene_18_3。本场景使用的元素大多来源于Unity官方的实例项目Viking Village(http://www.assetstore.unity3d.com/jp/#!/content/29140
18.3.1 设置光照环境
我们首先需要为场景设置光照环境。在默认情况下,Unity5中一个新创建的场景会包含一个默认的Skybox。在本例中,我们使用一个自定义的Skybox来代替默认值。做法是,打开Window>Lighting,在Scene标签页下把本例使用的SunsetSkyboxHDR拖拽到Skybox选项中。如下图所示
这里写图片描述
本例中的Skybox使用了一个HDR格式的Cubemap,这与我们之前在10.1节中制作Skybox时使用的纹理不用。这需要解释HDR(High Dynamic Range)的相关知识,我们将在18.4.3中详细了解HDR的原理和应用。但在这里,我们只需要知道,使用HDR格式的Skybox可以让场景中物体的反射更加真实,有利于我们得到更加可信的光照效果。
我们可以设置场景使用的环境光照,这些环境光照可以对场景中所有的物体表面产生影响。上图所示的设置面板中,我们可以选择环境光照的来源(Ambient Source选项),是来自于场景使用的Skybox,还是使用渐变值,亦或是某个固定的颜色。我们还可以设置环境光照的强度(Ambient Intensity参数),如果想要场景中的所有物体不接受任何环境光照,可以把该值设为0。在使用了Standard Shader的前提下,如果我们关闭场景中所有的光源,并把环境光照的强度设为0,场景中的物体仍然可以接受一些光照。
这里写图片描述
那么,这些光照是从哪里来的呢?答案就是反射。默认的反射源(Reflection Source 选项)是场景使用的Skybox。如果我们不想让场景中的物体接受任何默认的反射光照,可以把反射源设置为自定义(即Custom),并把自定义的Cubemap保留为空即可(另一种方式是直接把场景使用的Skybox设置为空),如上图所示。但为了得到更加逼真的渲染结果,我们通常是不会这样做的。在渲染实现上,即便场景中没有任何光源,unity在内部仍然会调用ForwardBase Pass(假设使用的是前向渲染路径的话),并使用反射的光照信息来填充光源信息,再进行基于物理的渲染计算。读者可以通过帧调试器(Frame Debugger)来查看渲染过程。需要注意的是,这里设置的反射源是默认的反射源,如果我们在场景中添加了其他反射探针(reflection Probes,见18.3.2),物体可能会使用其他反射源。当默认反射源是Skybox时,Unity会由场景使用的Skybox生成一个Cubemap,我们可以通过Resolution选项来控制它每个面的分辨率。
除了Standard Shader外,Unity还引入了一个重要的流水线——实时全局光照(Global Illumination,GI)流水线。使用GI,场景中的物体不仅可以受直接光照的影响,还可以接受间接光照的影响。直接光照指的是那些直接把光照射到物体表面的光源,在本书之前的章节中,我们使用的都是直接光照来渲染场景中的物体。但在现实生活中,物体还会受到间接光照的影响。例如。想要一个红色墙壁旁边放置了一个球体,尽管墙壁本身不发光,但球体靠近墙的一面仍会有少许的红色,这是由于红色墙壁把一些间接光照投射到了球体上。在unity中,间接光照指的就是那些被场景中其他物体反弹的光,这些间接光照会受反弹光的表面的颜色影响(例如之前例子中的红色的墙壁),这些表面会在反弹光线时把自身表面的颜色添加到反射光的计算中。在Unity5中,我们可以使用这些直接光照和间接光照来创建更加真实的视觉效果。
下面,我们首先设置场景使用的直接光照——一个平行关。在PBR中,太阳光的方向和天空盒上那个假的太阳的方向不一定会一致,可能导致太阳的位置和影子的位置有悖现实,所以必须手动调整,让它们重合。
在平行光面板的烘焙选项(即Baking)中,我们选择了Realtime模式(实时渲染模式)然而,实时光照往往需要较大的性能消耗,对于移动平台这样资源比较短缺的平台,我们可以选择Baked模式,此时,Unity会把该光源的光照效果烘焙到一张光照纹理(lightmap)中,这样我们就不用实时为物体计算复杂的光照,而只需要通过纹理采样来得到光照结果。选择烘焙模式的缺点在于,如果场景中的物体发生了移动,但是它的阴影等光照效果并不会发生变化。烘焙选项中的Mix模式则允许我们混合使用实时模式和烘焙模式,它会把场景中的静态物体(即那些被标识为Static的物体)的光照烘焙到光照纹理中,但仍然会对动态物体产生实时光照。
Unity5引入了实时间接光照的功能,在这个系统下,场景中的直接光照会在场景中各个物体之间来回反射,产生间接光照。正如我们之前讲解的,间接光照可以让那些没有直接被光源照亮的物体同样可以接受到一定的光照信息,这些光照是由它周围的物体反射到它的表面上的。当一条光线从光源被反射出来后,它会与场景中的一些物体相交,第一个和光线相交的物体受到的光照即为直接光照。当得到直接光照在该光线相交的物体,就会受到间接光照的影响,同时他们也会继续反射。当经过多次反射后,该光线最后完全消失。这些间接光照的强度是由GI系统计算得到的默认亮度值。下图所示的光源面板中的Bounce Intensity 参数可以让我们调节这些间接光照的强度。当我们把它设为0时,意味着一条光线仅会和一个物体相交,不在被继续反射,也就是说,场景中的物体只会受到直接光照的影响。
这里写图片描述
除了上述调整单个光源的间接光照强度,我们也可以对整个场景的间接光照强度进行调整General GI参数快中的Bounce Boost参数来控制场景中反射的间接光照的强度,它会和单个光源的Bounce Intensity 参数来一起控制间接光照的反射轻度。除此之,把indirect Internsity参数调用大同样可以每个增大间接光照的强度。需要注意的是,间接光照还有可能来自一些自发光的物体。
18.3.2 反之反射探针
在10.1中我们了解了环境映射,在实时渲染中,我们经常会使用Cubemap来模拟物体的反射效果。然而,如果我们永远使用同一个Cubemap,那么,当环境发生较大变化时,就很容易出现“穿帮镜头”,一种解决办法是可以在脚本中控制几何生成从当前位置观察到的Cubemap,而Unity为我们提供了一种更加方便的途径,即使用反射探针(Reflection Probes)。反射探针的工作原理和光照探针(Light Probes)类似,它允许我们在场景中的特定位置上对整个场景的环境反射进行采样,并把采样结果存储在每个探针上。当游戏中包含反射效果的物体从这些探针附近经过时,Unity会把从这些邻近探针存储的反射结果传递给物体使用的反射纹理。如果物体周围存在多个反射探针,Unity还会在这些反射结果之间进行插值,来得到平滑渐变的反射效果。实际上,Unity 会在场景中放置一个默认的反射探针,这个反射探针存储了对场景使用的Skybox的反射结果,来作为场景的环境光照(18.3.1)。如果我们需要让场景中的物体包含额外的反射效果,就需要放置更多的反射探针。
反射探针同样有3种类型:Baked,这种类型的反射探针是通过提前烘焙来得到该位置使用的Cubemap的,在游戏运行时反射探针中存储的Cubemap并不会发生变化。需要注意的是,这种类型的反射探针在烘焙时同样只会处理那些静态物体。Realtime,这种类型则会实时更新当前的Cubemap,并且不受静态物体还是动态物体的影响。当然,这种类型的反射探针需要花费更多的处理时间,因此,在使用时应当非常小心他们的性能。还好,Unity允许我们从脚本中通过触发来精确控制反射探针的更新;最后一种类型是Custom,这种类型的探针既可以让我们从编辑器中烘焙它,也可以让我们使用一个自定义的Cubemap来作为反射映射,但自定义的Cubemap不会被实时更新。
我在本节使用的场景中放置了3个反色探针,他们的类型都是Baked(前提是我们把场景中的物体标识成了Static)。
需要注意的是,在放置反射探针时,我们选取的位置并不是任意的。通常来说,反射探针应该被放置在那些具有明显反射现象的物体的旁边,或是一些墙角等容易发生遮挡的物体周围,在本例使用的场景中,木屋内的盾牌具有比较明显的反射效果,而盾牌本身又被木屋遮挡,因此,其中一个反射探针的位置就在盾牌附近。当我们放置好探针后,我们还需要为它们定义每个探针的影响区域。
使用反射探针我们可以模拟互相反射(interreflections)。我们曾在10.1中讲到使用传统的Cubemap方法无法模拟互相反射的效果。例如,假设场景中有两面互相面对面的镜子,在理想情况下,他们不仅会反射自己对面的那面镜子,也会反射那面镜子里反射的图像。只要反射光线没有被完全吸收,反射就会一直进行下去。要实现这种效果,就需要追踪光线的反射轨迹,这是传统的反射方法所无法实现的。Unity5引入的GI系统让这种效果变成了可能,我们在本书资源的Scene_18_3_2中展示了这样的一个例子:两个金属反射的图像包含了两次互相反射的效果。
这里写图片描述
实现流程:在每个金属球的位置处放置一个反射探针,并把每个金属球上的Mesh Renderer组件中的Reflection Probes设置为Simple,这样保证它们只会使用离它们最近的一个反射探针。默认情况下,反射探针只会捕捉一次,也就是说,左边金属球使用的反射探针只会捕获到由右边的金属球第一次反射过来的光线。但在理想情况下,反射过来的光线会继续被左边的金属球反射,并对右边的金属球造成影响。Unity允许我们控制物体之间这样来回反射的次数,这可以通过改变Reflection Bounces参数来实现。
这些探针实际上也是通过在它的位置上放置一个摄像机,来渲染得到一个Cubemap。如果我们把反弹次数设置的很大,或是使用实时渲染,那么这些探针很可能会造成性能瓶颈。更多关于如何优化反射探针以及它的高级用法,读者可以参见Unity的官方手册(http://docs.unity3d.com/Manual/ReflectionProbes.html)。

18.3.3 调整材质
要得到真实可信的渲染效果,我们需要为场景中的物体指定合适的材质。需要再次提醒读者的是,基于物理的渲染并不意味着一定要模拟像照片真实的效果。基于物理的渲染更多的好处在于,可以让我们的场景在各种光照条件下都能得到令人满意的效果。基于物理的渲染更多的好处在于,可以让我们的场景在各种光照条件下都能得到令人满意的效果,同时不需要频繁地调整材质参数。
在Unity中,想要和全局光照、反射探针等内置功能良好地配合来得到出色的渲染结果,就需要使用Unity内置的Standard Shader。我们在18.2.2中学习了如何针对不同类别的物体来调整他们使用的材质属性。在本例中,我们使用了更复杂的纹理和模型,他们都来自于Unity官方的实例项目Viking Village。这些材质可以为我们制作自动材质提供一些参考,例如,场景中所有物体都使用了高光反射纹理(Specular Texture)、遮挡纹理(Occlusion Texture)、法线纹理(Normal Texture),一些材质还使用了细节纹理来提供更多的细节表现。
18.3.4 线性空间
在使用基于物理的渲染方法渲染整个场景时,我们应该使用线性空间(Linear Space)来得到最后的渲染效果。默认情况下,Unity会使用伽马空间(Gamma Space),如果要使用线性空间的话,我们需要在Edit——Project Settings——Player——Other Settings——Color Space中选择Linear选项。
线性空间可以得到更加真实的效果。但它的缺点在于,需要一些硬件支持来实现线性计算,但一些移动平台对它的支持并不好。这种情况下,我们往往只能退而求其次,选择伽马空间进行渲染和计算。
线性空间、伽马空间的区别和伽马校正(Gamma Correction)有关。实际上,当我们在默认的伽马空间下进行渲染计算时,由于使用了非线性的输入数据,导致很多计算都是在非线性空间下进行的,这意味着我们得到的结果并不符合真实的物理期望。除此之外,由于输出时没有考虑显示器的显示伽马的影响,会导致渲染出来的画面整体偏暗,总是和真实世界不像。

猜你喜欢

转载自blog.csdn.net/qq_39710961/article/details/79917950