虚幻海洋材质
虚幻海洋材质

虚幻海洋材质

1. 基础形态部分

海洋参数收集

我们先收集一些海洋参数的基本信息

image.png

是否使用距离做衰减

image.png

从图由上到下来看,我们先收集海洋风力的信息 RG为风的方向。B为风力速度。

我们还需要确定波浪的大小和偏移。

再开启或者关闭距离衰减功能,距离衰减是有由顶点与相机距离决定的,通过计算出来的0到1的衰减,我们再决定衰减过程中有几级的LOD。

序列贴图与通道解码

海浪的大形状我们使用预先烘焙的一组序列帧贴图,这里序列帧一共烘焙了三种信息分别是

法线:

image.png

高度:

image.png

白沫

image.png

这里我们需要将这张图的各个信息通道解码出我们需要的真实值

image.png

image.png

首先我们看法线解码,法线主要是受四个参数影响 分别是 DecodeNormal 、WaveLength、DecodeHeight、WaveHeight,可以看出来海浪的高与宽都会对应的影响海面法线的强弱。

但是说白了这个法线解码也就是 RG存储法线0到1的信息,RG – 0.5 将法线映射到-0.5到0.5的范围,之后法线的强弱就与法线RG所在的范围空间有关了,最终输出法线的时候 append Z方向法线强度为1的值归一化后输出即可。

而海浪的Height输出与Foam范围输出就比较简单直接为采样贴图的B 与 A 通道。

偏移与高度调制

比较有意思的是海浪的Offset,海浪的Offset可以拆分为上下与左右两个方向的offset叠加,上下Offset其实好理解就是采样得到的贴图height 加上 heightoffset参数调整整体高度,再乘上WaveHeight。对于左右Offset我们是通过解码法线过程中得到的切空间法线的X与Y。

image.png

根据WaveChoppiness参数来调整波浪左右Offset的强度。

波浪左右的Offset强度计算就为 N.xy * -1 * WaveChoppiness * (1-SampleHeight)

这里SampleHeight为一个0到1的高度值,法线为什么会乘以-1再乘以WaveChoppiness 是因为我们需要让波浪往内凹,但是浪尖是不需要这样内凹的,这样会产生mesh穿插的错误。通过这步左右的Offset可以让我们产生波谷疏 波峰密的网格形状,这样海面Mesh上面的法线也会出现拉伸撕裂的感觉。

image.png

我们解码了贴图的各个通道值,得到海洋的Normal、Offset、SampleHeight(0-1)、SampleFoam(0-1)

image.png

下一步便是将数据处理打包到MaterialAttributes中输出。

速度场与流体特性

Normal和Offset已经处理过了可以直接输出

Foam只需要加上自定义偏移和缩放再乘以距离衰减即可输出

Velocity比较特殊,主要是由法线演变而来v=N.xy*lerp(WaveTextureVelocityScaleBottom,WaveTextureVelocityScaleTop,SampleHeight)最后在叠加上风力v+=WaveTextureVelocityWind,在houdini中用同样的算法快速可视化速度方向

image.png

V.gif

Divergence代表了流体的扩张和收缩,也代表基础浪的波峰和波谷。

ocean.gif

image.png

image.png

将法线和高度输出可以初步得到海洋的雏形。

细节波形叠加

距离分层

下一步即是在基础波形上添加细节法线,输入基础法线的组合打包信息。

image.png

这里可以看见叠加细节波大概就分两部分。一部分是通过距离分层,小于一定范围的波浪才使用参数叠加细节波。远处还是使用基础波。

image.png

三相位偏移叠加 (MF_AdvectWaveSample2D)

重点是第二部分 MF_AdvectWaveSample2D

一句话总结这个函数就是,采样三张细节贴图,通过基础波形的速度Velocity去进行uv偏移达到贴图流动的效果,通过flowmap一样的原理,以不同的UV偏移采样的这三张贴图通过不同的权重叠加起来,从而达到流动的效果并且去除贴图重复感的问题。

核心公式 $N = w1 * Tex1(UV + v1) +
w2 * Tex2(UV + v2) +
w3 * Tex3(UV + v3)$

一张贴图不使用Velocity进行UV偏移:

ocean1.gif

一张贴图使用Velocity进行UV偏移:

ocean2.gif

三张贴图不同offset叠加使用Velocity进行UV偏移:

ocean3.gif

这是根据问题一步步优化而来的最终结果,从一开始的单张细节贴图平铺

image.png

image.png

image.png

MF_AdvectWaveSample2D内部构成主要是计算三张贴图不同uv和权重采样贴图解码并通过权重混合。

image.png

uv偏移部分我们是通过一个0到1循环的Time,三张贴图的UV偏移和权重都分别记录在RGB三个通道中。乘上基础波形所提供的速度,最后在将三套不同的偏移应用到基础UV上。

image.png

RGB对应的UV偏移很好看,分别是+0、+0.3、+0.6,权重的话可以看下图,当UV从1跳变到0的时候可以看到所表现的权重为0。

image.png

得到三套UV后就可以直接采样细节贴图。

将贴图的RGBA输入和基础波形一样的解码函数,解码出三套细节法线、导数、高度、白沫叠加输出最终细节法线、导数、高度、白沫。

将细节贴图的信息叠加到基础波形上打包输出。

image.png

这里每个信息的叠加方式都有所不同,以上基本上是围绕这海洋的法线与形态计算,我们还需要计算海洋比较重要的白沫信息。

白沫部分

海浪白沫这里面大概可以分为三个部分,白沫的贴图采样,白沫的各个参数调整与计算,白沫这一层的效果与之前的效果相互混合。

image.png

贴图采样部分:

贴图采样部分总的来说没有什么难度,和细节法线的采样一样都是根据UV和速度信息做了三套UV的偏移采样三张贴图,根据不同的权重进行混合。唯一有点区别的是白沫的采样UV进行过一些扭曲处理才输入进去采样的。

image.png

image.png

image.png

这里贴一下这张贴图解释一下各个通道含义

image.png

image.png

image.png

这里RG输出为NormalXY&Normal、B输出为Soft、A输出为Height。

白沫的运算这里比较复杂,白沫的颜色分为内层与外层,由OpacityMask做为权重混合,内层为平铺的贴图效果,外层为纯色。内层白沫模拟的是水面产生的白沫效果(由法线B通道与白沫高度叠加构成),外层白沫模拟的是水底的白沫效果(由纯色白沫与水颜色混合)。

image.png

image.png

image.png

白沫计算块地方最重要的就是Opacity与OpacityMask的计算,OpacityMask是由Opacity得到了,这里就直接分析Opacity的逻辑了。

Opacity的构成也是分为两层,定义为软硬两层白沫,与上述的白沫内外两层对应。硬层也就是OpacityMask。这里挨个分析解释,首先是软层白沫:

软白沫

这里有个MF_FluidFoamShallow(水深权重控制白沫强度,水越浅白沫越弱)需要介绍一下:

image.png

$FoamShallow=saturate((Height+FoamShallowOffset)⋅(Depth⋅FoamShallowScale))$

image.png

  • Distance(0, 0) → 其实这里就是算一个速度长度/流速大小
  • Distance × FoamSoftVelocity速度带来的额外泡沫软化量
  • FoamSoftBase + (上面结果) → 泡沫软化基础值 + 流速修正。
  • Min(结果, FoamSoftMax) → 限制最大软化值。

FoamOpacitySoft= (min(FoamSoftBase+∣Velocity∣⋅FoamSoftVelocity,FoamSoftMax)⋅(SurfaceLayerFoam+1)) * (saturate((Height+FoamShallowOffset)⋅(Depth⋅FoamShallowScale))⋅SurfaceLayerFoam) * FoamTexSoft

$\begin{aligned}
FoamOpacitySoft &=
\underbrace{\min!\big(FoamSoftBase + \lVert Velocity \rVert \cdot FoamSoftVelocity,; FoamSoftMax\big)}{\text{速度驱动的软化}}
\cdot (SurfaceLayerFoam + 1) \
&\cdot \underbrace{\operatorname{saturate}!\big((Height + FoamShallowOffset)\cdot(Depth \cdot FoamShallowScale)\big)}{\text{浅水权重}}
\cdot SurfaceLayerFoam \
&\cdot \underbrace{FoamTexSoft}_{\text{泡沫贴图软化}}
\end{aligned}$

硬白沫:

image.png

硬白沫也是通过MF_FluidFoamShallow(水深权重控制),

FoamHardnessIntensity 控制边缘强度

FoamHardnessWidth 控制边缘宽度

✅ 最终整理:

$\text{FoamOpacityHardness} = \operatorname{saturate}!\Bigl(\text{FoamTexHeight} –
\operatorname{saturate}!\Bigl(
\left(\dfrac{\text{FoamHardnessIntensity}+1}{\text{FoamHardnessWidth}-1}\right)
\times \Bigl((\text{FoamHardnessWidth}-1)+\text{MF_FluidFoamShallow.Result}\times \text{SurfaceLayerFoam}\Bigr)
\Bigr)\Bigr)$

我们的OpacityMask即等于

$OpacityMask=saturate(FoamOpacityHardness*FoamNormalBlend)$

我们最终的Opacity就由软白沫与硬白沫层叠加得到:

$Opacity=saturate(max(FoamHardness*FoamOpacitySoft ))$

最后再将白沫的FoamSpecular、FoamRoughness、FoamTexNormal和我们计算得到BaseColor、Opacity、OpacityMask输入到MakeMaterialAttributes中,这样就得到了FoamLayer的所有表现。

得到了白沫层的表现还得与我们的水层表现做混合

image.png

这里介绍下颜色的混合和法线的混合方式

🔹 MF_CombineTranslucent

输入:

  • TopRGB / TopA:上层颜色和透明度(比如泡沫层)。
  • BottomRGB / BottomA:下层颜色和透明度(比如水层)。

节点逻辑:

  1. Alpha 混合公式

    • $OutA=TopA+BottomA−TopA⋅BottomA$

      → 这是标准的 Porter-Duff “Over” 模式,算出最终透明度。

  2. 颜色混合公式

    • OutRGB = Lerp(BottomRGB*BottomA, TopRGB, TopA) / max(OutA, 0.0001)

    • 实际上它等价于:

      $\boxed{\text{OutRGB}=\dfrac{\text{TopRGB}\cdot\text{TopA}+\text{BottomRGB}\cdot\text{BottomA}\cdot(1-\text{TopA})}{\max!\big(\text{TopA}+\text{BottomA}-\text{TopA}\cdot\text{BottomA},,0.0001\big)}}$

      解释:

      保证两层颜色在透明度范围内正确合成。

总结:MF_CombineTranslucent 就是一个“上下两层透明混合器”,输出合成后的颜色和透明度。

🔹 数学形式

如果记:

  • 水层法线 = $Nwater=(xw,yw,zw)$
  • 泡沫法线 = $Nfoam=(xf,yf,zf)$

那么合成法线的过程是:

$Nmix=normalize((xw+xf⋅xs,    yw+yf⋅ys,    zw))$

其中 $xs=FoamNormalScalesxfzw

$

$ys=FoamNormalScalesyfzw$


🔹 效果直观理解

  • FoamNormalScale 小 → 泡沫扰动很轻微,几乎就是水的法线。
  • FoamNormalScale 大 → 泡沫的“凹凸感”更强,覆盖掉更多水面的平滑性。
  • Normalize 保证最后的结果不会因为叠加而失真。

至此最重要的几部分已经分析完成。

总结

目前我说的只是水体渲染效果相关的一部分方法,而不是完整的流程,完整的材质效果只有根据各自的项目做取舍。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注