水波效果指在计算机图形学中模拟水面波纹的视觉效果,通常用于游戏、动画或者其他虚拟场景中。主要用于体现水体的动态感,比如水的波动、反射、折射、透明等,可以让人感觉像真实的水一样流动闪耀。
核心特点就是:
- 动态波纹
- 光学特性(反射、折射、菲涅耳效应)
- 透明度
1、基本原理
水波效果可以基于【带法线纹理的玻璃效果】进行修改,通过添加噪声法线纹理结合Shader内置时间变量实现水波动态效果,加入菲涅耳计算公式实现水面的光学特性
关键点:
- 噪声纹理的使用
可以利用沃利噪声(细胞噪声)生成的噪声纹理灰度图,在Unity中将该噪声纹理灰度图作为高度图使用,用它代表水面的法线信息,只需要在Unity中将该灰度图设置为Normal map,并勾选Create from Grayscale后应用即可
- 动态效果的实现
自定义两个属性,代表水平面x和y轴的速度。在片元着色器中利用Shader内置时间参数 _Time.y 得到累积速度变化。然后用该速度变量从噪声法线纹理中进行两次采样,再讲两次采样的结果相加得到扰动后的法线,最后用该法线处理折射、反射、菲涅耳效果,这样看起来就会有动态效果了
该算法是图形学前辈们总结的高效的模拟流动感的算法,水波、火焰、玻璃折射都可以用
- 菲涅耳公式的运用
2、实现
Shader "ShaderProj/21/WaterWave"
{
Properties
{
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_Cube("Cubemap", Cube) = ""{}
//控制折射扭曲程度的变量
_Distortion("Distortion", Range(0,10)) = 0
// 水波水平和数值速度偏移的属性
_WaveXSpeed("WaveXSpeed", Range(-0.1, 0.1)) = 0.01
_WaveYSpeed("WaveYSpeed", Range(-0.1, 0.1)) = 0.01
_FresnelScale("FresnelScale", Range(0,1)) = 1
}
SubShader
{
Tags{"RenderType"="Opaque" "Queue"="Transparent"}
GrabPass{}
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cube;
sampler2D _GrabTexture;
float _Distortion;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _FresnelScale;
struct v2f
{
float4 pos:SV_POSITION;
float4 grabPos:TEXCOORD0;
float4 uv:TEXCOORD1;
float4 TtoW0:TEXCOORD3;
float4 TtoW1:TEXCOORD4;
float4 TtoW2:TEXCOORD5;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.grabPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
//计算反射光向量
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
fixed3 bump1 = UnpackNormal(tex2D(_BumpMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_BumpMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
float3 worldNormal = float3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump));
fixed4 mainTex = tex2D(_MainTex, i.uv + speed);
float3 refl = reflect(-viewDir, worldNormal);
fixed4 reflColor = texCUBE(_Cube, refl) * mainTex;
//折射相关的颜色
float2 offset = bump.xy * _Distortion;
i.grabPos.xy = offset*i.grabPos.z + i.grabPos.xy;
fixed2 screenUV = i.grabPos.xy / i.grabPos.w;
fixed4 grabColor = tex2D(_GrabTexture, screenUV);
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(normalize(viewDir), normalize(worldNormal)), 5);
float4 color = lerp(reflColor, grabColor, 1 - fresnel);
return color;
}
ENDCG
}
}
}