Adding Reflections and Highlights to Procedural Water in GLSL
Think about standing beside a lake on a sunny day.
The water is not one solid color.
Bright highlights appear where sunlight hits the waves.
Some parts look dark.
Others reflect the sky.
These differences are what give water its depth.
Fortunately, we can create a similar effect with just a few mathematical functions.
Why Does Water Shine?
A perfectly flat surface reflects light evenly.
Water is never perfectly flat.
Every ripple points in a slightly different direction.
Some ripples catch more light than others.
Those bright areas are called highlights.
By creating brighter values on the tops of our waves, the water instantly looks more realistic.
Starting with the Water Pattern
We'll begin with the water value from the previous article.
float water =
sin(
uv.x * 12.0 +
n * 4.0 +
uTime * 2.0
);
This value describes the height of our waves.
Creating Bright Areas
The tops of the waves should receive more light.
One simple way is to increase the contrast.
float highlight =
pow(
water,
8.0
);
The pow() function compresses most of the values while making the brightest parts stand out.
The larger the exponent, the sharper the highlight becomes.
Soft Highlights
Using a smaller exponent creates softer reflections.
pow(
water,
3.0
);
This produces gentle sunlight on calm water.
Experimenting with this number dramatically changes the appearance.
Mixing the Colors
Create two shades of blue.
vec3 deep = vec3(0.05,0.20,0.55);
vec3 shallow = vec3(0.30,0.75,1.0);
Blend between them.
vec3 color =
mix(
deep,
shallow,
water
);
Already the water begins to show depth.
Adding the Highlight
Now simply add the bright reflection.
color +=
highlight * 0.5;
Suddenly the water appears glossy.
Even a small amount makes a noticeable difference.
Complete Shader
#ifdef GL_ES
precision mediump float;
#endif
uniform float uTime;
varying vec2 vUv;
void main(){
vec2 uv = vUv;
float n = fbm(uv * 3.0);
float water =
sin(
uv.x * 12.0 +
n * 4.0 +
uTime * 2.0
);
water = water * 0.5 + 0.5;
float highlight =
pow(
water,
8.0
);
vec3 color = mix(
vec3(0.05,0.20,0.55),
vec3(0.30,0.75,1.0),
water
);
color += highlight * 0.5;
gl_FragColor = vec4(color,1.0);
}
The shader is only slightly longer than before, but the result feels much more alive.
Simulating Sky Reflection
Real water reflects the sky.
Blend a lighter blue near the top of the screen.
color = mix(
color,
vec3(0.70,0.85,1.0),
vUv.y * 0.3
);
The upper portion of the water now appears brighter.
Creating Calm Water
Reduce the wave frequency.
uv.x * 6.0
The highlights become broad and smooth.
Perfect for lakes.
Creating Rough Water
Increase the wave frequency.
uv.x * 24.0
Many small highlights appear.
The surface begins to resemble an ocean on a windy day.
Making the Highlights Stronger
Increase the brightness.
highlight * 0.8
The water sparkles much more.
Reducing this value creates softer reflections.
Where Are These Techniques Used?
These simple ideas appear in many water shaders.
Oceans.
Lakes.
Rivers.
Swimming pools.
Fountains.
Underwater scenes.
Motion graphics.
User interface backgrounds.
Video games.
Animated artwork.
Professional water shaders build on these same principles using more advanced lighting.
Try These Experiments
Create a calm lake.
Create rough ocean waves.
Increase the highlight strength.
Reduce the wave size.
Try different shades of blue.
Add a second highlight layer.
Observe how each adjustment changes the appearance.
A Small Challenge
Can you create these effects?
A peaceful lake at sunrise.
A bright tropical ocean.
Moonlight reflecting on water.
A magical glowing pool.
A stormy sea.
Each one starts with the same wave pattern.
Leave Adding Reflections and Highlights to Procedural Water in GLSL to:
Read more #coding posts
Best Posts From hey2d
We have not curated any of hey2d's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.
More Posts From hey2d
- Fragment Shaders Made Simple: What gl-FragColor Does
- Understanding gl-Position: My First Real Vertex Shader
- Creating Procedural Galaxies and Nebulas in GLSL
- Creating a Procedural Night Sky and Stars in GLSL
- Creating Procedural Clouds in GLSL
- Adding Reflections and Highlights to Procedural Water in GLSL
- Creating Procedural Water in GLSL
- Creating Procedural Smoke in GLSL
- Creating Procedural Fire in GLSL
- Creating Procedural Wood Grain in GLSL
- Creating a Procedural Marble Texture in GLSL
- Domain Warping in GLSL: Distorting Noise to Create Organic Worlds
- Fractal Brownian Motion in GLSL: Building Rich Procedural Noise
- Understanding Gradient Noise in GLSL
- Creating Smooth Value Noise in GLSL
- Creating Random Values in GLSL: The Building Blocks of Procedural Graphics
- Combining Translation, Rotation, and Scaling in GLSL
- Scaling Shapes in GLSL: Making Procedural Graphics Grow and Shrink
- Rotating Shapes in GLSL: Understanding Coordinate Rotation
- Moving Shapes in GLSL: Understanding Coordinate Translation