现实的刻薄思考


大多数技术艺术家在其职业生涯中的某个时刻都试图创建合理的焦散反射。 如果您是游戏开发人员,那么阅读Twitter的主要原因之一就是可以从中汲取无穷的灵感。 几天前,Florian Gelzenlichter(在Twitter上的kolyaTQ )发布了使用着色器在Unity中创建的苛性效果的GIF。 该帖子(如下所示)迅速获得了1500顶赞,表明对此类内容有浓厚的兴趣。


尽管我通常对更长和技术复杂的系列文章更感兴趣(例如,关于体积大气光散射 [在Habré上的翻译 ]和逆运动学 [在Habré上的翻译的第一第二部分),但我无法抗拒编写简短而可爱的教程的诱惑。关于Florian的影响。

本文结尾处有一个链接,用于下载Unity软件包和所有必需的资产。

什么是苛性的


尽管您每天都会遇到这种影响,但您可能并不了解苛性碱的概念。 焦散是由曲面引起的光反射。 在一般情况下,任何曲面都可以像透镜一样工作,将光聚焦在某些点上并将其散射在其他点上。 提供这种效果的最常见介质是玻璃和水,它们会产生所谓的苛性波 (见下文)。


焦散可以采取其他形式。 例如,彩虹是一种光学现象,当光线在雨滴中折射时发生。 因此,严格来说,它是苛性的。

效果剖析


苛刻波的一个可识别特征是其移动方式。 如果您曾经看过泳池底部,很可能看到了他。 重建真实的苛性碱非常昂贵,因为它需要模拟许多光线。

从单一的腐蚀性纹理开始, Florian设法创造了合理的效果。 为了创建我的教程,我使用了如下所示的纹理,该纹理取自OpenGameArt


可以实现此效果的一个重要属性是上面显示的苛性碱模式是无缝的 。 这意味着您可以将两个图像彼此相邻放置,并且它们之间不会出现明显的接缝。 由于我们想在较大的表面上使用此效果,因此重要的是,我们有机会拉伸此纹理而不会产生会破坏幻觉的泪水。

收到纹理后, Florian建议采取以下三个步骤:

  • 在模型表面两次施加苛性碱图案,每次使用不同的大小和速度
  • 使用min运算符混合两种模式
  • 采样时分离RGB通道。

让我们看看如何在Unity中实现每个步骤。

着色器创建


第一步是创建一个新的着色器。 由于这种效果很可能会在具有真实照明的3D游戏中使用,因此最好从表面着色器开始。 表面着色器是Unity支持的多种着色器之一(例如,用于未照明材质的顶点和片段着色 器,用于后处理效果的屏幕着色器以及用于屏幕外模拟的计算着色器 )。

新的表面着色器仅具有一些功能。 要创建此效果,我们需要将信息传输到着色器。 首先是苛性纹理。 其次,这是用于缩放和偏移它的参数。

让我们创建两个着色器属性

 Properties { ... [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics_ST("Caustics ST", Vector) = (1,1,0,0) } 

以及相应的Cg变量

 sampler2D _CausticsTex; float4 _Caustics_ST; 

着色器属性对应于Unity Material Inspector中显示的字段。 相应的Cg变量本身就是值,可以在着色器代码中使用。

从上面的代码中可以看到, _Caustics_STfloat4 ,即它包含四个值。 我们将使用它们来控制苛性纹理的采样。 即:

  • _Caustics_ST.x :沿X轴的苛性纹理比例;
  • _Caustics_ST.y :沿Y轴的苛性纹理比例;
  • _Caustics_ST.z :苛性纹理沿X轴的位移;
  • _Caustics_ST.w :沿Y轴的腐蚀性纹理的位移;

为什么将变量称为_Caustics_ST?
如果您已经对使用着色器有一点经验,那么您已经看到了其他属性,它们的后缀为_ST 。 在Unity中, _ST可用于添加有关如何采样纹理的其他信息。

例如,如果创建Cg变量_MainTex_ST ,则可以在将纹理应用于模型时使用它来设置大小和偏移。

通常_ST变量不需要属性,因为它们会自动显示在检查器中。 但是,在这种特殊情况下,我们不能依靠它,因为我们需要两次采样纹理,每次使用不同的比例和偏移量。 将来,我们需要将此变量复制为两个不同的变量。

采样纹理


每个表面着色器都包含一个函数,通常称为surf ,用于确定每个渲染像素的颜色。 “标准” surf功能如下所示:

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

最终颜色由着色器必须初始化并在名为SurfaceOutputStandard的结构中返回的字段数确定。 我们需要更改Albedo ,它大致与白光照明的对象的颜色匹配。

在新创建的曲面着色器中,反照率取自名为_MainTex的纹理。 由于苛性效应叠加在现有纹理的顶部,因此我们将不得不在_CausticsTex中对纹理进行附加采样。

称为UV覆盖的技术使您能够根据需要渲染几何体的哪一部分来了解需要对纹理的哪一部分进行采样。 这是使用uv_MainTex变量float2 ,该变量存储在3D模型的每个顶点处,并指示纹理的坐标。

我们的想法是使用_Caustics_ST缩放和偏移uv_MainTex以在模型中拉伸和移动苛性纹理。

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Caustics sampling fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

如果反照率超过1会怎样?
在上面的代码中,我们添加了两个纹理。 颜色通常介于 0之前 1但是,不能保证结果是某些值不会超出此间隔。

在较旧的着色器中,这可能会引起问题。 这实际上是一个功能 。 如果像素颜色值超过1,则意味着其影响应“扩展”到其边界之外并影响相邻像素。

当获得非常明亮的镜面反射时,就会发生这种情况。 但是,此效果不应仅由曲面着色器创建。 为了发挥效果,相机必须打开HDR 。 此属性代表“ 高动态范围” ; 它允许颜色值超过 1。 另外,为了使相邻像素上的过多颜色模糊,需要后处理效果。

Unity有自己的后处理堆栈,该堆栈具有执行此操作的Bloom Bloom过滤器。 您可以在Unity博客上了解有关此内容的更多信息: PostFX v2-惊人的视觉效果,已升级

初步结果如下所示:


动画焦散


苛性碱的最重要特征之一是其如何运动。 目前,它们只是作为第二个纹理静态投射到模型的表面上。

可以使用Unity属性_Time来实现着色器中材质的动画。 它可用于访问当前游戏时间,即在方程式中增加时间。

最简单的方法是根据当前时间简单地偏移纹理。

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // Sampling fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; 

_Time.y字段包含当前播放时间(以秒为单位) 。 如果反射运动太快,可以乘以一个因子。 为此,在上面显示的代码中使用了_CausticsSpeed类型的_CausticsSpeed变量。

您可能需要为自己的目的振动正弦曲线中的腐蚀性纹理。 在这里重要的是要了解没有实现效果的标准方法。 根据您的需要,可以使苛性反射的移动方式完全不同。

下面显示的结果仍然相当中等。 这是正常现象:要使反射看起来更漂亮,我们还有很多事情要做。


多次采样


如果您对苛性纹理进行一次采样而不是两次采样,则效果会变得生动起来。 如果将它们彼此叠放并以不同的速度移动,结果将完全不同。

首先,我们复制_Caustics_ST_CausticsSpeed属性,以使两个纹理的样本具有不同的比例,位移和速度:

 [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics1_ST("Caustics 1 ST", Vector) = (1,1,0,0) _Caustics2_ST("Caustics 1 ST", Vector) = (1,1,0,0) // Speed X, Speed Y _Caustics1_Speed("Caustics 1 Speed", Vector) = (1, 1, 0 ,0) _Caustics2_Speed("Caustics 2 Speed", Vector) = (1, 1, 0 ,0) 

现在我们有了两个苛性碱样品,可以使用min运算符将它们混合。 如果仅取平均值,结果将不会很好。

 // Caustics samplings fixed3 caustics1 = ... fixed3 caustics2 = ... // Blend o.Albedo.rgb += min(caustics1, caustics2); 

如此小的变化带来了巨大的变化:


为了使代码美观,您还可以将腐蚀性采样代码包装在自己的函数中:

 // Caustics fixed3 c1 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics1_ST, _Caustics1_Speed); fixed3 c2 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics2_ST, _Caustics2_Speed); o.Albedo.rgb += min(c1, c2); 

RGB分离


为了使苛刻的反射看起来不错,您需要做最后一个技巧。 穿过切片,不同波长的光会有不同的折射。 这意味着当在水中移动时,灯光会“分裂”为不同的颜色。

为了模拟这种效果,我们可以将每个腐蚀性样品分为三个,每个颜色通道一个。 通过对红色,绿色和蓝色通道进行轻微采样来获得颜色不匹配。

让我们从添加_SplitRGB属性开始,该属性指示_SplitRGB效果的强度:

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // RGB split fixed s = _SplitRGB; fixed r = tex2D(tex, uv + fixed2(+s, +s)).r; fixed g = tex2D(tex, uv + fixed2(+s, -s)).g; fixed b = tex2D(tex, uv + fixed2(-s, -s)).b; fixed3 caustics = fixed3(r, g, b); 

RGB通道的偏移量可以任意选择,但是即使使用这种简单的偏移量,也可以获得令人信服的图像:


结论和下载


如果您有兴趣学习如何创建无缝的苛性纹理,那么应该阅读有趣的文章周期性苛性纹理

同时, Florian继续致力于他的苛性着色器,并做出了一些非常有趣的改进,可以看出。


Patreon上提供了本教程的完整软件包,其中包括重新创建此技术的所有必要资产。 该软件包是从Unity 2019.2导出的,并且需要Postprocessing Stack v2。

Source: https://habr.com/ru/post/zh-CN467887/


All Articles