Water Shader in GodotEngine 3
Introduction
I've started my journey through the wonderful world of shaders around month ago. It's still all new to me and I'm learning. But over the days I manage to put few things together and make an awesome water shader that you all asking about. Thats why I decided to make and in-depth analysis of this one shader.
Lets start with some background. I generates terrain using height map.
It is a special texture that keeps information about height of each vertex of the terrain. White pixel is the maximum height. Black is the base of terrain. Take a look a the pyramid and river shapes.
Now the fun part. The shader.
Here are the whole shader you can look at. I start with a bunch of variables. To easily tune-up the final look and make different styles.
shader_type spatial;
uniform vec2 amplitude = vec2(1.0, 1.0);
uniform vec2 frequency = vec2(.2, .2);
uniform vec2 time_factor = vec2(2.0, 2.0);
uniform vec3 water_color = vec3(0.25, 0.27, 0.15);
uniform float water_height = 2.5;
uniform float water_clearnes = 0.4;
uniform float water_refraction = 0.014;
uniform float water_alpha = 0.7;
uniform float water_shore = 0.36;
uniform float water_color_contrast = 6.0;
We need height map sprite to known where is the land. And to find shore line.
uniform sampler2D height_map;
Here comes the first function. Height.
float height(vec2 pos, float time, float noise){
return (amplitude.x * sin(pos.x * frequency.x * noise + time * time_factor.x)) + (amplitude.y * sin(pos.y * frequency.y * noise + time * time_factor.y));
}
It takes position and time + noise. Then it calculates height using sin and random value for natural look. To make the water "move", time variable is added to the calculations.
How to get noise?
float fake_random(vec2 p){
return fract(sin(dot(p.xy, vec2(12.9898,78.233))) * 43758.5453);
}
vec2 faker(vec2 p){
return vec2(fake_random(p), fake_random(p*124.32));
}
I was using texture with random noise but then I found this awesome function. One generates random float and the second vec2.
Vertex Shader
Now combine all of this. For each vertex of the water plain I change the height.
Thats how it looks with just a water height (flat).
VERTEX.y = water_height
And now with noise.
float noise = faker(VERTEX.xz).x;
VERTEX.y = water_height + height(VERTEX.xz, TIME, noise);
Water height is added for complex levels where some water will have different height (think waterfall).
Then there is this code. To be honest I don't really understand it but it's for generating normals. We will need this for refraction.
TANGENT = normalize( vec3(0.0, height(VERTEX.xz + vec2(0.0, 0.2), TIME, noise) - height(VERTEX.xz + vec2(0.0, -0.2), TIME, noise), 0.4));
BINORMAL = normalize( vec3(0.4, height(VERTEX.xz + vec2(0.2, 0.0), TIME, noise) - height(VERTEX.xz + vec2(-0.2, 0.0), TIME, noise), 0.0));
NORMAL = cross(TANGENT, BINORMAL);
Now that the water moves we can add a little bit of color to it.
Fragment Shader
Here's where the magic happens.
vec2 uv2 = UV * -1.0;
float height = texture(height_map, uv2.xy).r;
The way I make the terrain and particles my heightmap needs to be mirrored. Then I read height value from heightmap texture.
Using height I can calculate smooth gradient from the shore to the deepest places.
float gfx = smoothstep(0.15, water_shore, height);
vec3 w_color = vec3(gfx, gfx, gfx) * water_color_contrast;
New color is brighter when closer to the shore. To make it visible like in my example I added high contrast value.
Then comes all the parameters of the material.
ALBEDO = w_color;
ROUGHNESS = gfx;
METALLIC = 0.8;
SPECULAR = gfx;
ALPHA = 1.0 - clamp(gfx, water_alpha, 1.0);
And last we have refraction effect. I copied the code form the YouTube tutorial where the guy says he's copied it form yet another one. So it's a shared snippet.
vec3 ref_normal = normalize( mix(VERTEX,TANGENT * NORMALMAP.x + BINORMAL * NORMALMAP.y + VERTEX * NORMALMAP.z, NORMALMAP_DEPTH) );
vec2 ref_ofs = SCREEN_UV + ref_normal.xy * water_refraction;
EMISSION += textureLod(SCREEN_TEXTURE, ref_ofs, ROUGHNESS * water_clearnes).rgb * (1.0 - ALPHA);
ALBEDO *= ALPHA;
ALPHA = 1.0;
It takes all the light that comes and calculates refracted position. Then resets alpha as water is already fully rendered and needs to overlap real ground. The good part of it is that it just works :)
That's the final result:
I hope I could help and inspire you to make your own water shader.
Stuff
All the sources are available at the GitHub. Also the whole tech demo project.
I was using code from those sources:
- Godot Game Engine - Basic Water (Spatial-) Material by DerDieDasMedia
- Godot 3D shader tutorial : Water in 3D by Bastiaan Olij
Leave your comments and suggestions at /r/godot/ post. Thanks!
Tags: graphics, shaders, gamedev, godot-engine
My current game development stack
For years I was using MacBooks as my primary computers for everything. But as I gain experience in Linux I started to use more open/free software. Here are my current setup both in hardware and software. It’s definitely not final and I still perfecting it.
Hardware
When I’m outdoor I mostly use only the laptop. But when I’m home or doing some 48h hackathon I use all of those:
- Dell 7204 Rugged Extreme (i5/8/256)
- Dell P1914S (4:3, 1280x1024)
- Vortex Poker III (keyboard with Cherry MX Browns)
- Kensington Orbit (trackball)
The best part is that Dell 7204 have touch screen that can be flipped and the whole laptop can transform into (rather bulky) tablet. For pixel artist this is a dream machine. I no longer need to bringing my Wacom tablet anymore! Also touch in linux is now supported very well so I use my fingers a lot in day-to-day situations too.
I really like the 4:3 monitors. They’re small on desk but have lot of working space. They have the best medium to edit A4 documents. Or any code. Or photos. They are just perfect.
For any coder good keyboard is a main accessory. Mechanical keyboards are what I really need to do lot of writing. I choose Pok3r because it’s 60% keyboard - small but have all the buttons I really use.
Lot of peoples ask me about trackball - why I use it, is it better, etc… What I can say? I really don’t know. I never use any so I just bought one to test if it’s something for me or not. Turns out I fall in love from day one :) But to this day I can not say why. I just like to use it.
Software
This is the place where I constantly find something new. The last thing that I relay on that’s not free nor open is Adobe Photoshop.
Base
Standard linux setup. I use Debian-based distros for some time but when I discovered Arch I never get back. For windows management I love XFCE as it's fast and essential. But now, as I have touchscreen the only enviroment that supports it is GNOME. And GNOME is really fine. But slow...
- Linux (Arch/Antergos)
- GNOME (best for touch monitors)
Development
Nothing spectacular in this area. I use vim/nano when doing console based stuff. But to do actual code there is nothing better than Sublime!
- Godot Engine
- SublimeText 3
- Git (Terminal/GitKraken)
- gFTP
Graphics
I have Adobe CC licence for my MacBooks (Photoshop&Lightroom) but on Linux there is no alternative. Happily CS2 works in Wine good enough so I can now do all my work on Linux machine.
- Adobe Photoshop CS2 @Wine (main tool)
- Aseprite (pixel art)
- Scribus (print stuff)
- FontForge (fonts)
- Blender (modeling/rendering)