(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)

一、unity 5中的standard shader

在unity5中新创建一个模型或是新创建一个材质时,默认使用的着色器都是一个名为standard 的着色器。这个standard shader使用的就是之前所讲的基于物理的渲染。

unity支持两种流行的基于物理的工作流程:金属工作流和高光反射工作流。其中,金属工作流是默认的工作流程,对应的shader为standard shader。而如果想要使用高光反射工作流,需要在材质的shader下拉框选择Standard(Specular setup)。需要注意的是,使用不同的工作流可以实现相同的效果,只是它们使用的参数不同而已。金属工作流也不意味着它只能模拟金属类型的材质,名字源于它定义了材质表面的金属值(是金属类型的还是非金属类型的)。高光反射工作流的名字源于它可以直接指定表面的高光反射颜色(有很强的高光反射还是很弱的)等,而在金属工作流中这个颜色需要由漫反射颜色和金属值衍生出来。在实际游戏制作过程中,我们可以选择自己更偏好的工作流来制作场景,也可以混合使用。

下面的内容中,我们使用standard shader来统称standard和standard(Specular setup)着色器。unity提供的standard shader允许我们使用这一种shader来为场景中所有物体进行着色,而不需要考虑它们是否是金属材质还是塑料材质,从而大大减少我们不断调整材质参数所花费的时间。

1.1它们是如何实现的

standard和standard(Specular setup)的源码可以在网上下载,这些shader依赖于一些头文件,这些相关头文件名称大多类似于UnityStandardXXX.cginc,其中定义了和PBS相关的各个函数、结构体和宏等,如下表:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第1张图片

可以打开standard.shader和standardSpecular.shader文件分析unity是如何实现基于物理的渲染的。总的来说,这两个shader代码基本相同——都定义了两个subshader,第一个subshader使用的计算更加复杂,主要针对非移动平台(通过#pragma exclude_renderers gles代码来排除GLES平台),并定义了前项渲染路径和延迟渲染路径使用的pass,以及用于投射阴影和提取元数据的pass;第二个subshader定义了4个pass,其中两个pass 用于前项渲染路径,一个pass用于投射阴影,一个pass用于提取元数据,该subshader主要针对移动平台。

Standard.shader和standardSpecular.shader最大不同之处在于,它们在设置BRDF的输入时使用了不同的函数来设置各个参数,——基于金属工作流的standard shader使用MetallicSetup函数来设置各个参数,基于高光反射工作流的Standard(specular setup) shader使用SpecularSetup函数来设置。这俩函数均在UnityStandardCore.cginc文件中被定义。下图给出了standard shader用于前项渲染路径的典型实现,这是对内置文件的分析所得:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第2张图片

从上图看出,两个pass代码大体相同,只是forward Pass进行了更多光照计算,例如全局光照、自发光等效果,这些计算只需要在物体的整个渲染过程中计算一次即可, 因此不需要在FarwardAddPass中再计算一次。

1.2如何使用standard shader

之前说到unity 5中的standard shader适用于各种材质的物体,但怎么使用standard shader得到不同的材质效果?

为什么不同的材质看起来如此不同?材质和光的交互可以分为漫反射和高光反射两个部分,其中漫反射对应了次表面散射的结果,而高光反射对应了表面反射的结果。通过对金属材质和非金属材质的分析,可以得到它们的漫反射和高光反射的一些特点。

1.2.1金属材质

(1)几乎没有漫反射,因为所有被吸收的光都会被自由电子立刻转化为其他形式的能量。

(2)有非常强烈的高光反射。

(3)高光反射通常是由颜色的,例如金子的反光颜色为黄色。

1.2.2非金属材质

(1)大多数角度高光反射的强度比较弱,但在掠射角时高光反射强度反而会增强,即菲涅耳现象;

(2)高光反射的颜色比较单一。

(3)漫反射的颜色多种多样。

但真实的材质大多混合了上面的这些特性,unity提供的工作流就是为了更方便地让我们针对以上特性来调整材质效果。unity有提供针对两种工作流的校准表格,针对不同类型的材质来调整参数。

以金属工作流使用的校准表格为例,解释如何使用它来指导我们调整材质。如下图所示:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第3张图片

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第4张图片

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第5张图片

左边球体是金属材质,右边是塑料材质。注意,我们需要在PlayerSetting里的Other Setting->color space中选择Linear才可以得到上图效果,这是因为基于物理的渲染需要使用线性空间来进行相关计算。

在金属工作流中,材质面板中的Albedo定义了物体的整体颜色,它通常就是我们视觉上认为的物体颜色。从来亮度来看,非金属材质的亮度范围通常在50-243,而金属材质亮度一般在188-255之间,可以用unity校准表格里的推荐Albedo值,我们这里把金属材质Albedo设为银灰色,塑料材质设为蓝绿色。材质面板中的下一个属性是Metallic,它定义了该物体表面看起来是否更像金属或非金属。同样,可以使用一张纹理来采样得到表面的Metallic值,此时纹理中的R通道值将对应了Metallic值。我们的例子中把金属材质的Metallic值设为1,表明该物体几乎完全是一个金属材质,同时塑料材质Metallic值设为0,表明该物体几乎没有任何金属特性。最后一个重要的材质属性是Smoothness,他是Metallic的附属值,定义了从视觉上看该表面的光滑程度。如果我们在设置Metallic属性时使用的是一张纹理,那么这张纹理的A通道就对应了表面的Smoothness值(此时纹理的GB通道则被忽略)。我们把金属材质的Smoothness值设置为相对较大的0.7,表面该金属表面比较光滑,而把塑料材质Smoothness设为0.4,表面该塑料比较粗糙。

高光反射工作流使用的面板和上述工作流使用的基本相同,只是使用了不同含义的Albedo属性,并使用Specular代替了上述的Metallic属性。在高光反射工作流中,材质的Albedo属性定义了表面的漫反射强度。对于非金属材质,它的值仍然是视觉上认为的物体颜色,但对于金属材质,Albedo的值通常非常接近黑色(金属材质几乎不存在次表面散射现象)。高光反射工作流Specular属性则定义了表面的高光反射强度。非金属材质通常使用一个灰度值范围在0-55的深灰色作为Specular值,表明非金属材质的高光反射较弱。金属材质则通常会使用视觉上认为该金属的颜色作为它的Specular值。Specular属性同样也有一个子属性Smoothness,它定义了从视觉上来看该表面的光滑程度,和上面的金属工作流类似,如果使用了一张纹理来为Specular属性赋值,那么纹理的RGB通道对应了Specular的值,A通道对应了Smoothness属性值。

上述材质属性都属于材质面板中的Main Maps部分,除了这些属性,还包含了其他材质属性,例如切线空间下的法线纹理、遮挡纹理、自发光纹理等。Main Maps部分下面还有一个Secondary Maps属性部分,这个部分属性是用来定义额外的细节信息,这些细节通常会直接绘制在Main Maps的上面,来为材质提供更多的微表面或细节表现。

除了上述属性,我们还可以为Standard shader选择它使用的渲染模式,即材质面板上的RenderMode选项,它支持4种渲染模式:Opaque、Cutout、Fade和Transparent。其中,Opaque用于渲染最常见的不透明物体,这也是默认的渲染模式。对于像玻璃这样的材质,可以选择Transparent模式,在这个渲染模式下,Albedo属性的A通道用于控制材质的透明度。而在Cutout渲染模式下,Albedo属性中纹理的A通道会成为一个掩码纹理,而他的子属性Alpha Cutoff将是透明度测试时使用的阈值。Fade模式和Transparent模式是类似的,不同的是,在Transparent模式下,当材质的透明度不断降低时,它的反射仍然保留,而Fade模式所有渲染效果都会逐渐从屏幕上淡出。

需要注意的是,尽管Standard shader的材质面板有许多可供调节的属性,但我们不用担心由于没有使用一些属性而对性能有所影响。unity在背后已经进行了高度优化,在我们生成可执行程序时,unity会检查哪些属性没有被使用到,同时也会针对目标平台进行相应优化。

想要得到更理想的渲染结果,我们需要对不同的材质使用合适的属性值,尤其是一些重要属性值,例如Albedo、Metallic、Specular。当然要让整个场景的渲染结果令人满意,尤其包含了复杂光照的场景,仅仅有这些使用了PBS的材质是不够的,我们需要使用unity提供的一些其他重要技术,例如HDR格式的Skybox、全局光照、反射探针、光照探针、HDR和屏幕后处理等。

二、一个更复杂的例子

下面用一个比较复杂、基于物理渲染的例子来更深入学习PBS。我们使用基于物理的Standard shader与其他unity功能相互配合得到更好的效果。

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第6张图片

2.1设置光照环境

我们首先使用一个HDR格式的Cubemap当做天空盒,替换掉unity的默认天空盒。我们下篇文章中会详细介绍HDR的原理和应用。现在只需要知道,使用HDR格式的Skybox可以让场景中物体的反射更加真实,有利于我们得到更加可信的光照效果。

我们还可以设置场景的环境光:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第7张图片

可以选择环境光照来源是场景使用的天空盒,还是使用渐变值,亦或是某个固定颜色。还可设置环境光强度(Intensity参数),如果想要场景中不接受任何环境光照,可以把该值设为0。在使用standard shader前提下,如果我们关闭了场景中所有光源,并把环境光强度设置为0,物体仍然可以接受一些光照,如下图:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第8张图片 

那么这些光照是哪里来的呢?答案就是反射。默认的反射源(Relection Source选项)是场景中使用的skybox。如果不想让场景中物体接受任何默认的反射光照,可以把反射源设为自定义(Custom)并把自定义的cubema保留为空即可(或者直接把场景中使用的skybox设置为空)。一般为了得到更加逼真的渲染结果,不会这么做。在渲染实现上,即使场景中没有任何光源,unity在内部仍然会调用ForwardBase Pass(假设使用前向渲染路径),并使用反射的光照信息来填充光源信息,再进行基于物理的渲染计算。需要注意这里设置的反射源是默认的反射源,当默认反射源是skybox时,unity会由场景使用的skybox生成一个cubemap,可以通过Resolution选项来控制它每个面的分辨率。

除了standard shader外,unity还引入了一个重要的流水线——实时全局光照(Global Illumination,GI)流水线。使用GI,场景中的物体不仅可以受直接光照影响,还可以受间接光照影响。直接光照指的是那些直接把光照射到物体表面的光源,之前的章节我们都使用的直接光照来渲染整个场景的物体。但在现实生活中,物体还会受到间接光照的影响。举个例子,一个球放在一面红色的墙旁边,尽管墙壁本身不发光,但球体靠近墙的一面仍会由少许的红色,这是由于红色墙壁把一些间接光照投射到了球体上。在unity中,间接光照指的是那些被场景中其他物体反弹的光,这些间接光照会受反弹光的表面的颜色影响,这些表面会在反弹光线时把自身表面的颜色添加到反射光的计算中,在unity5中,我们可以使用这些直接光照和间接光照来创建更加真实的视觉效果。

下面首先设置场景中使用的直接光照——一个平行光。在PBR(physically based rendering)中,想要让渲染效果更加真实可信,我们需要保证平行光的方向和Skybox中的太阳或者其他光源的位置一致,使得物体物体产生的光照信息可以与skybox互相吻合。有时可能会使用一张耀斑纹理(Flare Texture)来模拟太阳等光源,此时我们同样需要确保平行光的方向与耀斑纹理的位置一致。与之类似的还有平行光的颜色,我们应该尽量让平行光的颜色和场景环境相配,比如傍晚平行光为浅黄色,日落为淡蓝色等。我们还再Skybox的材质面板上调整天空的旋转角度及曝光度,来调整场景的背景。

在平行光面板的烘焙选项中(Baking)我们选择的Realtime模式,意味着场景中受平行光影响的所有物体都会进行实时光照计算,当光源或其他物体位置、旋转灯发生变化,场景中光照结果也会随之变化。然而实时光照往往需要较大的性能消耗,对于移动平台这样资源比较短缺的平台,我们可以选择Baked模式,此时unity会把该光源的光照效果烘焙到一张光照纹理中(lightmap),这样就不用实时为物体计算复杂的光照,而只需要通过纹理采样来得到光照结果。择烘焙模式缺点在于,如果场景物体发生了移动,但它的阴影等光照效果并不会发生变化。烘焙选项中的Mix模式则允许我们混合使用实时模式和烘焙模式,它会把场景中的静态物体(即那些被标识为static的物体)的光照烘焙到光照纹理中,但仍然会对动态物体产生实时光照。

unity5引入了实时间接光照的功能,在这个系统下,场景中的直接光照会在场景中各个物体之间来回反射,产生间接光照。间接光照可以让那些没有直接被光源照亮的物体同样可以接受到一定的光照信息,这些光照是由它周围的物体反射到它的表面上的。当第一条光线从光源被发射出来后,他会与场景中一些物体相交,第一个和光线相交的物体受到的光照即为直接光照。此后与该光线相交的物体,就会受到间接光照的影响,同时它们也会继续反射。经过多次反射后,该光线最后完全消失。这些间接光照的强度是由GI系统计算得到的默认亮度值,光源面板中的Bounce Intensity参数可以让我们调节这些间接光照的强度。当我们把它设为0时,意味着一条光线会和一个物体相交,不再被继续反射,物体只受直接光照的影响。

除了上述单个光源的间接光照强度调整,也可以对整个场景间接光照强度进行调整。在光照面板LightSetting中的Scene标签页下,我们可以调整General GI参数块中的Bounce Boost参数来控制场景中反射的间接光照强度,他会和单个光源的Bounce Intensity参数来一起控制间接光照的反射强度。除此以外,把Indirect Intensity参数调大同样可以增大间接光照的强度,需要注意的是,间接光照还有可能来自一些自发光的物体。

2.2放置反射探针

在实时渲染中,经常会使用cubemap来模拟物体的反射效果,但永远使用同一个cubemap,当场景发生变化较大时,就很容易穿帮镜头,比如正在开车,车身或车窗的环境反射并没有随着环境的变化而变化,一种解决方法是可以在脚本中控制何时生成从当前位置观察到的cubemap,然而unity 5为我们提供了一种更加方便的途径,即使用反射探针(Reflection Probes)。反射探针的工作原理和光照探针(Light Probes)类似,它允许我们在场景中特定位置上对整个场景的环境反射进行采样,并把采样结果存储在每个探针上,当游戏中包含反射效果的物体从这些探针附件进过时,unity会把这些邻近探针存储的反射结果传递给物体使用的反射纹理。如果物体周围存在多个反射探针,unity还会在这些反射结果直接进行插值,来得到平滑渐变的反射效果。实际上,unity会在场景中放置一个默认的反射探针,这个反射探针存储了对场景使用的skybox的反射结果,来作为场景的环境光照。如果我们需要让场景中的物体包含额外的反射效果,就需要放置更多的反射探针。

反射探针由三种类型:

(1)Baked,通过提前烘焙来得到该位置使用的cubemap的,在游戏运行时反射探针存储的cubemap并不会发生变化,需要注意,这种类型的反射探针在烘焙时同样只会处理那些静态物体。(即那些被标识为Reflection Probe Static的物体)。

(2)Realtime,它需要花费更多的处理时间,因此使用时应该非常小心它们的性能,幸运的是unity允许我们从脚本中通过触发来精确控制反射探针的更新。

(3)Custom,既可以让我们从编译器中烘焙它,也可以让我们使用一个自定义的cubemap来作为反射映射,但自定义的cubemap不会被实时更新。

需要注意的是,在放置反射探针时,选取的位置并不是任意的,应被放置那些具有明显反射现象的物体的旁边,或是一些墙角等容易发生遮挡的物体周围。当放置好反射探针后,需要为它们定义每个探针的影响区域,当反射物体进入到这个区域就对物体反射产生影响。通常情况下,反射探针的影响区域之间往往会有所重叠,此时unity会计算反射物体的包围盒与这些重叠区域的交叉部分,并据此选择使用的反射映射。如果当前目标平台使用的是SM3.0及以上,unity还允许我们在这些相互重叠的反射探针之间进行混合,来实现平缓的反射过渡效果。

使用unity反射探针另一个好处是,可以模拟互相反射。传统的cubemap方法无法模拟互相反射效果,比如两个面对面的镜子,理想情况下不仅会反射自己对面那面镜子,还会反射镜子里反射的图像。要实现这种效果,就需要追踪光线的反射轨迹,这是传统反射方法无法实现的,unity 5引入的GI系统让这种效果变成了可能,如下图:

(十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)_第9张图片

在这个场景我们每个金属球位置处放置了一个反射探针,并把每个金属球上mesh render组件中的reflection Probes设为为simple,保证他们只会使用离他们最近的一个反射探针。默认情况下,反射探针只会捕捉一次反射,左边金属球使用的反射探针只会捕捉到右边金属球第一次反射过来的光线。但理想情况下,反射过来的光线会继续被左边金属球反射,并对右边金属球造成影响。unity允许我们控制物体之间这样来回的反射次数,通过改变Lighting Setting里面的Reflection Bounces参数来实现。

使用反射探针往往需要更多的计算时间,这些探针实际上也是通过在它的位置上放置一个摄像机,来渲染得到一个cubemap,如果把反弹次数设置很大,或使用实时渲染,这些探针可能造成性能瓶颈。

2.3调整材质

要得到真实可信的渲染效果,需要为场景中的物体指定合适的材质,基于物理的渲染并不意味着要一定模拟像照片真实的效果,更多好处在于可以让我们的场景在各种光照条件下都能得到令人满意的效果,同时不需要频繁地调整材质参数。

在unity中,要想和全局光照、反射探针等内置功能良好地配合来得到出色的渲染结果,就需要使用unity内置的standard shader,可以通过查看官方给的Viking Village Demo对这些效果很好的材质提供一些参考,例如场景中所有物体都使用了高光反射纹理、遮挡纹理、法线纹理,一些材质还使用了细节纹理来提供更多的细节表现。

2.4线性空间

在使用基于物理的渲染方法渲染整个场景时,应该使用线性空间(Linear Space)来得到最好的渲染结果,在Player Setting->Color Space中选择Linear选项。可以得到更加真实的效果,但他缺点在于,需要一些硬件支持来实现线性计算,但一些移动平台对他的支持并不好,这种情况下往往只能退求其次,使用伽马空间进行渲染和计算。

当我们在默认的伽马空间下进行渲染计算时,由于使用了非线性输入数据,导致很多计算都是在非线性空间下进行的,这意味着我们得到的结果并不符合真实的物理期望。除此之外,由于输出时没有考虑显示器的显示伽马的影响,会导致渲染出来的画面整体偏暗,总和真实世界不像。后面我们会了解伽马校正的原理。

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