当前位置:   article > 正文

Unity 水、流体、波纹基础系列(四)——朝水里看(Looking Through Water)

u3d做透明水的材质

目录

1 使水透明

1.1 水下风光

1.2 透明表面着色器

1.3 移除水阴影

2 水下雾

2.1 找出深度

2.2 抓取背景

2.3 应用雾

2.4 自定义混合

3 伪折射

3.1 抖动背景样本

3.2 使用法线向量

3.3 仅折射水下

3.4 方向性流体

收起

水下的雾化和折射(Underwater Fog and Refraction)

文章重点:

让水透明

采样深度并获取渲染的内容

添加水下雾

创建伪折射

这是有关创建流体材质系列文章中的第四篇。我们将使水表面透明,增加水下的雾和折射。

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

本教程使用Unity2017.4.4f1.

(水中折射)

1 使水透明

到目前为止,我们创建的水效果是完全不透明的。这适用于深水或其他非常浑浊的液体,或覆盖有一层碎屑,泡沫,植物或其他阻挡光的液体。但是透明的水是透明的,因此需要透明的着色器。因此,我们将调整表面着色器以使其具有透明度。我们只会关注水下。如果要水下相机的效果,需要不同的方法。

1.1 水下风光

首先,创建一些水下风景,以便在水面下有一些好玩的东西。我创建了一个深坑,其中一些物体暗示了植物的生长,无论深度在地下还是在地面。我还添加了两个漂浮在水面上的球体。为了照亮坑的底部,我添加了一个强烈的聚光灯,该聚光灯从水面射出。此灯和主方向灯都启用了阴影。

(测试场景,还没有水)

我们将使用“扭曲流体”效果,因此将带有该材质的四边形添加到场景中,代表水面。它仍然是完全不透明的,因此它将遮盖所有水下的东西。

(测试场景,不透明的水)

1.2 透明表面着色器

若要使Distortion Flow着色器支持透明性,请将其RenderType标记更改为“Transparent”,并将其“Queue ”标记也设置为“Transparent ”。这使得它可以与你可能拥有的任何替换着色器一起使用,并将着色器移至透明渲染队列,现在已经会所有不透明几何体渲染之后进行绘制。

我们还必须指示Unity从表面着色器代码生成透明着色器,这是通过将alpha关键字添加到表面编译指示中来完成的。

由于我们使用的是基于物理的标准照明功能,因此默认情况下,着色器将使用Unity的透明渲染模式,该模式会将高光和反射保持在其本来透明的表面之上。另一种选择是淡入淡出模式,它会均匀淡出所有内容,这是不真实的。

现在,我们可以通过调整材质albedo的alpha分量来控制水的透明度。

(透明的水表面)

1.3 移除水阴影

即使将其Alpha设置回1,水也不再接收阴影。这是因为现在它已放入透明渲染队列中。由于这些对象的渲染方式,它们无法接收阴影。尽管你可以在某种程度上解决此限制,但是使用简单的表面着色器是不行的。

但是,我们的水仍会投射阴影,从而消除了水下的所有直接照明。我们不希望这样,因为它使水下场景太暗。首先,我们可以删除fillforwardshadows关键字,因为我们不再需要支持任何阴影类型。

这尚未消除主定向光的阴影。这些仍由默认的漫反射阴影投射器过程添加,我们已从漫反射后备着色器继承了该过程。要消除阴影,请删除fallback。

(灯光穿透水)

2 水下雾

水不是完全透明的。它吸收穿过它的部分光,并散射其中的一些。这个现象会发生在任何介质中,但在水中比在空气中更明显。清澈的水吸收一点光,但是不同的频率以不同的速率吸收。蓝光被吸收最少,这就是为什么你越深入事物就会变蓝的原因(谐意梗吧)。这与部分透明的水表面不同,因为这不会根据深度更改水下颜色。

(半透明水,没有基于深度的颜色变化)

水下光的吸收和散射有点像雾。尽管雾化效果不能很好地近似实际发生的情况,但它是一种便宜且易于控制的方式,可以使水下深度影响我们所看到的颜色。因此,我们将使用与“渲染系列,雾章节”中描述的方法相同的方法,只是在水下使用。

我们可以通过两种方式将水下雾添加到场景中。第一种是使用全局雾并将其应用于在水面之前渲染的所有内容。当你只有一个统一的水位时,这可以很好地完成任务。另一种方法是在渲染水面时应用雾。这使得雾对每个表面都是特定的,从而允许水在不同的位置(甚至在不同的方向)不影响任何不在水下的东西。这里使用第二种方法。

2.1 找出深度

因为我们要更改水面以下的颜色,所以我们不再可以依赖标准着色器的默认的透明混合。渲染水面片段时,我们必须以某种方式确定水面后面的最终颜色是什么。让我们为此创建一个ColorBelowWater函数,并将其放在单独的LookingThroughWater.cging包含文件中。最初,它只是返回黑色。

为了测试这种颜色,我们将其直接用于水的albedo中,暂时覆盖其真实表面的反照率。同时将alpha设置为1,这样我们就不会因常规透明度而分心。

(水下的黑色)

要弄清光线在水下传播的距离,我们必须知道水下的每个物体有多远。由于水是透明的,因此不会写入深度缓冲区。所有不透明的对象都已被渲染,因此深度缓冲区包含我们需要的信息。

Unity通过_CameraDepthTexture变量使深度缓冲区全局可用,因此将其添加到我们的LookingThroughWater包含文件中。

_CameraDepthTexture总是可用的吗?

如果Unity决定渲染深度通道,则仅包含深度信息。使用延迟渲染时,总是这样。当主要方向光是使用屏幕空间阴影级联渲染时,正向渲染中也会使用深度传递。否则,你必须通过脚本设置相机的深度纹理模式。

要采样深度纹理,我们需要当前片段的屏幕空间坐标。我们可以通过将float4 screenPos字段添加到表面着色器的输入结构中,然后将其传递给ColorBelowWater来检索这些字段。

屏幕位置只是剪辑空间位置,其XY分量的范围从-1更改为0-1。除此之外,取决于目标平台,Y组件的方向可能会更改。它是一个四分量向量,因为我们正在处理同构坐标。如渲染系列,阴影章节 中所述,我们必须将XY除以W,以获得最终的深度纹理坐标。在ColorBelowWater中执行此操作。

现在,我们可以通过SAMPLE_DEPTH_TEXTURE宏对背景深度进行采样,然后通过LinearEyeDepth函数将原始值转换为线性深度。

这是相对于屏幕的深度,而不是水面的深度。因此,我们还需要知道水和屏幕之间的距离。我们可以通过选择screenPos的Z分量(即插值的剪辑空间深度)并通过UNITY_Z_0_FAR_FROM_CLIPSPACE宏将其转换为线性深度来找到它。

通过从背景深度中减去表面深度来找到水下深度。让我们使用它作为最终颜色来查看它是否正确,并按比例缩小,以便至少可以看到部分渐变。

(不同深度,聚光灯关闭)

此时你可能会获得颠倒的结果。为避免这种情况,请检查相机深度纹理的纹理像素大小在V维度上是否为负。如果是这样,请反转V坐标。我们只需要在使用从上到下坐标的平台上进行检查即可。在这些情况下,UNITY_UV_STARTS_AT_TOP被定义为1。

2.2 抓取背景

要调整背景的颜色,我们必须以某种方式对其进行检索。使用表面着色器的唯一方法是添加抓取通道。这是通过在着色器的CGPROGRAM块之前添加GrabPass {}来完成的。

现在,Unity将在渲染管道中添加一个额外的步骤。就在水被绘制之前,到此为止渲染的内容都将复制到抓取传递纹理中。每当使用我们的水着色器的东西被渲染时,就需要这么干。通过为抓取的纹理指定一个明确的名称,我们可以将其减少为一个额外的绘制。这是通过将带有纹理名称的字符串放入抓取pass的否则为空的块中来完成的。然后,所有水表面将使用相同的纹理,该纹理将在绘制第一个水之前立即被抓取。将纹理命名为_WaterBackground。

为该纹理添加一个变量,然后使用与采样深度纹理相同的UV坐标对其进行采样。ColorBelowWater的使用结果应与之前的完全透明水产生相同的图像。

(抓取背景)

能不能使用ComputeGrabScreenPos?

V坐标方向的规则对于深度纹理和抓取纹理都应相同。ComputeGrabScreenPos根据UNITY_UV_STARTS_AT_TOP对其进行翻转,我们也会对其进行检查。如果这不行的话,请告诉我下。

2.3 应用雾

除了深度和原始颜色,我们还需要进行设置以控制雾。我们将使用简单的指数雾,因此需要向着色器添加颜色和密度属性。

将雾的颜色设置为与水的albedo相同,其十六进制代码为4E83A9FF。我将密度设置为0.15。

(雾设置)

将相应的变量添加到包含文件中,然后使用它们来计算雾化因子并内插颜色。

(水下雾)

2.4 自定义混合

水下雾的效果可以了,但是目前它也正在作用于水面的albedo 。这是不正确的,因为albedo受光照的影响。相反,我们必须将水下颜色添加到表面照明中,这可以通过将其用作发光颜色来实现。通过水的albedo 来调节这一点。它越不透明,我们看到的背景越少。另外,我们必须恢复原始的Alpha,因为这会影响水面的照明方式。

(水下颜色被添加了)

这已经接近正确了,除了使用最终的alpha值与已渲染的内容进行混合外,因此最终显示的是原始背景。我们已经与背景融为一体了,不应重复两次。通过在计算最终片段颜色后将alpha设置回1来禁用默认混合。这可以通过添加一个功能来调整表面着色器的最终颜色来完成。将finalcolor:ResetAlpha添加到表面着色器的pragma指令中。

然后添加一个无效的ResetAlpha函数。此功能具有原始输入,表面输出和inout颜色作为参数。我们需要做的就是将该颜色的alpha分量设置为1。

(调整水表面的透明度)

3 伪折射

当光通过不同密度的介质之间的边界时,它会改变方向。就像反射一样,但它不是弹起而是以不同的角度穿过。方向的变化取决于光线穿过边界的角度。角度越浅,折射越强。

就像反射一样,精确的折射将需要我们将光线跟踪到场景中,但是我们会进行近似计算。可以使用反射探针,屏幕空间反射或从不同角度进行单独渲染的平面反射进行反射。相同的技术可以用于折射。

因为我们已经使用屏幕空间数据来创建水下雾,所以也将其重新用于屏幕空间折射。虽然结果不是很真实,但这使我们可以毫不费力地添加折射效果。因为在大多数情况下,使水下风光与表面运动略微同步就足以产生令人信服的折射幻觉,尤其是对于浅水区。

3.1 抖动背景样本

我们将通过抖动用于采样背景的UV坐标来创建伪折射。让我们看一下应用恒定的对角线偏移量(将两个坐标都加1)时的情况。在透视图分割之前执行此操作,因此透视图也适用于偏移。

(对角线偏移,顶视图)

我们得到一个对角线偏移,但它不是对称的。垂直偏移小于水平偏移。至少当图像的宽度大于高度时就是这种情况。为了使偏移相等,我们必须将V偏移乘以图像宽度除以其高度。可以用深度纹理的大小信息完成。其Z分量包含以像素为单位的宽度,其W分量包含以像素为单位的高度。但是,我们也可以使用其Y分量,该分量包含宽度的倒数,使用乘法而不是除法。

但是Y可以为负,以表示反转的V坐标。因此,我们应该采用绝对值,这不需要额外的判定。

(对称的偏移)

请注意,这意味着效果不取决于图像的分辨率,而是受其宽高比的影响。

3.2 使用法线向量

为了使偏移摆动,我们将使用切线空间法线向量的XY坐标作为偏移。这没有物理意义,但是与表面的表观运动同步。为此,将一个属性添加到ColorBelowWater。

并将其传递到水表面的最终切线空间法线。

(和法线向量一起偏移)

法线向量的X和Y分量起作用,因为它们位于切平面中。对于平坦表面,它们都为零,不会产生偏移。水波纹越大,偏移量越大,折射效果越强。我们可以通过着色器属性控制效果的整体强度。范围为0-1。

将相应的变量添加到我们的包含文件中,并使用它来调整偏移量。

全强度折射相当强。我已将其降低到0.25。

(淡化的折射)

3.3 仅折射水下

现在,我们具有不错的伪折射效果,但其中还包括非水下物体。之所以会发生这种情况,是因为UV坐标可能会以一个偏移量结束,该偏移量会将最终样本放置在位于水前的物体内。

(折射前景)

这是我们必须消除的非常明显的错误。通过检查用于雾的深度差是否为负,可以检测是否已经达到前景。如果是这样,我们采样了一个在水前面的片段。由于我们不知道背后的含义,因此无法产生有意义的折射。因此,让我们消除偏移,将原始UV用于最终的颜色样本。

(过滤后的偏移)

这样可以删除大多数错误的颜色样本,但还不能解决雾。在确定雾度因子之前,我们还必须使用重置的UV再次采样深度。

(使用了正确的深度)

这也解决了雾,但是在我们消除的折射边缘附近,我们仍然会出现细微的伪影。有时可能很难发现,但有时也可能非常明显,尤其是在水动画时。

(细的摆动的伪影线)

这些伪影的存在是由于在对抓取的纹理进行采样时进行了混合。这可以通过使用点过滤来解决,但是我们无法通过表面着色器控制抓取纹理的过滤模式。我们将通过将UV乘以纹理大小,舍弃分数,偏移到texel中心,然后除以纹理大小来完成此难题。让我们为此创建一个AlignWithGrabTexel函数,它也可以处理坐标翻转。然后使用该函数查找最终的UV坐标。

那应该会伪影线,但不总是如此。由于我们依赖深度缓冲区,因此也禁用了MSAA。要么使用不带MSAA的正向渲染,要么使用延迟渲染。即使这样,伪影仍然可以出现在场景或游戏窗口中,这取决于它们的大小是偶数还是奇数。为了验证它们确实消失了,你必须进行构建并进行播放。

(对齐的纹理像素样本)

尽管我们摆脱了大多数不正确的折射,但在水面附近仍然有些怪异。这是折射可以突然消失的地方。通过根据深度差缩小最终偏移量来消除这种情况。除了将负折射抛弃之外,还应将偏移乘以饱和深度差。这样可以减少折射,直至深度差为1。

(平滑浅折射)

在消除折射的情况下,仍然有可能获得怪异的结果,但是在大多数情况下,这种现象不再明显了。

(最终的折射效果)

3.4 方向性流体

现在,我们的“扭曲流体”着色器已完成。我们只需稍作更改即可为Directional Flow Shader提供相同的处理。

(透明的定向流,带有雾和折射)

伪折射不适用于Waves着色器,该着色器可置换顶点并且不使用切线空间法线。但是,如果你限制波浪高度,则水下雾可以会起作用,但这样你就永远不能同时看到多个波浪。当然,你还可以使用Waves作为基础,在该基础上应用较小的切线空间波纹,然后可以在其上添加伪折射。

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

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

原文地址:

https://catlikecoding.com/unity/tutorials

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/108483
推荐阅读
相关标签
  

闽ICP备14008679号