在GPU Gems1的第一小节中,可以看到通过Gerstner wave function来模拟海洋的波浪效果,同时在Unity自带的Water(Pro)效果中,FX/Water4中的部分水波效果正是通过该公式来模拟的。
首先来看看FX-Water4.shader的代码。
Tags {"RenderType"="Transparent" "Queue"="Transparent"} Lod 500 ColorMask RGB GrabPass { "_RefractionTex" } Pass { #pragma vertex vert #pragma fragment frag } } Subshader { Tags {"RenderType"="Transparent" "Queue"="Transparent"} Lod 300 ColorMask RGB Pass { #pragma vertex vert300 #pragma fragment frag300 } } Subshader { Tags {"RenderType"="Transparent" "Queue"="Transparent"} Lod 200 ColorMask RGB Pass { #pragma vertex vert200 #pragma fragment frag200 } } Fallback "Transparent/Diffuse"
其中包含三个subshader,具体执行哪一个Subshader,是通过waterBase.cs来设置sharedMaterial.shader.maximumLOD设置的。我们从low quality开始分析,顶点着色器的代码如下。
v2f_simple vert200(appdata_full v) { v2f_simple o; //计算出顶点的世界坐标 half3 worldSpaceVertex = mul(_Object2World, v.vertex).xyz; half2 tileableUv = worldSpaceVertex.xz; //通过设置的 _BumpDirection.xy值来模拟第一个方向,zw值模拟另一个方向, _BumpTiling.xy模拟第一个方向的速率,zw模拟第二个方向的变化速率。 o.bumpCoords.xyzw = (tileableUv.xyxy + _Time.xxxx * _BumpDirection.xyzw) * _BumpTiling.xyzw; //viewInterpolator是用来计算相机到该顶点的方向。 o.viewInterpolator.xyz = worldSpaceVertex-_WorldSpaceCameraPos; //pos暂时无用。 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.viewInterpolator.w = 1;//GetDistanceFadeout(ComputeScreenPos(o.pos).w, DISTANCE_SCALE); return o; }
在片元着色器中,我们可以看到法线的计算是通过PerPixelNormal函数实现的,在WaterInclude.cginc中可以找到该函数。
inline half3 PerPixelNormal(sampler2D bumpMap, half4 coords, half3 vertexNormal, half bumpStrength) { //计算normal map中两个不同方向的值。 half4 bump = tex2D(bumpMap, coords.xy) + tex2D(bumpMap, coords.zw); //计算出法线方向 bump.xy = bump.wy - half2(1.0, 1.0); //通过bumpStrength来增大x,z相对于Y的比例,即增加纹理的凹凸感,在unity 中可以看到,bumpStrength越大,水面反光效果越明显。 half3 worldNormal = vertexNormal + bump.xxy * bumpStrength * half3(1,0,1); //归一化计算。 return normalize(worldNormal); }
得到法线方向后,具体的片元着色器代码如下。
half4 frag200( v2f_simple i ) : SV_Target { //法线计算 half3 worldNormal = PerPixelNormal(_BumpMap, i.bumpCoords, half3(0,1,0), PER_PIXEL_DISPLACE); //归一化视角向量 half3 viewVector = normalize(i.viewInterpolator.xyz); //暂时无用 half3 reflectVector = normalize(reflect(viewVector, worldNormal)); //半角向量计算,减少计算光线与法线之间的计算量(Jim Blinn),与binnPhong高光模型计算一致 half3 h = normalize ((_WorldLightDir.xyz) + viewVector.xyz); float nh = max (0, dot (worldNormal, -h)); float spec = max(0.0,pow (nh, _Shininess)); //通过Fresnel参数控制缩放 worldNormal.xz *= _FresnelScale; //计算出fresnel反射率。 half refl2Refr = Fresnel(viewVector, worldNormal, FRESNEL_BIAS, FRESNEL_POWER); half4 baseColor = _BaseColor; //通过fresnel反射率混合折射与反射颜色。 baseColor = lerp(baseColor, _ReflectionColor, saturate(refl2Refr * 2.0)); //fresnel反射率越大,alpha越大,水下物品越看不清。 baseColor.a = saturate(2.0 * refl2Refr + 0.5); //与高光混合,最终颜色计算。 baseColor.rgb += spec * _SpecularColor.rgb; return baseColor; }
核心是fresnel反射率的计算,fresnel的基本原理是视线与法线的角度越小,即与平面越垂直,反射越小,折射越大,水下的物品越清晰,视线与平面越平行,反射越大,水下物品越看不清。在WaterINclude.cginc中有简易的fresnel的计算公式。
inline half Fresnel(half3 viewVector, half3 worldNormal, half bias, half power) { //夹角的计算。 half facing = clamp(1.0-max(dot(-viewVector, worldNormal), 0.0), 0.0,1.0); //反射率的计算。 half refl2Refr = saturate(bias+(1.0-bias) * pow(facing,power)); return refl2Refr; }
这一节主要是通过UV纹理的变化来模拟简易水波,但无法在相同的normal map下实现大波浪似的效果。下一节主要介绍Gerstner wave function如何更实现高级的水波效果。
时间: 2024-10-03 13:46:00