UnityShader精要笔记二十三 Standard Shad

2022-01-23  本文已影响0人  合肥黑

本文继续对《UnityShader入门精要》——冯乐乐 第十八章 基于物理的渲染 进行学习

https://github.com/candycat1992/Unity_Shaders_Book中,冯乐乐给出了2019年改版后的第18章,本文基于此版本进行学习。

一、Unity 5 的 Standard Shader

当我们在 Unity 5 中新创建一个模型或是新创建一个材质时,其默认使用的着色器都是一个名为 Standard 的着色器。这个 Standard Shader 就使用了我们之前所讲的基于物理的渲染。

Unity 支持两种流行的基于物理的工作流程:金属工作流(metallic workflow)和高光反射工作流(specular workflow)。其中,金属工作流是默认的工作流程,对应的 Shader 为 Standard Shader。而如果想要使用高光反射工作流,就需要在材质的 Shader 下拉框中选择 Standard(Specular setup)。

需要注意的是,通常来讲,使用不同的工作流可以实现相同的效果,只是它们使用的参数不同而已。金属工作流也不意味着它只能模拟金属类型的材质,金属工作流的名字来源于它定义了材质表面的金属值(是金属类型的还是非金属类型的)。高光反射工作流的名字来源于它可以直接指定表面的高光反射颜色(有很强的高光反射还是很弱的高光反射)等,我们在 18.2 节中实现的自定义的 PBS 实际上也是按高光反射工作流的方式来定义的。而在金属工作流中这个颜色需要由漫反射颜色和金属值衍生而来。

在实际的游戏制作过程中,我们可以选择自己更偏好的工作流来制作场景,这更多的是个人喜好的问题。当然也可以同时混用两种工作流。而在内部实现上,这两种工作流实际上最终都会使用同一套 BRDF 模型,不同的是 BRDF 模型中各个输入参数的来源不同而已。

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

1.它们是如何实现的

Standard 和 Standard(Specular setup)的 Shader 源代码可以在 Unity 内置的 builtin_shaders-5.x/DefaultResourcesExtra 文件夹中找到,这些 Shader 依赖于 builtin_shaders-5.x/CGIncludes 文件夹中
定义的一些头文件。这些相关的头文件的名称大多类似于 UnityStandardXXX.cginc,其中定义了和 PBS 相关的各个函数、结构体和宏等。表 18.1 列出了这些头文件的名称以及它们的主要用处。

表 18.1 Unity 内置的与 PBS 相关的各个头文件以及相关描述

文 件 描 述
UnityStandardInput.cginc 声明了 Standard 和 Standard (Specular setup) Shader 使用的所有材质参数(如_Color、_MainTex、_EmissionMap),定义了顶点着色器的输入结构体VertexInput,还定义了相关辅助函数用于从这些输入中计算得到相关的材质变量,例如 Albedo 函数可以从_MainTex 和_Color 参数中计算得到漫反射颜色,Occlusion 函数可以从_OcclusionMap 中计算得到遮挡值
UnityLightingCommon.cginc 定义了和 PBS 光照相关的各个结构体,例如 UnityLight、UnityIndirect、UnityGI和 UnityGIInput 等
UnityStandardCore.cginc 定义了 Standard 和 Standard (Specular setup) Shader 使用的顶点/片元着色器( 如 vertForwardBase 和 fragForwardBase )、相关的结构体 ( 如VertexOutputForwardBase 和 FragmentCommonData ) 和 辅助函数(如MetallicSetup、SpecularSetup、MainLight、PerPixelWorldNormal、FragmentGI) 等
UnityStandardCoreForwardSimple.cginc 定义了简化版的 顶 点 / 片 元着色器 ( 如 vertForwardBaseSimple 和fragForwardBaseSimple)、相关的结构体(如 VertexOutputBaseSimple)和辅助函数(如 FragmentSetupSimple、MainLightSimple)等。在默认情况下,Unity会将 UNITY_STANDARD_SIMPLE(在 UnityStandardConfig.cginc 文件中被定义)设为 0,即不使用这些简化后的实现
UnityPBSLighting.cginc 定义了表面着色器使用的标准光照函数和 相关的结构体等,如LightingStandardSpecular 函数和 SurfaceOutputStandardSpecular 结构体,这些定义主要是为 Unity 表面着色器(Surface Shader)服务的。除此之外,还定义PBS 函数的调用宏 UNITY_BRDF_PBS,Unity 会根据当前平台设置等为UNITY_BRDF_PBS 设置不同性能的函数入口,如 BRDF1_Unity_PBS、BRDF2_Unity_PBS 和 BRDF3_Unity_PBS 等函数,而这些函数是在UnityStandardBRDF.cginc 文件中被定义的
UnityStandardBRDF.cginc 实现了 Unity 中基于物理的渲染技术,定义了 BRDF1_Unity_PBS 、BRDF2_Unity_PBS 和 BRDF3_Unity_PBS 等函数,来实现不同平台下的BRDF。这个文件包含了关键的 BRDF 模型的实现部分,包括漫反射项和高光反射项的函数实现(如 DisneyDiffuse、SmithJointGGXVisibilityTerm)和相关的辅助函数(如 常用的数学函数 Pow4 和 Pow5 、
PerceptualRoughnessToSpecPower)
UnityGlobalIllumination.cginc 定义了计算全局光照的 UnityGlobalIllumination 函数,该函数在 FragmentGI 函数(在 UnityStandardCore.cginc 中被定义)中被调用,它会从光照贴图、光照探针、反射探针等输入中读取数据,计算全局光照中的间接漫反射颜色和高光反射颜色,并存储 UnityGI 结构体中的 UnityIndirect 结构体变量中(两个结构体均在 UnityLightingCommon.cginc 中被定义)
UnityImageBasedLighting.cginc 定义了和基于图像的光照相关的结构体和函数,这些结构体和函数会在计算全局光照时被使用,例如 Unity_GlossyEnvironment 函数会采样反射探针中的数据,它可以用于计算间接的高光反射
UnityStandardUtils.cginc Standard Shader 使用的一些辅助函数,将来可能会移到 UnityCG.cginc 文件中
UnityStandardConfig.cginc 对 Standard Shader 的相关配置,例如默认情况下使用 GGX 模型来实现 BRDF(将 UNITY_BRDF_GGX 设为 1)
UnityStandardMeta.cginc 定义了 Standard Shader 中“LightMode”为“Meta”的 Pass(用于提取光照纹理和全局光照的相关信息)使用的顶点/片元着色器,以及它们使用的输入/输出结构体
UnityStandardShadow.cginc 定义了 Standard Shader 中“LightMode”为“ShadowCaster”的 Pass(用于投射阴影)使用的顶点/片元着色器,以及它们使用的输入/输出结构体

我们可以打开 Standard.shader 和 StandardSpecular.shader 文件来分析 Unity 是如何实现基于物理的渲染的。总体来讲,这两个 Shader 的代码基本相同—它们都定义了两个 SubShader,第一个 SubShader 使用的计算更加复杂,Unity 为其定义了前向渲染路径和延迟渲染路径使用的 Pass,以及用于投射阴影和提取元数据的 Pass;第二个 SubShader 也定义了 4 个 Pass,其中两个 Pass 用于前向渲染路径,一个 Pass 用于投射阴影,另一个 Pass 用于提取元数据,该 SubShader 和第一个SubShader 的主要区别在于取消了一些计算,例如不计算视差贴图、不计算软阴影等。

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


图 18.7 Standard Shader 中前向渲染路径使用的 Pass(简化版本的 PBS 使用了 VertexOutputBaseSimple 等结构体来代替相应的结构体)

从图 18.7 中可以看出,两个 Pass 的代码大体相同,只是 ForwardBase Pass 进行了更多的光照计算,例如,计算全局光照、自发光等效果,这些计算只需要在物体的整个渲染过程中计算一次即可,因此不需要在 FarwardAdd Pass 中再计算一次,这与我们之前学习前向渲染时的经验一致。

二、如何使用 Standard Shader

我们之前提到,Unity 5 的 Standard Shader 适用于各种材质的物体,但是,我们应该如何使用Standard Shader 来得到不同的材质效果呢?

我们首先来回答一个问题,为什么不同的材质看起来是如此不同呢?这需要回顾我们在 18.1节讲到的内容。我们知道,材质和光的交互可以分成漫反射和高光反射两个部分,其中漫反射对应了次表面散射的结果,而高光反射则对应了表面反射的结果。通过对金属材质和非金属材质的分析,我们可以得到它们的漫反射和高光反射的一些特点。

1.金属材质
2.非金属材质

但真实的材质大多混合了上面的这些特性,Unity 提供的工作流就是为了更加方便地让我们针对以上特性来调整材质效果。在 Unity 官方提供的示例项目 Shader Calibration Scene①中,Unity 提供了两个非常有参考价值的校准表格,如图 18.8 所示,它们分别对应了金属工作流和高光反射工作流使用的参考属性值,来方便我们针对不同类型的材质来调整参数。读者也可以在本书资源的Assets/Textures/Chapter18/Charts文件夹找到这两张校准表格。
https://www.assetstore.unity3d.com/en/#!/content/25422

我们以图 18.8 的左图,即金属工作流使用的校准表格为例,来解释如何使用这张校准表格来指导我们调整材质。在本书资源的场景文件 Scene_18_3 中,我们提供了一个简单的场景来展示不同材质的结果。图 18.9 显示了场景结果以及物体使用的材质。需要注意的是,读者需要在 Edit →Project Setttings→Player→Other Settings→Color Space 中选择 Linear 才可以得到和图 18.9 中相同的效果,这是因为基于物理的渲染需要使用线性空间(详见 18.3.4 节)来进行相关计算。

图 18.9 使用金属工作流来实现不同类型的材质。左边的球体:金属材质,右边的球体:塑料材质

在金属工作流中,材质面板中的 Albedo 定义了物体的整体颜色,它通常就是我们视觉上认为的物体颜色。从亮度来看,非金属材质的亮度范围通常在 50~243,而金属材质的亮度一般在 186 255之间。Unity 给的校准表格(见图 18.8 中的左图)中还给出了一些非金属材质和金属材质使用的示例 Albedo 属性值,我们可以直接使用这些示例值来作为材质属性。当然,也可以直接使用一张纹理作为材质的 Albedo 值。在我们的例子中,我们把金属材质(图 18.9 中的左边的球体)的 Albedo 设为银灰色,而把塑料材质(图 18.9 中的右边的球体)的设为蓝绿色。材质面板中的下一个属性是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 属性值。我们在 18.2 节中实现的自定义的 PBS 实际上就是遵循了高光反射工作流。

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

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

需要注意的是,尽管 Standard Shader 的材质面板有许多可供调节的属性,但我们不用担心由于没有使用一些属性而会对性能有所影响。Unity 在背后已经进行了高度优化,在我们生成可执行程序时,Unity 会检查哪些属性没有被使用到,同时也会针对目标平台进行相应的优化。这些逻辑是通过使用一个 C#脚本文件来自定义材质面板的行为来实现的,读者可以在 builtin_shaders-5.x/Editor/ StandardShaderGUI.cs 中找到这个文件。这个脚本的核心思想是通过判断用户是否设置了某一材质属性来决定是否开启相应的 shader feature,例如,如果用户没有设置法线纹理,那么该脚本就会关闭名为_NORMALMAP 的 shader feature,从而在 Shader 逻辑中跳过相关代码。

从上面的内容可以看出,要想得到可信度更高的渲染结果,我们需要对不同材质使用合适的属性值,尤其是一些重要的属性值,例如 Albedo、Metallic 和 Specular。当然,想要让整个场景的渲染结果令人满意,尤其包含了复杂光照的场景,仅仅有这些使用了 PBS 的材质是不够的,我们需要使用 Unity 提供的其他一些重要的技术,例如 HDR 格式的 Skybox、全局光照、反射探针、光照探针、HDR 和屏幕后处理等。更多内容读者阅读本章的扩展阅读部分。

三、一个更加复杂的例子

在本章最后,我们将以一个更加复杂的、基于物理渲染的场景结束,该场景对应了本书资源中的 Scene_18_3。本场景使用的元素大多来源于 Unity 官方的示例项目 Viking Village(https://www.assetstore.unity3d.com/jp/#!/content/29140),读者可以下载完整的项目来更加深入地学习 Unity 中 的 PBS。

图 18.10 展示了在不同光照条件下本例实现的效果。需要注意的是,读者需要在 Edit →Project Setttings→Player→Color Space 中选择Linear 才可以得到和图 18.9 中相同的效果,这是因为基于物理的渲染需要使用线性空间(详见 18.3.4 节)来进行相关计算。

那么,基于物理的 Standard Shader 是如何与其他 Unity 功能相互配合得到这样的场景呢?

图18.10 在Unity 5中使用基于物理的渲染技术,场景在不同光照下的渲染结果
1.设置光照环境

我们首先需要为场景设置光照环境。在默认情况下,Unity 5 中一个新创建的场景会包含一个默认的 Skybox。在本例中,我们使用一个自定义的 Skybox 来代替默认值。做法是,打开 Window→Lighting,在 Scene 标签页下把本例使用的 SunsetSkyboxHDR 拖曳到 Skybox 选项中,如图 18.11所示。


图18.11 光照面板下的Scene标签页

本例中的 Skybox 使用了一个 HDR 格式的 Cubemap,这与我们之前在 10.1 节中制作 Skybox 时使用的纹理不同。这需要解释 HDR(High Dynamic Range)的相关知识,我们将在 18.4.3 节更加详细地介绍 HDR 的原理和应用。但在这里,我们只需要知道,使用 HDR 格式的 Skybox 可以让场景中物体的反射更加真实,有利于我们得到更加可信的光照效果。

我们还可以设置场景使用的环境光照,这些环境光照可以对场景中所有的物体表面产生影响。在图 18.11 所示的设置面板中,我们可以选择环境光照的来源(Ambient Source 选项),是来自于场景使用的 Skybox,还是使用渐变值,亦或是某个固定的颜色。我们还可以设置环境光照的强度(Ambient Intensity 参数),如果想要场景中的所有物体不接受任何环境光照,可以把该值设为 0。

在使用了 Standard Shader 的前提下,如果我们关闭场景中所有的光源,并把环境光照的强度设为0,场景中的物体仍然可以接受一些光照,如图 18.12 中的左图所示。


图18.12 左图:当关闭场景中的所有光源并把环境光照强度设为0后,使用了Standard Shader的物体仍然具有光照效果。右图:在左图的基础上,把反射源设置为空,使得物体不接受任何默认的反射信息

那么,这些光照是从哪里来的呢?答案就是反射。默认的反射源(Reflection Source 选项)是场景使用的 Skybox。如果我们不想让场景中的物体接受任何默认的反射光照,可以把反射源设置为自定义(即 Custom),并把自定义的 Cubemap 保留为空即可(另一种方式是直接把场景使用的 Skybox 设置为空),如图 18.12 右图所示。

但为了得到更加逼真的渲染结果,我们通常是不会这样做的。在渲染实现上,即便场景中没有任何光源,Unity 在内部仍然会调用 ForwardBase Pass(假设使用的是前向渲染路径的话),并使用反射的光照信息来填充光源信息,再进行基于物理的渲染计算。读者可以通过帧调试器(Frame Debugger)来查看渲染过程。需要注意的是,这里设置的反射源是默认的反射源,如果我们在场景中添加了其他反射探针(Reflection Probes,见 18.3.2节),物体可能会使用其他反射源。当默认反射源是 Skybox 时,Unity 会由场景使用的 Skybox 生成一个 Cubemap,我们可以通过 Resolution 选项来控制它每个面的分辨率。

除了 Standard Shader 外,Unity 还引入了一个重要的流水线—实时全局光照(Global Illumination,GI)流水线。使用 GI,场景中的物体不仅可以受直接光照的影响,还可以接受间接光照的影响。直接光照指的是那些直接把光照射到物体表面的光源,在本书之前的章节中,我们使用的都是直接光照来渲染场景中的物体。但在现实生活中,物体还会受到间接光照的影响。

例如,想象一个红色墙壁旁边放置了一个球体,尽管墙壁本身不发光,但球体靠近墙的一面仍会有少许的红色,这是由于红色墙壁把一些间接光照投射到了球体上。在 Unity 中,间接光照指的就是那些被场景中其他物体反弹的光,这些间接光照会受反弹光的表面的颜色影响(例如之前例子中的红色的墙壁),这些表面会在反弹光线时把自身表面的颜色添加到反射光的计算中。在 Unity 5 中,我们可以使用这些直接光照和间接光照来创建更加真实的视觉效果。

下面,我们首先设置场景使用的直接光照—一个平行光。在 PBR(Physically Based Rendering)中,想要让渲染效果更加真实可信,我们需要保证平行光的方向和 Skybox 中的太阳或其他光源的位置一致,使得物体产生的光照信息可以与 Skybox 互相吻合。有时,我们可能会使用一张耀斑纹理(Flare Texture)来模拟太阳等光源,此时我们同样需要确保平行光的方向与耀斑纹理的位置一致。

与之类似的还有平行光的颜色,我们应该尽量让平行光的颜色和场景环境相匹配。例如,在图 18.10 的左图中,场景的光照环境为日落时分,因此平行光的颜色为浅黄色,如图 18.13 所示,而在图 18.10 的右图中,场景的光照环境更接近傍晚,此时平行光的颜色为淡蓝色。我们还在Skybox 的材质面板上调整天空的旋转角度及曝光度,来调整场景的背景。


图18.13 使用的平行光

在平行光面板的烘焙选项(即 Baking)中,我们选择了 Realtime 模式,这意味着,场景中受平行光影响的所有物体都会进行实时的光照计算,当光源或场景中其他物体的位置、旋转角度等发生变化时,场景中的光照结果也会随之变化。然而,实时光照往往需要较大的性能消耗,对于移动平台这样资源比较短缺的平台,我们可以选择 Baked 模式,此时,Unity 会把该光源的光照效果烘焙到一张光照纹理(lightmap)中,这样我们就不用实时为物体计算复杂的光照,而只需要通过纹理采样来得到光照结果。

选择烘焙模式的缺点在于,如果场景中的物体发生了移动,但是它的阴影等光照效果并不会发生变化。烘焙选项中的 Mix 模式则允许我们混合使用实时模式和烘焙模式,它会把场景中的静态物体(即那些被标识为 Static 的物体)的光照烘焙到光照纹理中,但仍然会对动态物体产生实时光照。

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

图18.14 左图:将Bounce Intensity设置为0,物体不再受到间接光照的影响,木屋内阴影部分的可见细节很少。右图:将Bounce Intensity设为8,阴影部分的细节更加清楚

除了上述调整单个光源的间接光照强度,我们也可以对整个场景的间接光照强度进行调整。

这是按照图 18.11 所示的光照面板来实现的。在光照面板的 Scene 标签页下,我们可以调整 General GI 参数块中的 Bounce Boost 参数来控制场景中反射的间接光照的强度,它会和单个光源的 Bounce Intensity 参数来一起控制间接光照的反射强度。除此之外,把 Indirect Intensity 参数调大同样可以增大间接光照的强度。需要注意的是,间接光照还有可能来自一些自发光的物体。

2.放置反射探针

回忆我们在 10.1 节中讲到的环境映射,在实时渲染中,我们经常会使用 Cubemap 来模拟物体的反射效果。例如,在赛车游戏中,我们需要对车身或车窗使用反射映射的技术来模拟它们的反光材质。然而,如果我们永远使用同一个 Cubemap,那么,当赛车周围的场景发生较大变化时,就很容易出现“穿帮镜头”,因为车身或车窗的环境反射并没有随着环境变化而发生变化。

一种解决办法是可以在脚本中控制何时生成从当前位置观察到的 Cubemap,而 Unity 5 为我们提供了一种更加方便的途径,即使用反射探针(Reflection Probes)。反射探针的工作原理和光照探针(Light Probes)类似,它允许我们在场景中的特定位置上对整个场景的环境反射进行采样,并把采样结果存储在每个探针上。当游戏中包含反射效果的物体从这些探针附近经过时,Unity 会把从这些邻近探针存储的反射结果传递给物体使用的反射纹理。如果物体周围存在多个反射探针,Unity 还会在这些反射结果之间进行插值,来得到平滑渐变的反射效果。

实际上,Unity 会在场景中放置一个默认的反射探针,这个反射探针存储了对场景使用的 Skybox 的反射结果,来作为场景的环境光照(见 18.3.1 节)。如果我们需要让场景中的物体包含额外的反射效果,就需要放置更多的反射探针。

反射探针同样有 3 种类型:

我们在本节使用的场景中放置了 3 个反射探针,它们的类型都是 Baked(前提是我们把场景中的物体标识成了 Static)。使用反射探针前后的对比效果如图 18.15 所示。

需要注意的是,在放置反射探针时,我们选取的位置并不是任意的。通常来说,反射探针应该被放置在那些具有明显反射现象的物体的旁边,或是一些墙角等容易发生遮挡的物体周围。在本例使用的场景中,木屋内的盾牌具有比较明显的反射效果,而盾牌本身又被木屋遮挡,因此,其中一个反射探针的位置就在盾牌附近。当我们放置好探针后,我们还需要为它们定义每个探针的影响区域,当反射物体进入到这个区域后,反射探针就会对物体的反射产生影响。

通常情况下,反射探针的影响区域之间往往会有所重叠,例如,本例中盾牌附近的反射探针和另外两个(一个在木屋前方,一个在木屋后方)的影响区域都有所重叠。此时,Unity 会计算反射物体的包围盒与这些重叠区域的交叉部分,并据此来选择使用的反射映射。如果当前的目标平台使用的是 SM 3.0及以上的话,Unity 还可以允许我们在这些互相重叠的反射探针之间进行混合,来实现平缓的反射过渡效果。

使用 Unity 内置的反射探针的另一个好处是,我们可以模拟互相反射(interreflections)。我们曾在 10.1 节中讲到使用传统的 Cubemap 方法无法模拟互相反射的效果。例如,假设场景中有两面互相面对面的镜子,在理想情况下,它们不仅会反射自己对面的那面镜子,也会反射那面镜子里反射的图像。只要反射光线没有被完全吸收,反射就会一直进行下去。要实现这种效果,就需要追踪光线的反射轨迹,这是传统的反射方法所无法实现的。Unity 5 引入的 GI 系统让这种效果变成了可能,我们在本书资源的 Scene_18_3_2 场景中展示了这样的一个例子,如图 18.16 所示。我们可以在图 18.16 中看到,两个金属反射的图像包含了两次互相反射的效果。


图18.15 左图:未使用反射探针。右图:在场景中放置了两个反射探针,注意墙上的盾牌与左图的差别
图18.16 使用反射探针实现相互反射的效果

在图 18.16 所示的场景中,我们在每个金属球的位置处放置了一个反射探针,并把每个金属球上的 Mesh Renderer 组件中的 Reflection Probes 设置为 Simple,这样保证它们只会使用离它们最近的一个反射探针。

默认情况下,反射探针只会捕捉一次反射,也就是说,左边金属球使用的反射探针只会捕捉到由右边的金属球第一次反射过来的光线。但在理想情况下,反射过来的光线会继续被左边的金属球反射,并对右边的金属球造成影响。Unity 允许我们控制物体之间这样来回反射的次数,这可以通过改变图 18.11 中的 Reflection Bounces 参数来实现。在图 18.16 所示的场景中,我们把该值设为了 2。

然而,正如本节一开始所提到的,使用反射探针往往会需要更多的计算时间。这些探针实际上也是通过在它的位置上放置一个摄像机,来渲染得到一个 Cubemap。如果我们把反弹次数设置的很大,或是使用实时渲染,那么这些探针很可能会造成性能瓶颈。更多关于如何优化反射探针以及它的高级用法,读者可以参见 Unity 的官方手册(http://docs.unity3d.com/Manual/ReflectionProbes.html)。

3.调整材质

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

在 Unity 中,要想和全局光照、反射探针等内置功能良好地配合来得到出色的渲染结果,就需要使用 Unity 内置的 Standard Shader。我们已经在 18.2.2 节中学习了如何针对不同类别的物体来调整它们使用的材质属性。在本例中,我们使用了更复杂的纹理和模型,它们都来自于 Unity 官方的示例项目 Viking Village。这些材质可以为读者制作自己的材质提供一些参考,例如,场景中所有物体都使用了高光反射纹理(Specular Texture)、遮挡纹理(Occlusion Texture)、法线纹理(Normal Texture),一些材质还使用了细节纹理来提供更多的细节表现。

4.线性空间

在使用基于物理的渲染方法渲染整个场景时,我们应该使用线性空间(Linear Space)来得到最好的渲染效果。默认情况下,Unity 会使用伽马空间(Gamma Space),如果要使用线性空间的话,我们需要在 Edit→Project Settings→Player→Other Settings→Color Space 中选择 Linear 选项。图 18.17显示了分别在线性空间和伽马空间下场景的渲染结果。

图18.17 左图:在线性空间下的渲染结果。右图:在伽马空间下的渲染结果

从图 18.17 中可以看出,使用线性空间可以得到更加真实的效果。但它的缺点在于,需要一些硬件支持来实现线性计算,但一些移动平台对它的支持并不好。这种情况下,我们往往只能退而求其次,选择伽马空间进行渲染和计算。

那么,线性空间、伽马空间到底是什么意思?为什么线性空间可以得到更加真实的效果呢?这就需要介绍伽马校正(Gamma Correction)的相关内容了。实际上,当我们在默认的伽马空间下进行渲染计算时,由于使用了非线性的输入数据,导致很多计算都是在非线性空间下进行的,这意味着我们得到的结果并不符合真实的物理期望。除此之外,由于输出时没有考虑显示器的显示伽马的影响,会导致渲染出来的画面整体偏暗,总是和真实世界不像。

尽管在 Unity 中我们可以通过之前所说的步骤直接选择在线性空间进行渲染,Unity 会在背后为我们照顾好一切,但了解伽马校正的原理对我们理解渲染计算有很大帮助,读者可以在 18.4.2节找到更多的解释。

上一篇下一篇

猜你喜欢

热点阅读