前言
上一节为水面上色,本节梳理水面的波浪生成。共分为法线和顶点两部分
- 涉及的知识点
抓屏
GerstnerWave算法
1 法线
由于该案例光照不参与计算,因此法线只扭曲水下的内容(抓屏实现)。这里采样两次法线,动画速度和tile都不同,以模拟波浪运动的无规律性。
1.1 获取切线和副法线
o.tanW = normalize(TransformObjectToWorldDir(v.tangent.xyz));
o.biW = normalize(cross(v.normal.xyz, v.tangent.xyz)
* v.tangent.w * unity_WorldTransformParams.w);
1.2 法线采样和混合
- 法线混合函数
_blendNormal
BlendedNormal = normalize( float3( A.xy + B.xy, A.z*B.z ).
- 切线空间的法线
本节只使用切线空间的法线信息。
float3 _getNormalTS(float2 uv)
{
uv = uv / _NormalScale;
float speed = _Time.y * _NormalSpeed * 0.1;
real4 normalData1 = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, uv+speed);
float3 normalT1 = UnpackNormalScale(normalData1, _NormalStr);
real4 normalData2 = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap,
uv*0.2 + speed * (-0.35));
float3 normalT2 = UnpackNormalScale(normalData2, _NormalStr);
return _blendNormal(normalT1, normalT2);
}
1.3 抓屏
Grabpass在内置管线中的用法如下
GrabPass {"_GrabTex"}
urp没有直接的抓屏功能,所以本小节的目的是实现类似grabpass的功能,有几种方式可以实现,这里只列举最方便的方法,其他方法和细节可参看本人的urp语雀笔记。
回到管线设置,勾选Opaque Texture
可以看到这个生成的_CameraOpaqueTexture
在半透明渲染之前,也就是说此数据不会存在半透明物体,这部分需要注意
现在使用_CameraOpaqueTexture
来实现抓屏
float2 uvSS = screenPos.xy;
real4 grab_col = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, uvSS);
采样坐标是经过齐次除法的屏幕空间坐标,以便能正确映射。
而后我们利用之前的切线空间的法线做扭曲
uvSS += 0.01 * normalTS * _UWDistort;
1.4 与水表面颜色混合
水底扭曲只在水面mesh透明的部分,然后再添加岸边泡沫,因此代码如下
// =========Mix Results=========== //
base_col = lerp(grab_col, base_col, base_col.a);
base_col = lerp(base_col, base_col+sinWave, sinWave.a);
2 顶点动画
使用细分过的plane做顶点动画
GerstnerWave
这里创建GerstnerWave函数,调用三次
自定义的变量waveA,waveB,waveC为Vector数据类型,分量代表:
SpeedXY,Steepness,wavelength
float3 GerstnerWave( float3 position, inout float3 tangent, inout float3 binormal, float4 wave )
{
float steepness = wave.z * 0.01;
float wavelength = wave.w;
float k = 2 * 3.14159 / wavelength;
float c = sqrt(9.8 / k);
float2 d = normalize(wave.xy);
float f = k * (dot(d, position.xz) - c * _Time.y);
float a = steepness / k;
tangent += float3(
-d.x * d.x * (steepness * sin(f)),
d.x * (steepness * cos(f)),
-d.x * d.y * (steepness * sin(f))
);
binormal += float3(
-d.x * d.y * (steepness * sin(f)),
d.y * (steepness * cos(f)),
-d.y * d.y * (steepness * sin(f))
);
return float3(
d.x * (a * cos(f)),
a * sin(f),
d.y * (a * cos(f))
);
}
水平面使用默认的世界切线和法线,我们利用 GerstnerWave
函数修改模型空间的顶点和法线
void _getVertexData(inout a2v v)
{
float3 tangent = float3( 1,0,0 );
float3 binormal = float3( 0,0,1 );
float3 posW = TransformObjectToWorld(v.vertex.xyz);
float3 wave1 = GerstnerWave(posW, tangent,binormal, _WaveA);
float3 wave2 = GerstnerWave(posW, tangent,binormal, _WaveB);
float3 wave3 = GerstnerWave(posW, tangent,binormal, _WaveC);
posW = posW + wave1 + wave2 + wave3;
v.vertex.xyz = TransformWorldToObject(posW.xyz);
v.vertex.w = 1;
float3 normalW = normalize(cross(binormal, tangent));
v.normal = mul( unity_WorldToObject, float4(normalW, 0 ) ).xyz;
}
最后的调整结果