- 网格顶点没有很密集,不涉及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 } } }