您想找什么?
Engine & platform

Shader Graph 中的自定义照明:2019 年扩展您的图表

ALEX LINDMAN / UNITY TECHNOLOGIESContributor
Jul 31, 2019|13 Min
Shader Graph 中的自定义照明:2019 年扩展您的图表
为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

随着Unity Editor 2019.1的发布,Shader Graph包正式脱离预览版!现在,在 2019.2 中,我们为 Shader Graph 带来了更多特性和功能。

2019 年发生了什么变化?
自定义函数和子图升级

为了维护 Shader Graph 中的自定义代码,您现在可以使用我们新的自定义函数节点。该节点允许您定义自己的自定义输入和输出、重新排序它们以及将自定义函数直接注入节点本身或通过引用外部文件。

子图也得到了升级:您现在可以为子图定义自己的输出,具有不同的类型、自定义名称和可重新排序的端口。此外,子图的黑板现在支持主图支持的所有数据类型。

色彩模式和精度模式

使用 Shader Graph 创建强大且优化的着色器变得更加容易。在 2019.2 中,您现在可以手动设置图表中计算的精度,无论是整个图表还是基于每个节点。我们新的颜色模式可以快速轻松地可视化精度流程、节点类别,或显示自定义颜色以供您自己使用!

有关这些新功能的更多信息,请参阅 Shader Graph 文档

示例项目

为了帮助您开始使用新的自定义函数工作流程,我们创建了一个示例项目并提供了分步说明。从我们的存储库下载项目 并继续!该项目将向您展示如何使用自定义函数节点为轻量级渲染管线 (LWRP) 编写自定义照明着色器。如果您想使用新项目进行跟进,请确保您使用的是 2019.2 编辑器和 LWRP 包版本 6.9.1 或更高版本。

从主光源获取数据

首先,我们需要从场景中的主灯获取信息。首先选择 “创建”>“着色器”>“无光图形” 来创建一个新的无光着色器图形。在创建节点菜单中,找到新的自定义函数节点,然后单击右上角的齿轮图标打开节点菜单。

在此菜单中,您可以添加输入和输出。为 DirectionColor添加两个输出端口,并为两者选择 Vector 3。如果您看到“未声明的标识符”错误标志,请不要担心;当我们开始添加代码时,它就会消失。在 类型 下拉菜单中,选择 字符串。更新您的函数名称 - 在此示例中,我们使用“MainLight”。现在,我们可以开始在文本框中添加自定义代码。

图像

首先,我们将使用一个名为“#ifdef SHADERGRAPH_PREVIEW”的标志。因为节点上的预览框无法访问光数据,所以我们需要告诉节点在图形预览框上显示什么。`#ifdef` 告诉编译器在不同情况下使用不同的代码。首先定义输出端口的后备值。

接下来,我们将使用“#else”来告诉编译器在非预览时该做什么。这正是我们实际获取光数据的地方。使用 LWRP 包中的内置函数“GetMainLight()”。我们可以使用这些信息来分配 方向颜色 输出。您的自定义函数现在应如下所示:

现在,最好将此节点添加到一个组中,以便您可以记录它正在做的事情。右键单击节点,选择 从选择中创建组,然后重命名组标题以描述节点正在执行的操作。这里我们输入的是“获取主光”。

图像

现在我们有了灯光数据,我们可以计算一些阴影了。我们将从标准的朗伯光照开始,因此让我们取世界法线向量和光线方向的点积。将其传递到饱和节点,并将其与光色相乘。将其插入 Unlit Master 节点的 颜色 端口,您的预览应该会使用一些自定义阴影进行更新!

图像
使用自定义函数文件模式

由于我们现在知道如何使用自定义函数节点获取光数据,我们可以扩展我们的函数。我们的下一个函数除了方向和颜色之外,还从主光中获取衰减值。由于这是一个更复杂的功能,让我们切换到文件模式,并使用 HLSL 包含文件。这使得您可以在将其注入图表之前在适当的代码编辑器中编写更复杂的函数。这也意味着我们有一个统一的位置来调试代码。首先打开项目的 Assets > Include 文件夹中的“CustomLighting”包含文件。现在,我们只关注“MainLight_half”函数。该函数如下所示:

该函数包含一些新的输入和输出数据,所以让我们回到自定义函数节点并添加它们。为 DistanceAtten(距离衰减)和 ShadowAtten(阴影衰减)添加两个新输出。然后,为 WorldPos(世界位置)添加新的输入。现在我们有了输入和输出,我们可以引用包含文件。将 类型 下拉菜单更改为 文件。在源输入中,导航到包含文件,然后选择要引用的资产。现在,我们需要告诉节点使用哪个函数。在 名称 框中,我们输入“MainLight”。

图像

您会注意到,包含文件的函数名称末尾有“_half”,但我们的名称选项没有。这是因为 Shader Graph 编译器将精度格式附加到每个函数名称之后。由于我们正在定义自己的函数,因此我们需要源代码来告诉编译器我们的函数使用哪种精度格式。然而,在节点中我们只需要引用主要函数名称。您可以创建使用“浮点”值的函数副本,以浮点精度模式进行编译。通过“精度”颜色模式,您可以轻松跟踪图表中每个节点的精度设置,其中蓝色代表浮点数,红色代表一半。

我们可能希望在其他地方再次使用这个函数,而使这个自定义函数可重复使用的最简单方法是将其包装在子图中。选择节点及其组,然后右键单击以找到 “转换为子图”。我们将其命名为“获取主光”。在子图中,只需将所需的输出端口添加到子图输出节点,然后将节点的输出插入子图输出。接下来,我们将添加一个世界位置节点来插入输入。

图像

保存子图并返回到我们的未点亮图。我们将在现有逻辑中添加两个新的乘法节点。首先,将两个衰减输出相乘。然后,将该输出乘以光的颜色。我们可以将其乘以之前的 NdotL,以正确计算基本阴影中的衰减。

图像
创建直接镜面反射着色器

我们制作的着色器非常适合用于无光泽的物体,但如果我们想要一些光泽怎么办?我们可以将自己的镜面反射计算添加到我们的着色器中!在此步骤中,我们将使用另一个包裹在子图中的自定义函数节点,称为 Direct Specular。再次查看“CustomLighting”包含文件,发现我们现在引用了同一文件中的另一个函数:

此函数执行一些简单的镜面反射计算,如果您感兴趣,可以 在此处阅读有关它们的更多信息。该函数的子图还包括黑板上的一些输入:

图像

确保您的新节点具有所有适当的输入和输出端口以匹配该功能。向 Blackboard 添加属性很简单;只需单击右上角的 添加 (+) 图标,然后选择数据类型。双击药丸以重命名输入,然后拖放药丸以将其添加到图形中。最后,更新子图的输出端口并保存。

现在已经设置了镜面反射计算,我们可以返回到未点亮的图表,并通过创建节点菜单添加它。将 衰减 输出连接到 直接镜面 子图的 颜色 输入。接下来,将 Get Main Light 函数的 Direction 输出连接到 Specular Sub Graph 的 Direction 输入。将 NdotL*Attenuation 的结果添加到 Direct SpecularSub Graph 的输出,并将其插入 Color 输出。

图像

现在我们有点闪耀了!

使用多个灯光

LWRP 的主光源是指相对于物体最亮的定向光,通常是太阳。为了提高低端硬件的性能,LWRP 分别计算主光源和任何附加光源。为了确保我们的着色器能够正确计算场景中的所有灯光,而不仅仅是最亮的定向光,您需要在函数中创建一个循环。为了获取额外的光照数据,我们使用了一个新的子图来包装一个新的自定义函数节点。看一下`CustomLighting`包含文件中的`AdditionalLight_float`函数:

与之前一样,使用自定义函数节点的文件引用中的“AdditionalLights”函数,并确保已创建所有正确的输入和输出。确保在包裹节点的子图的黑板上公开 镜面颜色镜面平滑度 。使用位置、法线矢量和视图方向节点在子图中插入 世界位置世界法线世界空间视图方向

设置完功能后,就可以使用它了!首先,从上一步中获取主 Unlit 图,并将其折叠为子图。选择节点,然后右键单击 “转换为子图”。删除最后一个添加节点,并将输出插入子图的输出端口。我们建议您还为 镜面反射平滑度创建输入属性。

图像

现在您可以将主光计算和附加光计算结合在一起。在主 Unlit 图中,创建一个新节点,用于附加光计算与主光计算一起进行。将主光源和附加光源的 漫反射镜面反射 输出相加。非常简单!

图像
创建简单的卡通着色器
图像

现在您知道如何获取 LWRP 项目场景中所有灯光的数据,但是您可以用它做什么呢?着色器中自定义照明的最常见用途之一是经典的卡通着色器!

有了所有的灯光数据,创建卡通着色器就非常简单了。首先,将您迄今为止完成的所有光线计算再次包装到子图中。这将有助于提高最终着色器的可读性。不要忘记删除最后的 Add 节点,并将 DiffuseSpecular 输入到 Sub Graph 输出节点上的单独输出端口。

有很多方法可以创建卡通着色,但在这个例子中,我们将使用光强度从渐变纹理 (Ramp Texture) 中查找颜色。这种技术通常称为“坡道照明”。我们在示例项目中包含了一些坡道照明所需的纹理资产类型的示例。您还可以对渐变进行采样,以在“渐变照明”中使用动态渐变。第一步是将 漫反射镜面反射 的强度从 RGB 值转换为 HSV 值。这让我们可以使用光色的强度(HSV 值)来确定着色器上的亮度,并帮助我们沿着资产水平轴的不同位置对纹理进行采样。使用 UV 的 Y 通道的静态值来确定从上到下应该对图像的哪个部分进行采样。您可以使用此静态值作为索引来在单个纹理资产中引用项目的多个照明坡道。

图像

设置 UV 值后,使用 Sample Texture 2D LOD 节点对 Ramp Texture 进行采样。样本 LOD 很重要;如果我们使用常规的 Sample Texture 2D 节点,则渐变会在场景中自动进行 miped,而距离较远的物体将具有不同的照明行为。使用样本纹理 2D LOD 节点允许我们手动确定 mip 级别。此外,由于斜坡纹理只有 2 个像素高,我们为纹理创建了自己的采样器状态。为了确保正确采样纹理,我们将过滤器设置为点,并将包裹设置为夹紧。我们将其作为 Blackboard 上的属性公开,以便您可以在纹理资产发生变化时更改设置。

图像

最后,我们将漫反射计算中的渐变样本乘以颜色属性“ 漫反射”,这样我们就可以改变物体的颜色。将镜面反射计算中的渐变样本添加到漫反射输出,并将最终颜色插入主节点。

图像
扩展自定义照明
图像

这种简单的自定义照明设置可以扩展并应用于各种场景中的各种用例。在我们的示例项目中,我们包含了一个完整的场景,其中配置了使用我们自定义照明设置的着色器。它还包含顶点动画、简单的次表面散射近似以及使用深度的折射和着色。下载项目并查看我们的示例资产以探索更多高级方法!

继续学习!

如果您想讨论 Shader Graph 以及可以使用它制作的着色器,欢迎来到我们的全新 论坛空间!您还可以找到社区成员,有时还有几个开发人员在 社区 Discord中闲逛!

不要忘记关注我们的 SIGGRAPH 2019 会议的录音,我们将在其中更详细地介绍如何使用 Shader Graph 进行自定义照明!