水体系统背景
年份 | 游戏 | 实现方式 | 截图 |
---|---|---|---|
1996年 | 《Wave Race 64》 | Sin,屏幕分辨率小效果ok | |
1998年 | 《雷神之锤》 | 动态纹理制作的半透水 | |
2002年 | 《Super Mario Sunshine》 | 静态的几何波,实现比较基础的反射折射 用Mipmap技巧表现表面的Lod和模糊效果 | |
2006年 | 《FARCRY》 | 达到了本世代表现,有波纹效果。更准确的吸收、散射、折射 | |
2008年 | 《恐水症》 | 水和动态物体的交互。基于高度场的流体模拟系统 | |
2013年 | BattleField 4 | Tile离散傅里叶变换纹理 | |
2014年 | 《看门狗》 | 形变的频率与Gameplay的相互作用 | |
2016年 | 《神秘海域4》 | 改善了河流的几何流动模拟 | |
2018年 | 《盗贼之海》 | 虚幻引擎制作,离散傅里叶驱动波动,叠加程序化的泡沫和飞溅 | |
2018年 | 《刺客信条:奥德赛》 | 海岸线分层的波浪 |
UE4.26之前的水
UE引擎没有内置完整水系统,都是基于自制。常见做法使用一个平面mesh,指定一个水材质。而UE希望有网格的处理,有物理、模拟,能和地形进行交互,有完整工具链。
-
《Learning Water Graphics in UE4》 Hiroyuki Kobayashi
-
《VR Editor Demo Beach Scene》 Ryan Brucks et.al
UE4.26 水
- 统一的编辑工具
- 流动的河流
- 有深度的湖泊
- 海洋
- 高效且出色的渲染
- 模型
- 着色
- 具有确定性的波浪
Water插件
实验版本插件开启 非破坏性的修改地形 多种默认水体,基于(闭环)样条线的水体(同一高度上的样条线)创造工具,海洋(同一高度)、河流(有起点终点,可以不同高度,不同的河段有不同的流速)、湖泊(同一高度)能无缝连接。水体有水深、流速可和游戏玩法相结合。Body Custom只存储信息,不定义形状,用户可以去指定一个Static Mesh。 Water Body Actor只是编辑工具并不生成Mesh,Mesh由Water Mesh Actor生成并存储其中。
编辑工具
海洋是有距离限制的,默认大小是3平方公里,如果想让海洋继续延到地平线消失,可以在FarDistance中指定材质和延展距离,会在外部生成简化的低面数的Far Mesh并使用指定的材质。
波浪使用资产存储,cpu和gpu都可以访问这同一份数据。 可以调整波层数、波数默认是16层,可以对海洋波浪进行不错的模拟,堡垒之夜风格化渲染使用的6层,移动端可简化为4层 。每层都可以单独设置波长、振幅、方向、陡峭度。
GerstnerWave计算相对简单,状态独立的。(x、z坐标向波峰靠拢,y轴坐标做sin波计算。所以看起来波峰更尖锐,波谷更广阔(因为顶点向波峰靠拢了,波谷的顶点距离变更大了,看起来就更宽阔。)) 基本原理参见《GPU Gems的官方文档》.
- Qi是控制波浪陡度的参数,i是第i个波浪(也可以只有一个波浪,更好理解)。如果Qi是0,则是一般的正弦波。当Qi = 1/(wi*Ai)时,波峰最尖锐.应该避免Qi的值过大,不然会形成顶部的水波穿插,形成错误的网格顶点位置,甚至露出到水面背面的网格面。
- L(WaveLength)是世界坐标上波峰与波峰之间的距离。
- 参数wi是频率,是一个常数,wi = 2 π /L.根据正弦方程的标准函数f(x)=Asin(wx+β),最小正周期L就是L=2π/w。所以 w = 2π/L。
- Ai是水平面到波峰的高度。
- Di是波峰移动的水平向量。
- tφi 是初相,是一个常数。根据正弦型函数,ωx+φ叫做相位,φ叫做初相(即当x=0时的相位 )。t φi 决定了所有的点是否向左(t φi >0)或向右(t φi <0)平行移动t φi 个单位.t是时间。
- Di点乘(x,y)是可知两者的夹角方向,等于0为90度,大于0夹角是锐角。小于0夹角是钝角。(x,y)是水平位置(horizontal position)。
Unity C#实现GerstnerWave
public float height;
[Range(0, 1f)]
public float sharp = 0.5f;//尖锐
[Range(0.5f, 10f)]
public float speed = 2f;//初相
[Range(1, 50)]
public int waveT;//周期
public Vector2 WaveDir = Vector2.left;
private Vector3[] baseVertices;
private void OnEnable()
{
mesh = GetComponent<MeshFilter>().mesh;
baseVertices = mesh.vertices;
}
void Update()
{
Vector3[] vertices = this.mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
Vector3 vertice = this.baseVertices[i];
float A = this.height;
float w = (float)(2 * Math.PI / waveT);
float Qi = sharp / w * A;
float cosNum = Mathf.Cos(Time.time * speed + w * Vector2.Dot(WaveDir, new Vector2(vertices[i].x, vertices[i].z)));
float sinNum = Mathf.Sin(Time.time * speed + w * Vector2.Dot(WaveDir, new Vector2(vertices[i].x, vertices[i].z)));
vertice.x += Qi * A * WaveDir.x * cosNum;
vertice.z += Qi * A * WaveDir.y * cosNum;
vertice.y = sinNum * A;
vertices[i] = vertice;
}
this.mesh.vertices = vertices;
this.mesh.RecalculateNormals();//重新计算法线
}
Unity Shader实现GerstnerWave
Shader "Unlit/GerstnerWave"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_WaveLength("WaveLength", float) = 4//水波长度,世界空间中波之间的波峰到波峰的距离
_WaveAmplitude("WaveAmplitude", float) = 0.4//振幅,从水平面到波峰的高度
_WindDirection("WindDirection", Range(0, 360)) = 70//风方向
_WindSpeed("WindSpeed", float) = 0.4//风速系数
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
ZWrite On
ZTest On
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
float _WaveLength;
float _WaveAmplitude;
float _WindDirection;
float _WindSpeed;
struct Wave {
float3 vertex;
float3 normal;
};
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
Wave GerstnerWave(half2 pos, float waveCount, half waveLen, half amplitude, half direction, half windSpeed) {
Wave waveOut;
float time = _Time.y;
direction = radians(direction);
half2 D = normalize(half2(sin(direction), cos(direction)));//方向(D):垂直于波峰传播的波阵面的水平矢量。
half w = 6.28318 / waveLen;
half L = waveLen;//波长(L):世界空间中波之间的波峰到波峰的距离。
half A = amplitude;//振幅(A):从水平面到波峰的高度。
half S = windSpeed * sqrt(9.8 * w);//速度(S):波峰每秒向前移动的距离。
half Q = 1 / (A * w * waveCount);//陡度 (Q) : 控制水波的陡度。
half commonCalc = w * dot(D, pos) + time * S;
half cosC = cos(commonCalc);
half sinC = sin(commonCalc);
waveOut.vertex.xz = Q * A * D.xy * cosC;
waveOut.vertex.y = (A * sinC) / waveCount;
half WA = w * A;
waveOut.normal = half3(-(D.xy * WA * cosC), 1 - (Q * WA * sinC));
waveOut.normal = waveOut.normal/waveCount;
return waveOut;
}
Wave GenWave(float3 vertex) {
half2 pos = vertex.xz;
Wave waveOut;
uint count = 4;
for (uint i = 0; i < count; i++) {
Wave wave = GerstnerWave(pos, count, _WaveLength, _WaveAmplitude, _WindDirection, _WindSpeed);
waveOut.vertex += wave.vertex;
waveOut.normal += wave.normal;
}
return waveOut;
}
v2f vert (appdata v)
{
v2f o;
Wave wave = GenWave(v.vertex.xyz);
v.vertex.xyz += wave.vertex;//叠加偏移
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = mul(wave.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//漫反射公式
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 worldNormal = normalize(i.normal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal,worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, _Color.a);
}
ENDCG
}
}
}
UE水体渲染
- 渲染-生成网格体
- 水面按照四叉树网格生成grid
- 每次重启引擎或者需要重建时重新生成
- 每次更新所有水体耗
- 时非常短
- 不同的WaterBody生成的grid不合并
- 命令行 r.Water.WaterMesh.ShowTileBounds 可以看到不同颜色的方格
- 每种颜色对应不同的WaterBodyType
- 河流可以与海洋和湖泊交汇,交汇处生成的grid不同
- Tile
- 每个Tile只需要渲染自己的WaterBodyType的材质
- Culling是基于Tile
- 剔除后的tile,同材质同LOD的可以instancing
- 命令行 stat watermesh 可以查看面数,生成的grid数和drawcall
- LOD
- 变形逻辑是基于到摄像机的距离
- 近处高细节
- 远处简化
- 遍历四叉树时使用相同的距离
- morph完成后,将精确切换 tiled LOD
- 平滑的过度
- 渲染-着色
- Material:一个主材质
- ShaderingModel:水体、大气、其他胶状物体有点类似,有散射,在次表面中比较常见。
- SingleLayerWater:,可见光有些波长优先被吸收掉了(红色),蓝光走的远
- Scattering&AbsorptionCoeffs:消光指数,光在水中如何传播。
- PhaseG:各向异性散射系数
- ColorScaleBehindWater:调整水后面的cene color,更好表现焦散效果。 * 独立的管线
- 降采样 scene color/depth
- 渲染SingleLayerWater物体到GBuffer
- Tile SingleLayerWater MaterialID
- 为SingleLayerWater的像素计算SSR
- composite reflection captures, sky & SSR
-
UnderWater PP
-
模拟-浮力:Buodancy Component
- 模拟-流体Fluid
- Ripple Solver:No Velocity,相对cheap
- Shallow Solver:不稳定,有速度。
目前还是实验版本,不是很稳定和完善。