Advanced shaders · lesson 3 of 19 · Advanced · about 5 min
Ocean 3: sky, Fresnel and sun glitter
You will build: Give the sea a real sky with a sun, mix the water and the reflected sky with a Fresnel factor, and add the sun glitter.
Real water has almost no color of its own. What you see is mostly the sky reflected in it, and how much depends on the angle you look at it. This is the Fresnel effect: look straight down and you see through the water to its dark depths; look at it at a grazing angle and it is a mirror.
The standard cheap formula: fresnel = 0.02 + 0.98 * pow(1.0 - dot(n, -rd), 5.0). Then color = lerp(waterColor, reflectedSky, fresnel).
To get the reflection, bounce the ray off the wave normal with reflect(rd, n) and ask the sky function what it sees in that direction. (Waves are not perfect mirrors; if the bounce points slightly below the horizon we flip it up with abs.)
The sun's reflection is a tiny, extremely bright highlight: pow(max(dot(r, sun), 0.0), 400.0). Because the wave normals change from pixel to pixel it breaks up into the glitter you know from photos of the sea.
Finally, far-away waves are too small to see, so we calm the normals with distance: lerp(n, up, smoothstep(12.0, 60.0, t)).
float3 r = reflect(rd, n); r.y = abs(r.y); float fres = 0.02 + 0.98 * pow(1.0 - saturate(dot(n, -rd)), 5.0); col = lerp(water, sky(r), fres); col += float3(1.0, 0.9, 0.7) * pow(max(dot(r, sun), 0.0), 400.0) * 2.0;
The numbers you need
Everything the picture depends on is given, so the challenge is the code, not guessing.
- sky(rd)
lerp(float3(0.75, 0.82, 0.9), float3(0.2, 0.42, 0.78), pow(saturate(rd.y), 0.45)), plus float3(1.0, 0.75, 0.45) * pow(s, 24.0) * 0.5 and float3(1.0, 0.9, 0.7) * smoothstep(0.9995, 0.9999, s), where s = max(dot(rd, sun), 0.0)- Calmer far away
n = normalize(lerp(n, float3(0.0, 1.0, 0.0), smoothstep(12.0, 60.0, t)))- Reflection
r = reflect(rd, n); r.y = abs(r.y)- Fresnel
0.02 + 0.98 * pow(1.0 - saturate(dot(n, -rd)), 5.0)- Water color
lerp(float3(0.02, 0.1, 0.18), float3(0.05, 0.3, 0.33), saturate(h * 2.0 + 0.5)), h = waveH(pos.xz)- Final color
lerp(water, sky(r), fresnel) + float3(1.0, 0.9, 0.7) * pow(max(dot(r, sun), 0.0), 400.0) * 2.0- Sky pixels
sky(rd)
Try it in your own language
The lesson is written once and shown in every language. Pick yours: Unity (Built-in), Unity (URP), Unreal, Shadertoy / WebGL, OpenGL (LWJGL, raylib), three.js, GameMaker, LÖVE (Lua), Godot, Apple (iOS, macOS), WebGPU / Bevy.
Start coding