Unity只在一个面片上实现真实水体渲染
上面是效果图,题目指的一个面片是指只有两个三角面组成的面片,只有四个顶点。勘误:
我发现水面的高光效果在安卓手机上失去了效果,具体原因如下:
halfDirection 得用下面的Unity_SafeNormalize方法,而不是normalize,
否则在手机上高光会失去效果,修改如下:
float3 Unity_SafeNormalize(float3 inVec)
{
float dp3 = max(0.001f, dot(inVec, inVec));
return inVec * rsqrt(dp3);
}然后
float3 halfDirection = Unity_SafeNormalize(viewDirection+ lightDirection);概述
因为前一版卡通水体制作人觉得效果一般,所以换成了《海岛纪元》这种风格的水体。
这次我使用截帧工具,这次使用RenderDoc来截帧,效果还不错。
本来打算复刻一版的,后来发现他们貌似不是用unity做的。代码风格有点不一样。
其实是因为我看不懂他们的代码。。下面我会分析实现的理论和讲述具体实现。最后给出demo下载。
具体实现
通过截帧工具可以看到:
生成浪花的贴图是由lightmap和一个Rays贴图来渲染的。
而且浪花是单独一个mesh,这波操作其实我是看不懂的。原本以为通过shader在一个面片上就可以实现类似效果。
他们浪花的渲染效果大概如下:
再看一下他们水的效果:
1、使用了深度值来区分水深颜色
2、使用了法线贴图数据扰动来做水波纹效果
3、冲向岸边的浪花基本是实心的白色线条
4、反射和折射,反射用了环境贴图(并非是cubemap)
5、有高光,为什么他们的高光这么柔和。
它们所使用的贴图如下:
大概知道原理,就可以做了。
首先采样法线贴图扰动来做波纹效果:
//采样法线贴图的uv坐标,和时间相加
o.bumpUv1.xy = v.uv + float2(_SinTime.x * _WaveSpeed.x, _SinTime.x * _WaveSpeed.y);
o.bumpUv1.zw = v.uv + float2(_CosTime.y * 1.2 * _WaveSpeed.z, _SinTime.y*0.5* _WaveSpeed.w);
//法线贴图
half4 bump10 = (tex2D(_DumpTex, v.bumpUv1.xy / _NormalsScale) * 2) + (tex2D(_DumpTex, v.bumpUv1.zw / _NormalsScale) * 2) - 2;
half3 oriOffset = UnpackNormal(bump10);
oriOffset.xy = oriOffset.xy * _NormalsStrength;
bump = normalize(oriOffset);
half2 offset = oriOffset.xy;
//采样折射贴图(地面) float4 refraCol = tex2D(_MainTexture, v.uv.xy + offset );
注意上面使用了不同的uv坐标采样两次法线贴图的数据,并各自乘以2后相加的结果减去2。
这样的目的是为了使得波纹有交叉扰动的感觉。
接着采样深度贴图,做出水浅,水深过渡的效果。
//深度贴图 float zdepth = tex2D(_DepthTexture, v.oriuv).r;
//把深度强的地方增强,弱的地方变化不大,这步可做可不做,具体看效果zdepth = pow(zdepth, 1.3);
float depthDifference = zdepth * _FoamDeP; float waterDepthDifference01 = saturate(depthDifference);
waterDepthDifference01 = pow(waterDepthDifference01, 0.8);
//根据深度值插值颜色
float4 lightColor = lerp(float4(1, 1, 1, 1),_DepthGradientShallow , smoothstep(0,0.5, waterDepthDifference01));
float4 waterColor = lerp(lightColor, _DepthGradientDeep, waterDepthDifference01); float alpha = smoothstep(0, 0.1, zdepth);
再接着做实线的浪花
//浪花贴图float4 FoamCol = tex2D(_FoamTexture, v.foamv);
//根据深度值来显示透明度float FoamAlpha = (FoamCol.r + (FoamCol.g * cSecondWeight));
FoamCol.a = FoamAlpha;
half waveFaInner = _waveFaMax - _Time.y * _WaveSpeed.x * _waveFaSpeed % (_waveFaMax);
waveFaInner = clamp(waveFaInner, _waveFaMin, _waveFaMax);
//是否显示浪花half borderFlag = step(zdepth.r, waveFaInner) * step(waveFaInner - _line, zdepth.r);
half4 border1 = FoamCol.a * borderFlag * _specularColor * (alpha);
然后就可以得出水体的颜色了:
float4 finalCol = float4(refraCol.rgb * waterColor.rgb, alpha);
half4 cc =finalCol+ border1;
最后加上高光,
刚开始高光是想用blin-phong做的,效果不太好,感觉是一片片的生硬的色块,不仅有锯齿,还没过渡的感觉。
而海岛纪元的高光真的过渡很柔和,很真实。难道是PBR的高光渲染?
试了一下,果然是真的。
所以这里我用到了PBR的高光,也就是BRDF高光公式
//高光,PBR的高光
float roughness = 1.0 - _Smoothness;
roughness = max(roughness, 0.002);
float roughness2 = roughness * roughness;
//d项
half dCol = BRDF_DTerm(NdotH, roughness2);
////G项
half gTerm = BRDF_GTerm(NdotL, NdotV, roughness2);
////F项 菲涅尔
half3 frenCol = BRDF_FresnelTerm(_specularColor.rgb, LdotH);
float specularPBL = dCol * gTerm * UNITY_PI * frenCol;
//不会为负数
specularPBL = max(0, specularPBL * NdotL);
//any 参数里的任意一个元素不为零
specularPBL *= any(_specularColor.rgb) ? 1.0 : 0.0;
//加上高光,得到最终的颜色
float4 outCol = cc + float4(specularPBL * mainLight.color, 1) * alpha;
outCol.r = clamp(outCol.r, 0, 1);
outCol.g = clamp(outCol.g, 0, 1);
outCol.b = clamp(outCol.b, 0, 1);
return outCol;
高光的感觉自然多了!
demo下载,记得在URP环境下使用:
链接:https://pan.baidu.com/s/11D5koAJSypTpaw_7G-F7Fg
提取码:tuf1
页:
[1]