UE5入门学习笔记——Gerstner波

Posted 真的是好麻烦啊!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE5入门学习笔记——Gerstner波相关的知识,希望对你有一定的参考价值。

一、前言

波形叠加是模拟水面的很常见的一种方法,它是通过叠加多个不同周期、不同振幅的周期函数来模拟水体表面的波动。使用的基础波形一般并不是通常的正弦(余弦)波,而是一种名为Gerstner波的特殊波形,与通常的正弦(余弦)波的区别在于,Gerstner波的波峰尖锐而波谷平缓,而且在波峰两侧有收紧的趋势,与真实海洋表面更加接近。波形如下图所示。

二、Gerstner波公式

公式参考Nvidia的《GPU Gems》(第一部)Chapter 1。

链接:https://developer.nvidia.com/gpugems/gpugems/part-i-natural-effects/chapter-1-effective-water-simulation-physical-models


其中:
波长(L):波与波峰之间的波峰距离。
频率(w):w与波长L有关,w = 2/L。
振幅 (A):从水平面到波峰的高度。
速度(S):波峰每秒向前移动的距离。将速度表示为相位常数φ很方便,其中 φ = S x 2/L。
方向 (D):垂直于波峰沿波峰行进的波前的水平矢量。
波浪陡度(Q):Q如果为0,则产生的是通常的正弦波,而如果Q = 1/(wA),则生成的是尖锐的波峰,如果继续提高Q,则会导致波峰形成环形。
时间变量(t):不断变化的时间变量,用以产生波移动的效果。

上图函数是对于多个不同Gerstner波的总和函数,这里将它写成单个波的公式则为:
对于波面上的一点P(这里以坐标系z轴朝上),有

Px = x + Q×A×Dx×cos(w×D·(x,y) + φ×t);
Py = y + Q×A×Dy×cos(w×D·(x,y) + φ×t);
Pz = A×sin(w×D·(x,y) + φ×t);

这里先用Shader做下测试(OpenGL中y轴朝上,所以上述函数中Py和Pz需要互换一下):


可见截面与前言中图所绘波形相同。

三、在UE5中实现

1、Gerstner波的实现

接着打开UE5,新建或打开一个项目,在左上角选择建模模式

选择Rect,创建一个宽度、深度为10001000,宽度细分、深度细分为500500(最大细分似乎只能500)的平面。


接着在内容管理器中新建一个材质函数,材质函数相当于将一系列材质操作封装成一个有输入和输出的函数,可以在多个材质中调用,避免这一系列材质操作重复编写。

接着双击打开新建的材质函数,在材质图表上右键,并输入input,选择FunctionInput节点。点击出现的红色节点,在细节面板的输入类型中可以设置输入值的类型,这里主要就用两种,一种是标量(float),另一种是向量2(vec2)。


根据函数公式,创建输入值对应的FunctionInput节点。


这里介绍几种需要用到的节点:

Mask
用于分离出向量(vector)的各个通道,在细节面板上可以勾选需要分离出的通道,假设仅勾选R,则输出的是x的值,如果勾选RG,则输出的是二维向量(x,y),其他以此类推。

Add、Subtract、Multiply、Divide
加、减、乘、除,A +/-/×/÷ B。

Normalize
向量标准化,就是将向量转化为模为1的向量。

Dot
向量点乘,点乘概念请自行搜索。

Sine
正弦函数。

Cosine
余弦函数。

Time
获取一个时间。

AppendMany
将多个值合成向量输出。

接着用节点将之前的单个波的函数表示出来。这里注意,之前Px和Py函数中需要加上x或y,在这里不需要,原因之后会讲。另外建议将Sine和Cosine节点的细节面板中的句号键设置为2π(默认是1.0,因为是float,设置为2π实际值应该输入6.283185),这个值表示的是一个周期的长度,设置为2π符合习惯。完成后保存。

接着在内容管理器中,新建一个材质,并双击打开。

这里先创建一个基础颜色,在材质图表上右键,并输入constant,选择Constant4Vector节点,双击打开取色器选择一个合适的颜色,然后将此节点连上材质结果节点中的基础颜色。


然后点击左下角内容侧滑菜单,打卡内容管理器,将其中之前的材质函数拖入材质图表,图表中出现了材质函数对应的节点。然后右键输入world,选择WorldPosition节点,然后点击在细节面板中将着色器偏移设置为绝对世界坐标(排除材质着色器偏移)

另外参数方面,float使用Constant,vec2使用Constant2Vector。将参数关联上材质函数的输入,然后材质函数的输出关联上材质结果节点中的全局位置偏移。都关联上后保存。

然后回到场景界面,给创建的平面添加这个材质,可以看见平面产生了波动,如果波动效果不对,则适当的调节一下输入的参数。

这里解释一下之前说的为什么不需要加上x或y,因为材质函数的输出关联上的全局位置偏移是在原坐标的基础上再加上连上的值,即原坐标是(x,y,z),材质函数的输出是(x1,y1,z1),则最终的结果则是(x+x1,y+y1,z+z1),如果依照Px和Py函数再加上x或y,则会导致整体多偏移了一个x或y,效果就会像图中一样,模型自身坐标轴还在原位,但显示的已经跑到别的地方去了。

2、对应法线的实现

这个时候的平面在不同角度看还是会显得很平,并不像表面在波动的样子,这是因为材质缺少法线,无法与光照形成阴影。
在《GPU Gems》1.2.2中阐述了求法线的方法,对周期函数求偏x和偏y的偏导数,分别为副法线(binormal)和切线(tangent),这里分别用B和T表示,然后将B和T做叉乘,求得的向量即为法线(normal),这里用N表示。
偏导数求法这里不过多赘述,简单来说求偏x的偏导数,就是将函数中的x视为变量,y视为常量来进行求导,求偏y的偏导数则与之相反。
这里直接写偏导数结果(注意:点积是化为x1y1+x2y2再来求导):

Bx = 1 - w×Dx×Q×A×Dx×sin(w×D·(x,y) + φ×t);
By = - w×Dx×Q×A×Dy×sin(w×D·(x,y) + φ×t);
Bz = w×Dx×A×cos(w×D·(x,y) + φ×t);

Tx = - w×Dy×Q×A×Dx×sin(w×D·(x,y) + φ×t);
Ty = 1 - w×Dy×Q×A×Dy×sin(w×D·(x,y) + φ×t);
Tz = w×Dy×A×cos(w×D·(x,y) + φ×t);

然后新建一个材质函数,接着用节点将B和T的函数表示出来,过程与上面的差不多。
这里新用到的一个节点是Cross节点,表示将A和B做叉乘。

完成之后保存。

接着打开之前的材质,将这个材质函数拖入材质图表,然后关联上相应的输入参数,将材质函数的输出连上材质结果节点上的Normal,最终的材质图表如图所示。

场景中平面的效果如下图,可以很明显的感受到波动的立体感。

UE4 Material 101学习笔记——23-29 水涟漪/水深/折射反射/Gerstner海浪/波光焦散/泡沫/FlowMap

UE4 Material 101学习笔记——23-29 水涟漪/水深/折射反射/Gerstner海浪/波光焦散/泡沫/FlowMap

本系列学习资料来源,Ben Cloward的油管空间,B站的搬运翻译

接下来的这个系列是一组新的关于水的shader
先来看看水吧


  • 水的表面有一些波纹的图案,不断旋转也有着比较混乱的外观
  • 随着深度增加,水似乎会改变颜色,一开始很透明,但是水越深越不透明了,看到更多颜色
  • 水面有在反射周围的环境,但是当直接从上往下看水的时候,就看不到了
  • 波纹和涟漪组成着非常有趣的变形图案
  • 水折射光线,导致底部的岩石和其他物体扭曲
  • 急流中有泡沫,水流中间块两边慢,水在岩石周围旋转……

水有很多很多特征,但是我们会专注于实现以下这些

Lec23 水的表面涟漪 Water Ripples Shader

1 法线图与panner

原视频地址,内有本节的贴图链接
首先我们需要一个较好看的水的法线贴图,去goole上搜一搜

我们使用的法线图如下

使用一个叫做panner的节点来滚动纹理,传入一个速度的变量以后,得到这种效果


2 世界位置投影与多次叠加

但是这样存在一些问题
它移动的很快,真实的水的运动是很混乱的,但目前动的太统一了

我们把panner节点的输出接到UV上,在前面使用世界位置投影,让水只在XY平面有


可以看到我们现在是在对角线上移动,因为我们的Speed同时作用了UV

于是进行改动,可以把Speed连出来的常量改成二维向量

我们如果多次复制以上这一组合,那么就可以在涟漪中制造一些混乱
也就是多次采样法线图,朝着不同方向滚动,进行叠加


我们再把Z补上,使用的是自动算Z的这个节点



看着很像水,不过,在本节中不涉及反射折射透明度,只专注于涟漪的实现

除了波纹,我们还可以再复制一组法线,调慢速度制造慢一点的波浪

甚至我们还可以把之前写雨滴涟漪的方法放进来


下一节,我们尝试水的深度颜色和深度透明度

Lec24 水深着色器 Water Depth Shader

在进入这节之前,我们可以先看看最终的完整的水的着色器会是什么效果

我们会有涟漪,有水深,有折射有反射,这就是我们的目标

下面第一件事,在上一节shader基础上,选择混合模式为半透明

接下来我们就要来解决深度问题

有两个因素会影响水的不透明度,一个是我们看水面的角度,一个是水的深度

1 水深测量——SceneDepth与PixelDepth

所以我们需要有一种方式去测量水深

下面介绍第一种方式

该方式有几个节点可以用到,SceneDepth和PixelDepth
我们用一张图来辅助理解这两个节点在干什么
Scene Depth返回从相机方向开始,到场景最近的一个像素。会忽略透明物体
Pixel Depth返回从相机到这个半透明水面的距离
两者相减得到一个新矢量,也就得到了一种深度

我们简单的获取蓝色的向量,做一个除法和幂次操作

构建一个小的场景,围一个范围,里面摆些演示,摆上一个面片附加我们的shader,可以看到效果

表现了一些深度近似值,从有一点黑,到变得纯白
这就是我们需要的数据,我们可以根据这个来创建蒙版

但是存在一些奇怪的现象

以下是我们垂直看的效果

然后这是我们水平看的效果

我们的角度越水平,相减得到的向量越大,越不透明

计算的深度是深紫色的深度,然而浅色的深度才是真正的深度


我们怎么计算实际的深度呢?

因为之前的两个depth,引擎都帮我们算好了值,所以我们来相除,得到比例

通过相机位置减去世界位置,得到Pixel depth的向量值,乘以比例得到Screen depth的向量值,两个向量相减,拿到Z的差距,也就是深度了(原视频的做法我无法理解,于是用自己的方法了)

  • 下图是原视频的做法(作者一开始那个70代表该面片的Z)

可以看到用自己的方法“水深”也是不变的

2 水的透明度——角度

影响水的透明度的因素还有看水面的角度,我们会使用Fresnel节点

  • 原理类似这种



我们更改根节点的光照方式,然后连上法线,在结果乘上一个深度衰减来柔和边缘


哦,还有一点,我们做的是不透明度,所以最后要连回不透明度

可以看到深度达成了

3 水的颜色渐变

我们还知道,如果深度改变会变颜色,所以我们可以插入颜色


本节全节点如下

Lec25 水体反射和折射 Water Reflection & Refraction Shader

为了更好的表现反射和折射,我们把此前的链接都断开,只是给一个粗糙度常数为0

我们的场景反射了天空,但是,岩石的反射呢?

这就要涉及UE4中反射的实现方式,反射是很复杂的,一般来说在UE4中有5种不同的实现方式,UE4中许多场景都是采用多种反射方式组合使用

这里介绍其中三种

第一种就是天空盒,正如图上所示,但是如果我们要反射更近的局部对象呢?

也就是Light Probe光照探针

在默认的场景中,这个图标的名字是球体反射捕获,它做的事情,是从这一点开始,捕获一个cubemap,然后将该cubemap用于创建反射

我们创建一个探针,可以看到效果

这样很方便偏移,只要捕捉一次立方体贴图就可以拿来反射

但是还是有问题的,因为这只是从探针那个点来说是正确的,一但移开就有错误

因此探针反射,虽然便宜但也有缺点,只是比单纯用天空盒好些

第三种方法是屏幕空间反射
关于这个方法就要回到shader的根节点上,勾上红框中的选择


效果很好,但是屏幕空间反射只能用屏幕数据,一但不在屏幕内的物体就看不到了

我们可以三个一起用,同时先把雨水的涟漪去掉,调低一点水面的波纹


但是还有问题,这个石头太完整了,缺失了折射效果
这在根节点的设置中也有



这一节没有对材质节点做很大修改,主要是设置方面

Lec26 Gerstner算法的海浪 Water Gerstner Waves

在这节中,我们会为我们的水加上几何波

1 交界修正

我们现在是有水的波纹,但是它很小,并且观察交接处只是僵硬的连在一起

所以我们这节要做的是就是给水面添加动画

但是,回到之前的shader,我们发现水面交界处有这种绿线,看着很奇怪

这是深度渐变节点造成的
我们小作修改

我们注意到,这个折射效果各处都一样

我们希望在水的边缘处减少折射的效果,这时候深度渐变节点又可以派上用处

2 曲面细分

接下来我们正式可以进入本节几何动画的内容了,我们用线框模式查看场景,可以看到我们所用的水面,其线框是很稀疏的,三角形不多

如果就这样进行几何移动,我们会有很粗糙的动画效果,我们进入shader的根节点,设置曲面细分,这样就能方便对顶点进行动画处理


但细分之后其实性能消耗也更昂贵,要注意谨慎控制这个值

所以我们为了更好的控制细分,我们一般会使用相机和表面的距离来减少曲面细分乘数,越远,三角形越少。似乎有些偏题,所以这次我们只用一个常数0.5

到这里,我们终于可以开始操作几何了,通常的我们会使用这个

但因为使用了曲面细分而不是真实网格,我们会操作这个东西

3 Gerstner算法与实现

我们会使用数学公式来模拟海浪,也就是Gerstner公式

  • 关于Gerstner方法,这里找到了几篇很不错的博客
    水面的简单渲染 – Gerstner波
    Gerstner 水波详解

  • 大意如下,一般的波动效果,诸如sin函数,只是顶点y坐标在时间函数下上下波动,顶点根本没有发生水平移动。
    但是实际的水不仅仅只有表面。下面还有更多的水。
    当表面的水向下移动时,其下方的水会怎么运动?当表面向上移动时,什么填充了其下面的空间?

  • 事实证明,表面点不仅向上和向下移动,而且也向前和向后移动。
    它们有一半的时间与波一起移动,而另一半则沿相反的方向移动。表面以下的水也是如此,但越深,运动就越少。

  • 下图为正弦波与Gerstner波

    Gerstner波以参数方程的形式给出:

    自变量为p,参数Q、D、A用来控制形状。Q控制波峰的尖锐度,D控制波长,A为振幅。

我们直接在材质编辑器中开始做(说实话后面它这么连到最后实现这个波,我是不明觉厉的,求大神细细理论分析,我爬了.jpg)
以下节点连到世界场景位移中


开始动起来了,是很像水,但是还缺少一些东西

Gerstner算法的一部分是进行坡度控制,但是我们现在还没有添加,所以看起来这个运动是非常平滑的

Gerstner算法能让我们有控制波峰尖锐与否,以及波浪是否陡峭的能力,于是我们接着实现

同时调整一下数据,让其更尖锐,效果更明显


但这样确实真的不像水,我们还有几件事没做

首先我们改变了表面的形状,但是我们却没有改变法线,所以就会感觉虽然动起来很像波,但是看着还是光照很平坦的样子

所以我们需要重新计算法线,新算一份法线,结合之前的法线


调整一下数值

这样很不错,但是现实中不只是有一种波在动,而是很多不同的波在传播

于是我们想要构建材质函数,来通过多次调用,可控地实现这个功能

4 材质函数构建

将gerstner的方法进行材质函数的构建

回到原来的shader,我们构建三组不同的波进行叠加

这样看去就不那么具有重复性了

5 整理

好的,我们来整理和调整一下





Lec27 波光焦散 Water Caustics

本期的视频地址,内有本节用到的贴图链接
在制作之前,首先我们看看真实生活中的样子是什么样的

现实中,当我们进入水下
太阳照耀着水面,水面的涟漪扭曲着光照,把这些光照变成非常酷炫的样子
我们在UE中使用实现的方式,会是贴花(decal)

1 贴花 (decal)

贴花(decal)在游戏里面是运用的比较多的,因为他可以简单的把图片运用不同的叠加方式绘制到另一个物体的表面,比如说游戏中的脏迹,脚印,弹孔和血迹等,都是贴花的效果。

我们使用decal的原因,是我们想要这些焦散的效果投射到水下的所有东西上,但是又不想要去该shader
如下图,在绿框内的所有物体都会附上焦散效果

2 制作—纹理/扰动/世界坐标投影

下面我们开始做

新建一个材质球,在其根节点进行设置

这是我们用到的纹理


这里有这么个软件可以用来创造这种图案 下载地址

亮纹处模糊的效果如何实现?在ps中,蓝通道向左移动1像素,红通道向右移动1像素

我们开始连节点

主要思路是,进行世界坐标投影,保证三个方向都能得到正确的焦散结果
而后根据一组法线纹理作出扰动,去扰动两组运动速度不同的焦散纹理,进行叠加

拆分如下,世界坐标投影

两组纹理和扰动

可以看到,在三个方向都获得了很好的投影效果


将其放到水中,调节一下参数


非常酷炫

Lec28 浪花泡沫 Water Foam

本节视频地址,内有所使用的贴图链接

1 泡沫贴图

在这一节,我们要在26节水的着色器基础上加一些泡沫

一般来说,水的泡沫发生在与其他物体相撞很迅速的时候

在自己做的小场景,加上泡沫可能不太合理,不过我们也依旧试着做做。

我们首先用到一个泡沫贴图,它的三个通道图片如下,三种不同数量的泡沫



我们通过这些,要做的是让泡沫在某些区域变厚,其他区域变薄

因为泡沫它不是可以均匀混合的东西,它是致密的或者稀薄的,所以有三个mask,分别是薄中厚

接下来还有一个渐变图,依然是三个通道
泡沫出现表示为在左边是黑的,然后根据消失速度的不同,设置渐变
用来控制泡沫的数量


我们在使用纹理时,需要用到一个引擎自带的材质函数,右键func,选择MaterialFunctionCall,按照如下寻找到这个函数

该材质函数作用是,采样一个纹理四次,每次进行不同的位移,然后叠加起来,造成混乱
其内部是这样子的

2 边缘出现

为了控制泡沫出现的地方,我们会使用之前用过的深度图,让泡沫只在边缘出现

divide连出来的是这里

颜色这么设置

泡沫的应该是不透明的,也就是输出结果连到add

结果会是这样

这是一种用法,我们只用深度添加泡沫

但是我们也可以自己绘制mask,让mask投影在世界坐标下,去控制渐变图的UV

我们也可以在Gerstner的波峰的地方加入泡沫,通过数学计算,去控制渐变图的UV

Lec29 水流映射 Water Flow Maps

这一节我们要做FlowMapping

什么是FlowMapping?

我们此前做了水的shader,也有涟漪,但是这些涟漪(ripples)没有一个主要的方向,它只是泛泛的涟漪

但是如果我们想要制造一条溪流呢?他们有特定的流动方向,或者我们想要精确定义方向的时候该怎么办呢?

1 FlowMap绘制与使用

这时候,FlowMap就可以做到这一点,我们可以绘制一个纹理,其中每个像素都代表了流向,然后我们在shader中用它来移动涟漪。

我们可以用一个叫做FlowMap Painter的程序来绘制它,软件地址在这里


绘制个圈圈

转换成方向向量图

看看运动线

然后我们选择 Bake to Texture 即可获得纹理,导入UE4,我们来构建我们的FlowMap Shader

UE4自带了一些相关的FlowMap的函数去使用,但是我们本节的目的是了解其背后的数学原理

简单示例,可以看到这种扭曲效果带来的影响

我们加入时间的影响,同时用锯齿波来进行时间动画的制作

测试的效果如下

2 材质函数应用

我们基于此,制作材质函数

在shader中使用

断开原来的Normal和世界场景偏移

得到效果

3 后续使用

这是一种通用的FlowMap的使用方式,在实际使用中,我们更可能使用顶视图观察场景


进行截图之后PS裁剪处理,导入之前的FlowMapPainter,输入路径即可叠加

这样我们就能根据实际场景,自己画自己的FlowMap

以上是关于UE5入门学习笔记——Gerstner波的主要内容,如果未能解决你的问题,请参考以下文章

UE5 Shader基础学习笔记——13-20 DetailNormal/Smoothstep/Length/CeilFloorRound/DDXDDY/SinCos/Power

UE5 Shader基础学习笔记——13-20 DetailNormal/Smoothstep/Length/CeilFloorRound/DDXDDY/SinCos/Power

UE5学习笔记——蓝图基础之基础节点知识

UE5 Shader基础学习笔记——01-12 图形管线/创建shader/数学节点/贴图压缩/LerpDotUV/常用向量/坐标空间/MinMaxClampSaturate/法线贴图混合

UE5 Shader基础学习笔记——01-12 图形管线/创建shader/数学节点/贴图压缩/LerpDotUV/常用向量/坐标空间/MinMaxClampSaturate/法线贴图混合

UE5快速入门教程