UE5 材质 Water Shader

目标

  • 水有许多特性,本篇将着重实现如下特性
    • 表面涟漪
    • 水深效果
    • 水的反射和折射
    • 海浪
    • 波光的焦散

表面涟漪

初步实现

image-20230520150152153
image

模拟水的运动

  • 现实中水的流动是较为混乱的,但上图的水流动过于统一。我们使用世界坐标的位置进行投影,且让水只在XY平面进行运动
    image-20230520152927951
  • 如何让水的流动混乱呢?很容易想到,我们需要让纹理图在不同方向上进行变换
    image-20230520154703210
    image

水深

影响水透明度的因素

  • 看向水面的角度
  • 水深。水越深,会变得越来越不透明

设置blend mode

  • 因为这里涉及到半透明渲染,我们需要选择"Blend Mode"为"Translucent"
    image-20230520155955692

测量水深

"SceneDepth" & "PixelDepth"

步骤

  • SceneDepth:以相机为视角,返回场景中离它最近的一个物体的距离且忽略透明物体
    image-20230520161006162
  • PixelDepth:以相机为视角,返回它到半透明物体的距离
    image-20230520161223225

实现

  • 初步实现
    image-20230520162419701
    image-20230520162441733

    从上图可以看出这正是我们想要的效果,从浅到深,刚开始有些黑色,然后变白

  • 纠正视角问题

    • 问题
      当我们垂直看去,得到的效果如下
      image-20230520162843968

      当我们水平看去,得到的效果如下
      image-20230520162939926

      可以看出因为角度的问题,求得的深度值也有所不同,角度越偏向水平,得到的深度值越大,透明度越低

    • 原因

      很明显水深并不是"SceneDepth - PixelDepth",而是垂直的深度,这里用相似即可实现
      image-20230520163533817

    • 实现
      image-20230520171132082
      image-20230520171310120
      image-20230520171334922

测量角度

目标

  • 当视角越来越垂直水面,水越来越透明
  • 当视角越来越平行水面,水越来越不透明

实现

水颜色的渐变

目标

  • 因为水的颜色收到深度的影响,因此我们需要创建两种颜色的水,一种代表深处的颜色,一种代表浅处的颜色

实现

image-20230520175855482
image-20230521145104621

以下是水深的全部实现内容
image-20230521145510602

水的反射和折射

UE中的反射

  • UE中的反射有五种不同的实现方法,且许多场景不单单使用一种反射而是多种反射方法相结合。在这里我们介绍其中三种

    • sky box

      通过sky box可以反射天空,但是它无法反射场景的物体。

    • Light Probe(光照探针)

      Light Probe可以解决sky box的缺点。简单来说,Light Probe给予场景中某个点向它周围收集光照的能力,并记录周围的光照(irradiance map),也就是cubemap。随后对某个像素点进行渲染时,利用它附近的probe的光照信息估计该点所受光照
      img

      • 缺点
        • 只适用于静态物体和静态光照。因为只是从Light Probe那个点来说光照是正确的,但如果从其他方向看去会发现反射是错误的
    • 屏幕空间反射(Screen Space Reflections)

      简单来说,Screen Space Reflections以屏幕数据计算反射效果。因为它需要G-Buffer中的normal,所以只适用于延迟渲染

      • 缺点
        • 成本高
        • 只能反射屏幕内的物体,对屏幕外的无效

UE中的折射

  • UE5提供了两种折射模式,一种是基于物理的无normalmap的折射模式"Index of Refraction";另一种是不基于物理的normal的折射模式"Pixel Normal Offset"

  • 这两种模式各有长处

    • Index of Refraction模拟光线在介质间传播时的折射方式。适用于小物体,对于较大的物体很可能会带有瑕疵,因为当前屏幕中的物体的颜色很可能从屏幕外获取

    • Pixel Normal Offset以vertex normal为基础,计算每个pixel的normal和vertex normal的差异来得出折射偏移。适用于较大平面,因为无需从屏幕外读取数据。需要注意的是,若参数"Refraction Depth Bias" > 1,法线将沿平面平移

      • 参数Refraction Depth Bias

        Refraction Depth Bias用于防止距离较近的对象以尖锐的(acute)视角渲染到扭曲的表面。但可能会增加表面和折射位置的距离

启用反射和折射

  • 在根节点处启用"Screen Space Reflections"
    image-20230521153144962
  • 在根节点处选用"pixel normal offset"
    image-20230521153416422
  • 效果
    • 没有开启折射
    • 开启折射

海浪

海浪算法

思想

  • 水体渲染主要运用两个表面的模拟:一个用于表面网格的几何波动,另一个是网格上法线图的扰动。而水面高度由简单的周期波叠加表示
  • 因为水的波纹呈sin分布,所以我们基于简单的sin函数进行叠加可以得到一个连续函数,该函数描述水面上所有点的高度和方向

波的选择

  • 在正式开始前,我们需要了解不同类型的波浪应该用哪种波形
    • 对于受影响生成的波,应使用方向波。对于方向波,波的方向是在风的一定范围内画的
    • 对于平静的水面,生成的波并不是因为风(如瀑布),应使用圆形波。对于圆形波,波中心是在限定范围内任意画的
      image-20230521193006821

为什么选择Gerstner算法?

  • 因为Gerstner算法有一个特性,它将顶点朝着每个浪头顶部移动,从而形成更尖锐的波峰。而这一特性,正是我们所需要的
    img

从sin函数开始

  • 由于sin函数在x轴方向不变,在y轴方向上下移动,使得它形成的面更为圆滑,因此sin函数更适合平静的湖面
参数选择
  • 对于波的形成,我们需要考虑以下几个参数

    1. 波长(\(L\)):world space中波峰到波峰的间距。\(L\)\(\omega\)(角频率)的关系 \(\omega = \frac{2\pi}{L}\)
    2. 振幅(A):水平面到波峰的间距
    3. 速度(S):波峰每秒移动的距离。为方便将S表示为相位常数\(\varphi = S \frac{2\pi}{L}\)
    4. 方向(D):垂直于波面且的水平向量
  • 波的状态定义为位置(x,y)和时间t的函数:\(W_i(x,y,t) = A_i \times sin(D_i · (x,y) \times \omega_i + t \times \varphi_i)\)

    img

  • 所有波i的总表面:\(W_i(x,y,t) = \sum A_i \times sin(D_i · (x,y) \times \omega_i + t \times \varphi_i)\)

法线和切线
  • 切线和副切线分别是y方向和x方向上的偏导
  • 现有2d平面的任意点\((x,y)\),表面的3d坐标\(p(x,y,t) = (x,y,H(x,y,t))\)
    • 切线即为对y轴求偏导:\(T(x,y) = (0, 1, \frac{\partial}{\partial y}(H(x,y,t))\)
    • 副切线即为对x轴求偏导:\(B(x,y) = (1, 0, \frac{\partial}{\partial x}(H(x,y,t)))\)
    • 法线即为副切线和切线的叉乘:\(N(x,y) = (-\frac{\partial}{\partial x}(H(x,y,t)), -\frac{\partial}{\partial y}(H(x,y,t), 1)\)

Gerstner波

  • Gerstner波有一个特性,它将顶点朝着每个浪头顶部移动,从而形成更尖锐的波峰,这适用于粗犷的海洋
  • Gerstner波中每一个水分子都在做圆周运动,这意味着x,y都在变化(而sin函数只有y变化,x不变),且水分子在波峰聚集,在波谷分散,越靠近水面,圆周运动的半径越大
定义
  • Gerstner波定义

    \(P(x,y,t) = \left(\begin{array}{cc} x + \sum(Q_i A_i \times D_i.x \times cos(\omega_i D_i·(x,y) + \varphi_i t)) \\ y + \sum(Q_iA_i \times D_i.y \times cos(\omega_i D_i·(x,y) + \varphi_i t)) \\ \sum(A_i sin(\omega_i D_i·(x,y) + \varphi_i t)) \end{array}\right)\),其中\(Q_i = \frac{1}{\omega_i A_i}\)

  • 对于单个波i,\(Q_i = 0\)时,Gerstner波为sin波

    • 使用\(Q_i = \frac{Q}{\omega_i A_i \times numWaves}\)可以控制波的平滑或尖锐程度
    • 需要注意的是,应当避免使用过大的\(Q_i\),这会导致波峰形成环
      img
切线和法线
  • 副切线\(B = \left(\begin{array}{cc} 1-\sum(Q_i \times D_i.x^2 \times WA \times S()) \\ -\sum(Q_i \times D_i.x \times D_i.y \times WA \times S()) \\ \sum(D_i.x \times WA \times C()) \end {array}\right)\)
  • 切线\(T = \left(\begin{array}{cc} -\sum(Q_i \times D_i.x \times D_i.y \times WA \times S()) \\ 1 - \sum(Q_i \times D_i.y^2 \times WA \times S()) \\ \sum(D_i.y \times WA \times C()) \end{array}\right)\)
  • 法线\(N = \left(\begin {array}{cc} -\sum(D_i.x \times WA \times C()) \\ -\sum(D_i.y \times WA \times C()) \\ 1 - \sum(Q_i \times WA \times S()) \end{array}\right)\)
    其中:
    • $ WA = \omega_i \times A_i $
    • $ S() = sin(\omega_i \times D_i · P + \varphi_i t) $
    • $ C() = cos(\omega_i \times D_i · P + \varphi_i t) $
参数
  • 波长

    这里波长的选择不是根据现实而定,而是使用少数几个波达到最大效果。因此我们选择中等的波长,以它的\(\frac{1}{2}\)\(2\)倍间产生任意波长

  • 波速

    波速与波长L、重力g(国际单位\(9.8m/s^2\))相关:\(S = \sqrt{g \times \frac{2\pi}{L}}\)

  • 振幅

    在Shader中指定一个系数,由美术人员对波长指定对应的合适振幅

  • 方向

    波的运动方向和其他参数完全独立,可以自由选择

实现

  • 解决水面交界的绿线

    从上图可以看到水面和岩石及墙体的交界都有一道绿线,这是由"depth fade"节点造成的。"depth fade"在我们的实现中仅仅用于水的不透明度,但我们却将其用在水的颜色
    image-20230521162239829

  • 降低水和物体交界处的折射效果以交界处的硬线

    从上图可以看到在物体和水面的交界处折射效果十分强烈,但我们希望在交界处的折射效果不那么强烈。为了降低折射效果我们需要用到"depth fade"节点

    image-20230521174405103

海浪

  • 套用上述公式,可以轻易求得海浪的坐标变化和法线变化
    • 坐标变化


      简单地改变海浪的尖锐程度、振幅和波长

    • 法线变化
      可以看到目前这个海浪更像波,因为我们并没有求得海浪的法线变化。
      海浪的法线变化求取如下

  • 叠加波
    从上面几张图来看目前实现的海浪其实一直在重复,为了避免重复性需要对波进行叠加

波光焦散

目标

  • 在现实中,当太阳照射水面,水面会扭曲这些光纤从而生成特别酷炫的纹理
    image-20230523161227367

原理

  • 对于这种纹理也是可以实现的,实现方式是贴花(decal)

    简单来说,运用在实时渲染中的贴花技术是屏幕空间的延迟贴花——利用现有的G-Buffer,直接将贴花投射在物体表面

  • 运作时间

    写入G-Buffer后,屏幕空间的后处理前

  • 与TBN类似,需要将世界空间转换到贴花空间,这样才能得到正确的贴花位置及其纹理。随后计算投影位置即可

根节点设置

  • 这里我们需要新建一个材质,并将根节点按如下设置
    image-20230523173017968

纹理

image-20230523173218299image-20230523173233120
image-20230523174652224

实现

初步实现

image-20230523173442096image-20230523173543387

加入动画

  • 简单的动画效果
    image-20230523174250523
    image

  • 扭曲效果

    目前的动画太有规律了,但现实中更像没有规律的。因此,我们需要将平移效果带点起伏
    image-20230523175928464
    image

  • 继续优化

    可以看到确实有那味儿了!若为了更好的效果,可以再加一个不同的法线扰动效果
    image-20230523181035090
    image

修正投影

  • 目前,我们的贴花是基于z轴投影的,这会导致z轴的贴花不会有理想效果
    image-20230523181753982
  • 如何修正呢?很简单,三个轴都进行投影
    image-20230523183811632
    image

浪花

目标

实现当海浪和物体高速相撞时产生的浪花

纹理

浪花数量纹理

在这里使用到了一个纹理贴图
image-20230820202047011

  • R通道下
    image-20230820202218863
  • G通道下
    image-20230820202258526
  • B通道下
    image-20230820202315365

从上面三幅图可以看出这其实是代表三种浪花数量的贴图,这是因为浪花并不是一种分布均匀的物质,事实上是在一些区域可能会很厚,而在另一些区域很薄

浪花渐变纹理

因为浪花并不是一起出现,也不是一起消失的,所以需要一种纹理用于控制它的渐变
image-20230820203231252

  • R通道
    image-20230820203258005
  • G通道
    image-20230820203316996
  • B通道
    image-20230820203338562

显然的,这三个通道分别表示三种渐变程度

实现

motion_4waychaos函数

  • UE引擎提供了motion_4waychaos这一函数,但需要在"内容浏览器"中启用"显示引擎内容"
    image-20230820210005295

  • 搜索"materialFunctionCall"
    image-20230820210128679

  • 选中它继续搜索"motion_4waychaos"
    image-20230820210222550

  • 函数的意义

    简单来讲,该函数就是对纹理制造运动混乱

    从实现不难看出该函数对同一纹理进行四次采样且四次uv都不相同,最后将他们相加
    image-20230820210406879

初步实现

image-20230820213233610

image-20230820213001108

可以看到效果还不错有许多浪花

控制浪花出现的位置

  • 设置寻址模式为clamp

    这样可以防止渐变重复出现
    image-20230820214901091

  • 以深度控制泡沫的多少
    image-20230820220114493

    "divide"连下图的mask(B)
    image-20230820220134112
    image-20230820222647266

上色和不透明度

image-20230820222922532

image-20230820223003314

最终效果

image-20230820223034934

水流

目标

实现类似溪流的水流效果,其有特定的流向

Flowmap的绘制及使用

Flowmap即水流贴图,其中每个像素表示水流的方向。但如何绘制该贴图呢?

有一款叫做FLOWMAP PAINTER软件专门用于绘制水流贴图

通过移动鼠标(下图中的圈圈)来绘制水流轨迹,绘制完毕后点击“Back to Texture”即可
image-20230820230754499

随后导入UE中,再将其转换为法线贴图
image-20230820231128419

实现

初步实现

image-20230820231407601

加入动画

image-20230820233429463

2023820

优化动画

从上图可以看到动画有终止,这并不是我们想要的水流效果

image-20230820233553175

2023820233613

最终效果

封装为材质函数
image-20230820235313389

最后连接至根节点的normal
image-20230820235502082

水下材质

加入后处理actor

依次点击“”放置Actor” 、”后期处理体积“”

image-20230822153932468

image-20230822154005909

随后将该actor覆盖的面积调整为整个水下的面积

最后新建一个材质,并为后处理actor添加一个材质
image-20230822154204732

赋予颜色

在开始前,需要将材质域改为“后期处理”

image-20230822164222390

image-20230822164420547

效果如下
image-20230822164706030

雾效

但实际上上图的效果并不完全是理想的效果,实际上在水下的效果应是越近越清晰,越远越模糊,且视野中间清晰,越向左右两边靠近越模糊,也就是这里需要实现的雾效

image-20230822170551559

image-20230822170607093

  • 远近雾效
    image-20230823022132114

    image-20230822171213766

  • 左右雾效

    image-20230822172014847
    该方法实现出了一个中心黑两边亮的圆球,但我们想要的中心亮两边黑且该球需要处于屏幕中间
    具体改动如下
    image-20230822172329515

  • 效果
    image-20230822172500389

模糊和锐化效果

  • 思路

    简单来说,实现模糊是加权平均的过程,依次选取屏幕上的像素点,对于每一个像素点都选取它周围的五个点,并进行加权求和
    image-20230823000853867

  • 模糊实现

    image-20230823003047581
    image-20230823003119922

  • 锐化实现
    对上图中的lerp的alpha值取负即可实现锐化
    image-20230823003341512

放大图像

image-20230823010045589

但我实际想要的是中间稍微放大,而边缘不怎么变化,这就需要用到mask

image-20230823012316202

扭曲效果

这个效果在之前总结过,这里不再提及推导过程
image-20230823012530647

整合

接下来就是将上面的实现结合起来

首先求出扭曲效果的uv坐标后将其与放大效果结合
image-20230823013130995

随后将新的uv与模糊效果结合
image-20230823015429577

但需要注意的模糊效果应是从中间到边缘效果逐渐递增,因此需要用到mask
image-20230823020453420

最后将得到的模糊后的颜色与水下颜色结合
image-20230823021724191

最终效果

reference

基于 Probe 的实时全局光照方案(Probe-based Global Illumination) - KillerAery - 博客园 (cnblogs.com)

[Siggraph15]Stochastic Screen-Space Reflections - 知乎 (zhihu.com)

虚幻引擎中的屏幕空间反射 | 虚幻引擎5.0文档 (unrealengine.com)

使用像素法线偏移实现折射 | 虚幻引擎文档 (unrealengine.com)

使用像素法线偏移实现折射 | 虚幻引擎文档 (unrealengine.com)

在虚幻引擎中使用像素法线偏移实现折射 | 虚幻引擎5.1文档 (unrealengine.com)

使用折射 | 虚幻引擎文档 (unrealengine.com)

GPU Gems1

[Game-Programmer-Study-Notes/README.md at master · QianMo/Game-Programmer-Study-Notes · GitHub](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/《GPU Gems 1》全书提炼总结/README.md#一、-用物理模型进行高效的水模拟(effective-water-simulation-from-physical-models))

水体渲染之Gerstner波形理解与推导 - 知乎 (zhihu.com)

贴花 | 虚幻引擎文档 (unrealengine.com)

谈谈游戏影视中的贴花decals技术 - 知乎 (zhihu.com)

posted @ 2023-05-23 23:22  爱莉希雅  阅读(898)  评论(0编辑  收藏  举报