unity片元着色器中获取屏幕坐标_Unity 水、流体、波纹基础系列(四)——朝水里看(Looking Through Water)...

目录

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.

1a37c3951a3afc6ff37ecd08effebfc8.png

(水中折射)

1 使水透明

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

1.1 水下风光

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

370d285d80bddc8c0e23f0410be0d3c4.png

(测试场景,还没有水)

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

fbc185821a67ab9754b27cc9f2431313.png

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

1.2 透明表面着色器

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

907d7893d75f90198457d65a08f80e62.png

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

48bab560e5fc3477d8b89926664c8e00.png

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

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

0fbb2f05fb422cfad42ddc4f0e7fc6bc.png

(透明的水表面)

1.3 移除水阴影

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

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

0455a58bf98d52d95bce013aee64e5c3.png

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

6245e4dfb476cf0a5c2ad9fee689c343.png

e41cb3bcd979b5fb956970c603f92bd2.png

(灯光穿透水)

2 水下雾

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

01b67392e9384aebe0807531f0d30fd8.png

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

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

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

2.1 找出深度

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

b73cd75e36d3e44c767208cf84d420b5.png

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

35a7806aa353bfa230460366749e2fe5.png

42c58df21265ebc16853d82abfbea44c.png

(水下的黑色)

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

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

3dd22995210a2029dd78a7230714f837.png

_CameraDepthTexture总是可用的吗?

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

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

b7471a9a3c53805876576a297cefd450.png

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

6c5d89a5f3adcaedd991ef56f57606c5.png

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

51e6780b4a9f4ca950b4084aceaca5d4.png

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

90374aa3a406ea4a409d8a7640df224e.png

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

c0a2e21c450184c074481fdde070cdf6.png

de8cbde614687e42e599bb8b69bc34cd.png

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

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

94f91120ddb444b54c684a347b898b0f.png

2.2 抓取背景

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

07293d93d12b2499fdaf8f1f8d86cb3e.png

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

e359bbf87773a74d22a865f299607ac1.png

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

aea58d459fd498719dd8f394bec0517b.png

5c7c5e90e42e5743b9cc91d808bbe4cf.png

(抓取背景)

能不能使用ComputeGrabScreenPos?

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

2.3 应用雾

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

40ef2d5fef0014b9bd8e1d922683f4f8.png

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

117b3725c288f63eb26edc28acd069c1.png

(雾设置)

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

3e79b510e2db6cc4aa0ccd86928fd841.png

1a6a7c1e1a67e2de41f843d2f6931115.png

(水下雾)

2.4 自定义混合

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

d2fbf08e8792fb12618fa1279388e680.png

4278752fc94521efe388f1818acffb6b.png

(水下颜色被添加了)

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

f8039a137fd53a2e985f12de8dbf37f7.png

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

a85880eec10e48b13d99e2edff211d74.png

2138e01fa2fa1db4f6b0cd9506c13651.gif

(调整水表面的透明度)

3 伪折射

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

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

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

3.1 抖动背景样本

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

b88180fb3f255ea8f16e27e773351cc1.png

ee974d51b89e142248a8177309741418.png

(对角线偏移,顶视图)

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

a67e403a1d4de982e83d45b6765f985d.png

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

9d1302de460714bd6a0a753a0dfed976.png

64360884591454d5b8003b1b5222ea50.png

(对称的偏移)

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

3.2 使用法线向量

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

e2a20b9151b7d673d426ff3f7f67ff57.png

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

420ea7613a7cb98edf7b8ffadde2111b.png

9ad6e6d29e105e968006545ad7e260ef.png

(和法线向量一起偏移)

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

c5843a831fad0c5851d6d35e4c1d567f.png

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

5dab1e30fa64d8982b7cf4ea1ac0adf6.png

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

97261a7fd37f73be552d823787f082ec.png

c13b6a6fb7f11022f90124991f836054.gif

(淡化的折射)

3.3 仅折射水下

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

d3e6f5f5de98bd4db44cdbf3b5f7bdfe.png

(折射前景)

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

0b13a6d7e0e1b36676e920b6ded62b9d.png

f983e0cc109d6f288ceba1ec5904bed9.png

(过滤后的偏移)

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

857e47cf169577b0da55b378af49a579.png

d6c73967e4821f5c5e50094dccb51beb.png

(使用了正确的深度)

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

01e51a6ea2d953fb154250dcade8cfb6.png

(细的摆动的伪影线)

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

a171682c8b8286ed5027ca6353163fdb.png

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

5ed7d282215830aeb294b2321a69f55f.png

(对齐的纹理像素样本)

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

c1026931a627bfab7c3c2448a9a693e9.png

42e1328ecef00c2ffbf8f6de3ec49228.png

(平滑浅折射)

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

3d45e9918e61fb76b33ae37f31104887.gif

(最终的折射效果)

3.4 方向性流体

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

b1bacacdaa4acc9c364b0f63568201ac.png

cd9091e78449f13961d3ad2ece2f3fe6.gif

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

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

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

https://catlikecoding.com/unity/tutorials

bfcba45d8101ff39acefd2d918c946c9.png
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值