1. 基础形态部分
海洋参数收集
我们先收集一些海洋参数的基本信息

是否使用距离做衰减

从图由上到下来看,我们先收集海洋风力的信息 RG为风的方向。B为风力速度。
我们还需要确定波浪的大小和偏移。
再开启或者关闭距离衰减功能,距离衰减是有由顶点与相机距离决定的,通过计算出来的0到1的衰减,我们再决定衰减过程中有几级的LOD。
序列贴图与通道解码
海浪的大形状我们使用预先烘焙的一组序列帧贴图,这里序列帧一共烘焙了三种信息分别是
法线:

高度:

白沫

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


首先我们看法线解码,法线主要是受四个参数影响 分别是 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。

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

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

下一步便是将数据处理打包到MaterialAttributes中输出。
速度场与流体特性
Normal和Offset已经处理过了可以直接输出
Foam只需要加上自定义偏移和缩放再乘以距离衰减即可输出
Velocity比较特殊,主要是由法线演变而来v=N.xy*lerp(WaveTextureVelocityScaleBottom,WaveTextureVelocityScaleTop,SampleHeight)最后在叠加上风力v+=WaveTextureVelocityWind,在houdini中用同样的算法快速可视化速度方向


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



将法线和高度输出可以初步得到海洋的雏形。
细节波形叠加
距离分层
下一步即是在基础波形上添加细节法线,输入基础法线的组合打包信息。

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

三相位偏移叠加 (MF_AdvectWaveSample2D)
重点是第二部分 MF_AdvectWaveSample2D
一句话总结这个函数就是,采样三张细节贴图,通过基础波形的速度Velocity去进行uv偏移达到贴图流动的效果,通过flowmap一样的原理,以不同的UV偏移采样的这三张贴图通过不同的权重叠加起来,从而达到流动的效果并且去除贴图重复感的问题。
核心公式 $N = w1 * Tex1(UV + v1) +
w2 * Tex2(UV + v2) +
w3 * Tex3(UV + v3)$
一张贴图不使用Velocity进行UV偏移:

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

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

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



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

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

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

得到三套UV后就可以直接采样细节贴图。
将贴图的RGBA输入和基础波形一样的解码函数,解码出三套细节法线、导数、高度、白沫叠加输出最终细节法线、导数、高度、白沫。
将细节贴图的信息叠加到基础波形上打包输出。

这里每个信息的叠加方式都有所不同,以上基本上是围绕这海洋的法线与形态计算,我们还需要计算海洋比较重要的白沫信息。
白沫部分
海浪白沫这里面大概可以分为三个部分,白沫的贴图采样,白沫的各个参数调整与计算,白沫这一层的效果与之前的效果相互混合。

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



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



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



白沫计算块地方最重要的就是Opacity与OpacityMask的计算,OpacityMask是由Opacity得到了,这里就直接分析Opacity的逻辑了。
Opacity的构成也是分为两层,定义为软硬两层白沫,与上述的白沫内外两层对应。硬层也就是OpacityMask。这里挨个分析解释,首先是软层白沫:
软白沫
这里有个MF_FluidFoamShallow(水深权重控制白沫强度,水越浅白沫越弱)需要介绍一下:

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

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}$
硬白沫:

硬白沫也是通过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的所有表现。
得到了白沫层的表现还得与我们的水层表现做混合

这里介绍下颜色的混合和法线的混合方式
🔹 MF_CombineTranslucent
输入:
- TopRGB / TopA:上层颜色和透明度(比如泡沫层)。
- BottomRGB / BottomA:下层颜色和透明度(比如水层)。
节点逻辑:
-
Alpha 混合公式
-
$OutA=TopA+BottomA−TopA⋅BottomA$
→ 这是标准的 Porter-Duff “Over” 模式,算出最终透明度。
-
-
颜色混合公式
-
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 保证最后的结果不会因为叠加而失真。
至此最重要的几部分已经分析完成。
总结
目前我说的只是水体渲染效果相关的一部分方法,而不是完整的流程,完整的材质效果只有根据各自的项目做取舍。