- 网格顶点没有很密集,不涉及tessellation函数,对移动端友好。在网上找到的案例大多都是在standard surface shader或者pbr shader的基础上着色 (比如Harry Alisavakis的Water Shader),对移动端不友好,而且pbr有点黑盒子,想做自定义修改比较不方便。于是我用的是简单的高光公式来上色,主要是用来模拟液体的反光,似乎够用
- 顶点动画 用了3个Gerstner波,参考自教程catlike coding Waves
- 简单地根据顶点高度上色,模仿次表面散射
- 根据视角和法线夹角做简单的高光计算
- 用“湍流”噪声对法线扰动,使表面光斑更碎
Shader "Test/Ocean" {
Properties {
[Header(Color)]
_SurfaceColor ("PeakColor", Color) = (1,1,1,1)
_WaterColor ("ValleyColor", Color) = (1,1,1,1)
_AlphaSmooth ("AlphaSmooth", vector) = (0, 1, 1, 1)
_HeightSmooth ("HeightSmooth", vector) = (-1, 1, 1, 0)
[Header(Specular)]
_Glossy ("Glossy", float) = 0.35
_SpecPower("Specularity Power", Range(0,1)) = 1
_SunColor ("SunColor", Color) = (1,1,0.901,1)
_SunDir ("SunDirection", Vector) = (0, 0, 0, 0)
[Header(Wave)]
_WaveA ("Wave A (dir, steepness, wavelength)", Vector) = (1,0,0.5,10)
_WaveB ("Wave B", Vector) = (0,1,0.25,20)
_WaveC ("Wave C", Vector) = (1,1,0.15,10)
_Speed("Speed", float) = 1
[Header(Bump)]
_Bump ("Bump (RGB)", 2D) = "bump" {}
_BumpScale ("BumpScale", Range(0.01,8)) = 0.3
_TurbulenceTex ("Turbulence Tex", 2D) = "white" {}
_DistortPower ("Distort Power", float) = 0
_PowerX ("Power X", range (0,1)) = 0
_PowerY ("Power Y", range (0,1)) = 0
}
SubShader {
Tags { "RenderType" = "Opaque" "Queue"="Geometry"}
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ TRANSPARENT
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
half4 grabPos : TEXCOORD2;
float3 worldPos : TEXCOORD3;
float height : TEXCOORD4;
float2 uvnoise : TEXCOORD5;
};
half4 _SunDir;
float4 _WaveA;
float4 _WaveB;
float4 _WaveC;
float _Speed;
float4 _HeightSmooth;
fixed _PowerX;
fixed _PowerY;
fixed _DistortPower;
sampler2D _TurbulenceTex;
half4 _TurbulenceTex_ST;
float3 GerstnerWave (float4 wave, float3 p, inout float3 tangent, inout float3 binormal) {
float steepness = wave.z;
float wavelength = wave.w;
float k = 2 * 3.141593653 / wavelength;
float c = _Speed;
float2 d = normalize(wave.xy);
float f = k * (dot(d, p.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))
);
}
v2f vert (appdata_full v) {
v2f o;
float3 gridPoint = v.vertex.xyz;
float3 tangent = float3(1, 0, 0);
float3 binormal = float3(0, 0, 1);
float3 p = gridPoint;
p += GerstnerWave(_WaveA, gridPoint, tangent, binormal);
p += GerstnerWave(_WaveB, gridPoint, tangent, binormal);
p += GerstnerWave(_WaveC, gridPoint, tangent, binormal);
float3 normal = normalize(cross(binormal, tangent));
o.height = p.y - v.vertex.y;
v.vertex.xyz = p;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(normal);
o.uv = v.texcoord.xy;
o.uvnoise = TRANSFORM_TEX(v.texcoord.xy, _TurbulenceTex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
sampler2D _Bump;
half4 _SurfaceColor;
half4 _WaterColor;
half _BumpScale;
half _SpecPower;
half4 _SunColor;
float _Glossy;
half4 frag (v2f i) : COLOR {
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
float3 lightDir = normalize(_SunDir);
float3 normal = i.normal;
float2 originUV = i.uv;
fixed4 offsetColor1 = tex2D(_TurbulenceTex, i.uvnoise + fmod(_Time.xz*_DistortPower,1));
fixed4 offsetColor2 = tex2D(_TurbulenceTex, i.uvnoise + fmod(_Time.yx*_DistortPower,1));
i.uv.x += ((offsetColor1.r + offsetColor2.r) - 1) * _PowerX;
i.uv.y += ((offsetColor1.r + offsetColor2.r) - 1) * _PowerY;
normal += tex2D(_Bump, i.uv) * _BumpScale;
half4 col = lerp(_WaterColor, _SurfaceColor, smoothstep(_HeightSmooth.x, _HeightSmooth.y, i.height) * _HeightSmooth.z);
float3 halfdir = normalize(lightDir + viewDir);
float ndh = max(0, dot(normal, halfdir));
col.rgb += _SunColor.rgb * pow(ndh, _SpecPower*128.0) * _Glossy;
return col;
}
ENDCG
}
}
}


