Unity可编程渲染管线系列(七)反射(镜面和环境)

目录

1 高亮的高光

1.1 光滑度

1.2 表面数据

1.3 漫反射光

1.4 镜面选项

1.5 镜面高光

1.6 逐物体平滑度

2 反射环境

2.1 采样环境

2.2 调制反射

2.3 菲涅尔

2.4 金属表面

3 非同一的缩放值

3.1 移除假设条件

4 反射探针

4.1 盒投影

4.2 探针混合

4.3 HDR解码

5 透明表面

5.1 预乘Alpha

5.2 预设

本文重点:

1、添加镜面高光

2、反射环境

3、支持非同一的缩放

4、和反射探针一起工作

5、结合反射和透明度

这是涵盖Unity的可脚本化渲染管道的教程系列的第七部分。它涵盖了反射,镜面高光和采样反射探针的添加。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。

本教程使用Unity 2018.3.0f2制作。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第1张图片

(倒影生动起来)

1 高亮的高光

到目前为止,我们仅使用了简单的漫射照明,没有任何照明。我们现在将改变它,增加对镜面照明的支持。我们将使用与Unity的Lightweight渲染管道中使用的模型相同的模型。“渲染4,拳头照明”教程介绍了镜面照明的原理,但使用了不同的模型。

1.1 光滑度

完美的漫反射表面(即完美的漫射器)根本没有镜面反射。表面是如此粗糙,以至于没有聚焦反射发生。表面越光滑,反射的光线就越多,而不是扩散。我们将通过从0到1的平滑度材料属性(默认值为?)进行控制。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第2张图片

(光滑度 滑动条)

添加相应的着色器变量。

1.2 表面数据

囊括镜面反射会使我们的照明模型更加复杂。让我们将其所有代码放在一个单独的文件Lighting.hlsl中。在里面定义一个LitSurface结构,该结构包含执行照明计算所需的所有表面数据。那就是表面的法线,位置,漫反射和镜面反射的颜色及其粗糙度。还会包含视图方向,它实际上不是表面的属性,但是相对于我们正在使用的表面点而言是恒定的。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第3张图片

添加一个GetLitSurface函数,该函数返回Lit的表面数据。在此HLSL文件中,我们将不依赖于着色器特定的属性,纹理或定义,因此必须将所有相关数据定义为参数。尽管我们把平滑度作为输入,但是在内部,我们使用粗糙度,只是反过来而已。此外,我们现在使用金属工作流程并将自己局限于电介质材质。介电材质是非金属的,仅反射少量的光,相当于平均0.04的灰度镜面反射。就像Unity的光照代码一样,我们将对其进行硬编码。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第4张图片

根据迪斯尼模型(Disney model),此时我们拥有的粗糙度实际上是一个称为感知粗糙度的值。物理粗糙度是其平方。我们都需要。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第5张图片

在Lit.hlsl中包含Lighting.hlsl。

现在,在执行任何光照计算之前,我们可以在LitPassFragment中获取表面。视线方向就是相机位置减去片段位置(归一化)。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第6张图片

1.3 漫反射光

我们将把照明计算移到Lighting.hlsl。为此,为其提供一个LightSurface函数,以表面和光的方向为参数。它执行漫反射点乘积,并将其乘以表面的漫反射色。我们假设这里是理想的光线,纯白色而没有衰减。

调整MainLight,使其仅将表面作为参数,而不是位置和法向矢量。让它调用LightSurface而不是执行点乘积本身。和以前一样,在此处应用衰减和光色。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第7张图片

以相同的方式调整DiffuseLight,将其重命名为GenericLight,从现在开始可以更好地描述其目的。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第8张图片

现在,LitPassFragment必须将表面作为参数传递。而且,漫反射现在也沿途(而不是在末尾)计入颜色。在开始时,我们仍然必须将其纳入顶点照明。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第9张图片

我们还必须对顶点灯光使用GenericLight。顶点照明是纯漫反射的,因此仅需要位置和法线。颜色为白色,其他值无关紧要。让我们为Lighting.hlsl添加一个方便的包装函数。

然后在LitPassVertex中使用它。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第10张图片

1.4 镜面选项

在添加镜面高光之前,让我们可以跳过它,以便仍然支持完美的漫反射材质。我们将通过添加到表面并作为GetLitSurface的参数的布尔字段来指示默认情况下将其设置为false。这个想法是,你可以对参数进行硬编码,或者使其依赖于着色器关键字。

如果是完美的散光器(diffuser),则平滑度和镜面反射度应始终为零。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第11张图片

同样,我们可以使所有镜面反射计算都以该布尔为条件。始终只需要最终的点积。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第12张图片

这正是我们顶点照明所需要的。

1.5 镜面高光

镜面反射高光背后的想法是,它代表纯粹的反射。光线照射到表面,并且其中一些反射。如果相机对准的话,使其与反射完美匹配,那么它将看到光线,否则看不到。但是,如果表面不是十分光滑,则反射会散布一些,因此即使未完全对准,照相机仍会看到一些反射。表面越粗糙,过渡区域越大。这样就创建了镜面高光。

Unity的轻量级渲染管线使用修改后的简约CookTorrance BRDF计算高光。我们将执行相同的计算。有关详细信息和链接,请参见Lightweight RP软件包中的Lighting.hlsl。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第13张图片

被反射的光也不会散射,因此,除非表面是完美的散射体,否则在GetLitSurface中相应地减少散射体的颜色。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第14张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第15张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第16张图片

(有和没有镜面高光)

1.6 逐物体平滑度

我们制作了InstancedColor组件,因此我们可以为每个对象提供自己的颜色,而无需使用单独的材质。为了实现平滑,我们也可以这样做。可以使用相同的组件来做这两个事情,但是我们应该重命名它以更好地描述其更通用的功能。

首先,在“Project”窗口中将资产文件从InstancedColor重命名为InstancedMaterialProperties,然后在代码中重命名类定义并添加平滑度。按此顺序进行操作可使Unity编辑器保持所有组件引用完整。否则,你必须修复丢失的脚本引用。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第17张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第18张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第19张图片

(逐物体变化的光滑度)

为了保持GPU实例化,我们必须将smoothness变量移至实例化缓冲区。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第20张图片

并使用UNITY_ACCESS_INSTANCED_PROP宏检索它。

2 反射环境

镜面高光表示光源的直接反射。但是,光(直接和间接)都可以来自各个方向。我们无法实时跟踪所有这些反射,因此我们将对包含环境照明的立方体贴图进行采样。

2.1 采样环境

默认环境是skybox。Unity通过unity_SpecCube0在着色器中将其变为可用。这是一个立方体贴图纹理资源,我们可以通过TEXTURECUBE宏以及samplerunity_SpecCube0采样器状态进行定义。在Lighting.hlsl中定义这些参数以及其他变量和资源,因为我们如何获取环境数据的细节与照明计算无关。

再次在Lit.hlsl中,以表面为参数定义SampleEnvironment函数。它将按照“渲染8,反射”中所述的相同方式对立方体贴图进行采样。首先,使用具有负视图方向和表面法线的反射函数来获取反射向量。然后调用PerceptualRoughnessToMipmapLevel来基于感知粗糙度找到正确的Mip级别。之后,我们可以使用SAMPLE_TEXTURECUBE_LOD宏对立方体贴图进行采样,并将结果用作最终颜色。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第21张图片

PerceptualRoughnessToMipmapLevel在ImageBasedLighting.hlsl核心RP库文件中定义。粗糙表面会产生模糊反射,我们可以通过选择适当的Mip级别来获得模糊反射。Unity生成特殊的Mipmap,以表示散射的反射。

要检查是否可行,请在LitPassFragment的末尾对环境进行采样,并将其用作最终颜色,以覆盖照明。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第22张图片

结果是统一的灰色,这意味着没有可用的立方体贴图。我们必须指示Unity绑定环境地图,这是通过在MyPipeline.Render的渲染器配置中设置RendererConfiguration.PerObjectReflectionProbes标志来完成的。因为此时我们还没有使用反射探针,所以所有对象最终都带有天空盒立方体贴图。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第23张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第24张图片

(采样天空盒)

2.2 调制反射

反射多少环境取决于表面的粗糙度。为此,以表面和环境颜色为参数,向Lighting.hlsl添加ReflectEnvironment函数。请注意,此函数并不关心环境颜色是从立方体贴图样本,或者统一值还是其他值衍生而来。

理想的漫反射器不会反射任何东西,因此结果始终为零。否则,将镜面反射颜色计入结果中。然后将其除以平方粗糙度+1。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第25张图片

通过此功能过滤采样的环境。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第26张图片

(调制反射)

2.3 菲涅尔

在掠射角处,大多数光被反射,并且所有表面都像镜子一样起作用。这称为菲涅耳反射。这实际上是根据表面法线和视图方向之间的角度增强镜面反射颜色。我们将使用一个减去法线和视图方向的饱和点积,并提高到四次方。核心RP库为此定义了一个方便的Pow4函数。使用结果将高光颜色插值为白色,然后将其分解为ReflectEnvironment中的环境颜色。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第27张图片

(菲涅尔反射)

但是粗糙度也会影响菲涅耳反射。表面越粗糙,效果越弱。将fresnelStrength字段添加到表面以跟踪此情况。我们将简单地使其等于平滑度。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第28张图片

现在提高菲涅耳强度,而不是始终保持最大强度。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第29张图片

(调制菲涅尔反射)

最后,将环境反射添加到直接照明中,而不是替换它。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第30张图片

(反射添加到直接照明)

2.4 金属表面

固定的单色镜面反射镜0.04适用于介电材质,但不适用于金属。纯金属仅反射光,但会改变其颜色。要指示某物是否为金属,请添加另一个着色器属性。再次使用范围为0-1的滑块,这样就可以表示由电介质和金属混合而成的材质。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第31张图片

(金属度滑块)

我们也将立即将METALAL属性添加到InstancedMaterialProperties中。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第32张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第33张图片

(逐物体的金属度)

添加着色器实例属性。

并在平滑之前将其传递给LitPassFragment中的LitSurface。

将相应的参数添加到GetLitSurface,并为LitSurface提供一个字段以存储其反射率。如果是完美的扩散器,则始终为零。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第34张图片

对于金属,提供的颜色控制镜面反射而不是漫反射。由于金属不是二进制的,因此可以使用它在非金属的0.04和金属的颜色之间插入镜面反射颜色。在那之后,反射率将简单地等于金属,除了我们使用0.04作为最小值。因此,根据金属从0.04插值到1。最后,反射率增加了菲涅耳强度,最大为1。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第35张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第36张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第37张图片

(一半 VS 全部 金属度)

3 非同一的缩放值

在继续之前,让我们重新考虑所有对象具有统一比例的假设。该限制适用于简单的场景,但是要构建具有简单形状的更复杂的场景,将它们拉伸会很有用,这会导致缩放比例不一致。如果现在这样做,最终将导致着色不正确,如渲染4,第一束光中所述。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第38张图片

(拉伸的球体下 不正确的着色)

3.1 移除假设条件

为了使阴影正确,无论使用何种缩放比例,我们都必须在着色器中摆脱假设一致的缩放编译指示。

如果比例尺不均匀,则必须使用对象的wold-to-object矩阵正确转换法线向量。因此,除了unity_ObjectToWorld外,我们还需要UnityPerDraw缓冲区中的unity_WorldToObject。

除此之外,Unity的实例化代码希望通过UNITY_MATRIX_I_M宏将其定义为UNITY_MATRIX_M的反函数。

在LitPassVertex中转换法线向量时,我们必须交换矩阵,逆转乘法顺序,并对结果进行归一化。但是,仅在不采用统一缩放比例的情况下才需要这样做。如果确实启用该选项,则将定义UNITY_ASSUME_UNIFORM_SCALING,并且仍然可以使用更便宜的方法。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第39张图片

(拉伸的球体 正确的着色)

4 反射探针

仅当没有其他反射探头可用时,才将skybox用于环境反射。你可以通过GameObject/ Light / Reflection Probe将一个添加到场景中。默认情况下,反射探针的“Type”设置为“Baked”,这意味着其立方体贴图由Unity编辑器渲染一次,并且仅显示标记为反射探针为静态的对象。如果更改了静态内容,它将更新。设置为“Realtime”时,它将显示整个场景。何时渲染立方体贴图取决于“Refresh Mode”和“Time Slicing ”设置。在编辑模式下,场景变化时,实时反射探针并不总是刷新。有关更多详细信息,请参见渲染8,反射。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第40张图片

(反射探针设置为实时渲染)

反射探针仅在有东西要反射时才有用,因此将它们放在其他物体附近的场景中。你可以将它们放置在一个对象中,只要该对象使用剔除背面的材质即可。这样,你可以获得该对象的最准确的反射。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第41张图片

(场景中间有一个反射探针)

Unity在渲染立方体贴图面时会使用我们的管道,尽管这不会在帧调试器中显示。但可以通过记录“Render”中使用的摄像机类型,在MyPipeline中对此进行验证。默认情况下,立方体贴图在需要时渲染一次。这意味着反射面最终会比环境图中的预期要暗,因为它们自己依赖于环境图。你可以通过照明设置增加反射量。例如,2的反射值意味着所有立方体贴图都将正常渲染,但随后会使用先前用于环境映射的结果再次渲染它们。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第42张图片

(2次反射值)

4.1 盒投影

默认情况下,由立方体贴图表示的光被视为来自无限远的地方。这不适用于附近的表面。盒映射可用于使小区域的反射更准确。Unity的轻量级渲染管道不执行此操作,但我们将使用与Unity的Legacy渲染管道相匹配的“渲染8,反射”中描述的方法来支持盒映射。

将探针盒的最小和最大边界及其位置的变量添加到UnityPerDraw缓冲区。

直接从其他教程中复制BoxProjection函数。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第43张图片

使用该函数在SampleEnvironment中查找调整后的样本坐标。

为反射探针启用“盒投影”,并调整其盒边界,使其最匹配其反射的内容。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第44张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第45张图片

(盒投影)

4.2 探针混合

反射探针经过一些调整后可以产生不错的结果,但仅适用于合理接近探针位置的物体。否则,除了反射未对齐之外,对象最终有可能反射自己。如果错误太明显,则必须使用更多探针。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第46张图片

(用两个探针替代1个)

每个对象使用哪种反射探针由其盒控制。选择覆盖对象的最接近或最重要的探针。如果该对象未包含在任何探针盒中,它将使用天空盒。但这会导致从一个探针到下一个探针的艰难过渡。可以通过在探针之间进行混合来缓解这种情况,可以针对每个对象进行配置。最多可以将两种探针以这种方式混合。

为了支持混合,我们必须为第二个探针添加另一组变量。

除了第二个立方体贴图纹理外,我们还可以为两者使用相同的采样器状态。

再次,我们从其他教程中复制该方法。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第47张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第48张图片

(混合探针)

4.3 HDR解码

现在,我们支持反射探针,但前提是它们的纹理包含未编码的光数据。通常是这种情况,但是当烘焙的贴图经过HDR编码或探针的强度发生变化时,情况并非如此。为了支持这一点,我们可以依赖在ImageBasedLighting.hlsl核心RP库文件中定义的DecodeHDREnvironment。

每个探测器都有HDR解码指令,为此我们必须添加变量。

然后,我们可以解码立方体贴图样本。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第49张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第50张图片

(双倍强度)

请注意,当使用多次反射时,增加探头的强度会产生巨大的变化,因为每次反射都会使光线增强。

5 透明表面

我们通过回顾半透明的表面来总结。在上一教程中,我们增加了对淡入淡出表面的支持。淡出表面的整个颜色,也包括反射。如果你想使所有物体淡化,那效果还不错,但是它不适用于水晶或玻璃等材质。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第51张图片

(淡入淡出反射)

5.1 预乘Alpha

玻璃通常几乎是完全透明的,但仍会反射光。为了表示这一点,我们只需要将不透明度应用于漫反射光照。我们通过将Alpha用作漫反射颜色来实现。因此,我们预乘alpha而不是在完成所有光照后应用它,而不是使用常规片段混合。之后,我们根据反射率提高Alpha值,以使反射替换表面后面的任何东西。向Lighting.hlsl添加一个函数来相应地调整表面和Alpha。

如果定义了_PREMULTIPLY_ALPHA着色器关键字,则在LitPassFragment中获取表面后调用它。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第52张图片

添加切换着色器属性以控制关键字。

为了使这项工作有效,我们必须将源混合模式设置为一种,而不是source-alpha。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第53张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第54张图片

(开启预乘alpha)

5.2 预设

让我们向LitShaderGUI添加用于透明材质的预设。为它提供一个属性来设置_PREMULTIPLY_ALPHA关键字以及随附的着色器属性。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第55张图片

到目前为止,在所有预设方法中将其设置为false。

复制两个淡入淡出预设方法并进行调整,以使它们配置透明材质。因此PremultiplyAlpha变为true,SrcBlend变为BlendMode.One。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第56张图片

最后一步是调用OnGUI中的方法以及其他方法。

Unity可编程渲染管线系列(七)反射(镜面和环境)_第57张图片

Unity可编程渲染管线系列(七)反射(镜面和环境)_第58张图片

(新的透明预设)

下一章,全局光照。

往期推荐:

Unity可编程渲染管线系列(七)反射(镜面和环境)_第59张图片

Unity可编程渲染管线系列(六)透明度(裁剪与淡化 Clipping and Fading)


Unity可编程渲染管线系列(七)反射(镜面和环境)_第60张图片

Unity可编程渲染管线系列(五)方向阴影(级联贴图 Cascaded Maps)

Unity可编程渲染管线系列(四)聚光灯阴影(阴影贴图)


Unity可编程渲染管线系列(三)光照(单通道 正向渲染)

欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。

本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

你可能感兴趣的:(java,python,unity,人工智能,webgl)