|
最近看了《UnityShader入门精要》,在顶点动画章节中,有关于河流模拟,对书中样例做了扩充,利用sin波与Gerstner波对水面进行模拟。这里原理与实现都是参考网上有关文章和书籍,在理解原理基础上进行了shader编写,是一篇学习的记录。
一、Sin波
利用正弦函数来表示水面波形。顶点着色器中,构造出在Y方向上变化,Y方向的位置变化可以根据x,z平面上的任意方向。这里首先根据X方向控制平面的起伏变化。
c为整个正弦波左右移动的速度,k和波长有关 。在进行求解漫反射光照和高光反射时,还需要用到顶点法线信息,由于平面随时间呈正弦波形式的变化,这里求解其切线和法线方向。
值的说明的是,由于Y只随X方向起伏变化,Tx=1,Tz=0,法线方向 通过 为0得到。
float3 SinWave(float3 pos,inout float3 tangent,inout float3 normal) {
float waveLen =5;
float k = 2 * UNITY_PI / waveLen;
float waveA = 1;
float waveSpeed = 1;
float f = k * (pos.x - _Time.y * waveSpeed);
pos.y = waveA * sin(f);
tangent = normalize(float3(1, k * waveA * cos(f), 0));
normal = float3(-tangent.y, tangent.x, 0);
return float3(pos.x, pos.y, pos.z);
}
二、Gerster波
个人理解是Gerster波可以在波峰处有更好的聚集性,在波谷处又能很好的分散开,参考文章里通过给定了顶点位置关系:
也就是相当于表面每个水分子在x,y方向上都可以组成一个圆,每个水分子x上都加上一个A为直径的圆的坐标。其切线与法线分别为,
float3 Gerstners1(float3 pos,inout float3 tangent, inout float3 normal) {
float k = 2 * UNITY_PI / 1;
float waveA = 0.08;
float f1 = k * (pos.x - 1 * _Time.y);
tangent = normalize(float3(
1-k*waveA*sin(f1),
k*waveA*cos(f1),
0
));
normal = float3(-tangent.y, tangent.x, 0);
return float3(waveA*cos(f1), waveA *sin(f1),0);
}
单个Gerster波的效果
多个Gersterner波叠加的情况,与单个波不同的是法线的计算,
其中 代表了当前波的方向,多个类似的波可以进行叠加。
float3 Gerstners2(float4 wave,float3 pos,inout float3 tangent, inout float3 binormal) {
float2 d = normalize(wave.xy);
float wavelength = wave.w;
float stepness = wave.z;
float k = 2 * UNITY_PI / wavelength;
float wavespeed = sqrt(9.8 / k);
float waveA = stepness / k;
float f = k * (dot(d, pos.xz) - wavespeed * _Time.y);
tangent += float3(-d.x * d.x * (stepness * sin(f)), d.x * stepness * cos(f), -d.x * d.y * (stepness * sin(f)));
binormal += float3(-d.x * d.y * (stepness * sin(f)), d.y * (stepness * cos(f)), -d.y * d.y * (stepness * sin(f)));
return float3(d.x * waveA * cos(f), waveA * sin(f), d.y * waveA * cos(f));
}
两次Gertner叠加
三、水体折射
为了水体更显真实感,这里在水体中做透明和折射处理。根据《UnityShader入门精要》中10.1.4中介绍,我们在片元着色器中,将上述求得的法线转换到切线空间内,并对屏幕坐标进行扰动,进而形成一种折射效果。具体操作为:
"Queue" = "Transparent" "RenderType" = "Opaque"
GrabPass{"_RefractionTex"}其中“Queue”作为Transparent保证在渲染改pass之前其他非透明物体已经被渲染,这样可以保证透过水体看见水下物体。GrabPass定义了一个抓取屏幕的pass。“_Refraction”表示存入哪个纹理中。
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 scrPos : TEXCOORD1;
float3 worldNormal:TEXCOORD2;
float3 worldPos:TEXCOORD3;
float3 worldViewDir : TEXCOORD4;
float3 modelTangent : TEXCOORD5;
float3 modelBinormal : TEXCOORD6;
float3 modelNormal : TEXCOORD7;
};
//模型空间到切线空间转换矩阵
float3x3 rotation = float3x3(i.modelTangent.xyz, i.modelBinormal.xyz, i.modelNormal.xyz);
//切线空间法向量
fixed3 bump = mul(rotation, i.modelNormal).xyz;
//折射偏移
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//屏幕坐标偏移
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
//对渲染纹理_RefractionTex采样
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb;看下加了折射效果的水面效果:
带有折射效果
这里去掉了高光反射,这里与书中例子不同的是,这里的法线使我们手动计算得到。书中通过法线纹理进行采样得到。水面反射效果通过立方体纹理添加后,出现大片白色光斑,还有待进一步查找问题原因,对于菲涅尔效果来说,反射效果是必要的。同时折射计算时,是在切线空间下对屏幕坐标进行“扰动”,需要进一步理解,这里也是先挖个坑,后续专门说明。
参考文章:
https://catlikecoding.com/unity/tutorials/flow/waves/
nanayon:水体渲染之Gerstner波形理解与推导
栗野:Unity顶点动画与Gerstner Wave海面模拟 |
|