Absorption Based Stylized Water
UPDATED!
An advanced water shader that samples the screen texture and absorbs defined color values depending on depth. Comes with simple player interactions and refraction. Reflections are possible through Godot’s own post-processing methods, of which I highly recommend reflection probes. Now comes with Screen Space Reflections with interpolated step distance for higher performance/quality! Other Godot internal reflection methods are still desirable to cover off screen reflections.
Caustics are now refracted and depend on the coordinates of the underlying mesh.
Set up
- Create a new mesh instance with a plane.
- Add a ShaderMaterial with this shader.
- Go to Project Settings > Globals > Shader Globals and add wind_intensity(float), wind_direction(vec3) and player_position(vec3).
- Add suitable noise textures in the shader parameters.
- Add to your player movement script a way to adjust the player_position unifrom through the RenderingServer with the global position.
Fresnel, Displacement, Player Interaction, Screen Space Reflection and Caustic (not in the screenshots) effects can be toggled by commenting out the respective #define effects to customize the look in a performance friendly way.
Parameters explanation
- Absorption color – Color that is absorbed from the screen texture
- Fresnel radius – angle at which the Fresnel color is blendet in
- Fresnel color – Color used at high angles
- Roughness – Very low for reflections
- Specular – PBR Specular
- Depth Distance – Considered to be the maximum depth
- Beers Law – How quickly the color absorption ramps up
- Displacement Strength – Wave bump height
- Displacement Scroll Speed – Speed at which the displacement texture is moved across the world
- Displacement Scroll Offset – directional offset by which a copy of the displacement texture is shifted
- Displacement Scale Offset – Scaling factor for the copied texture
- Displacement Scale – base scale of the displacement texture
- Displacement texture – Simplex, Perlin or Cellular
- Edge Thickness – Scale of the edge where neighboring meshes overlap
- Edge speed – speed at which the edge noise texture is scrolled
- Edge noise scale – Size of the edge noise texture
- Edge noise – Cellular noise
- Edge Ramp – Very steep gradient1D texture (0.0 > white, 0.08 > black)
- Influence Size – Size of the waves caused by the player
- Player Wave Frequency – Amount of waves caused by the player
- Player Wave Speed – Speed of the wave animation
- Caustics Size – Scale of the caustic noise
- Caustic Range – Range at which caustics are no longer visible
- Caustic Strength – Strength of the caustic effect
- SSR Mix Strength – Alpha of the captured reflections on the water (effected by roughness)
- SSR Travel – The maximum distance a reflection ray takes
- SSR Resolution Near – Distance between SSR samples close to the surface
- SSR Resolution Far – Distance between SSR samples at the maximum travel distance (interpolated linearly)
- SSR Tolerance – Percentage of Screen Depth Tolerance relative to the current SSR resolution
- Refraction Strength – Amount of which the Normal Map offset the screen sample
- Normal Map Strength – Amount of which the Normal Map effects the lighting pass
- Scroll Speed – Speed at which the normal texture is moved across the world
- Scroll Offset – directional offset by which a copy of the normal texture is moved
- Scale Offset – Scaling factor for the copied texture
- Normal Map Scale – base scale of the normal texture
- Normal Map – Cellular
Shader code
shader_type spatial;
render_mode shadows_disabled;
#define CAUSTICS
#define FRESNEL
#define PLAYER_WAVES
#define DISPLACEMENT
#define SSR
group_uniforms color;
uniform vec3 absorption_color : source_color = vec3(1.0, 0.35, 0.0);
#ifdef FRESNEL
uniform float fresnel_radius : hint_range(0.0, 6.0, 0.01) = 2.0;
uniform vec3 fresnel_color : source_color = vec3(0.0, 0.57, 0.72);
#endif
uniform float roughness : hint_range(0.0, 1.0, 0.01) = 0.15;
uniform float specular : hint_range(0.0, 1.0, 0.01) = 0.25;
// Depth adjustment
uniform float depth_distance : hint_range(0.0, 50.0, 0.1) = 25.0;
uniform float beers_law : hint_range(0.0, 20.0, 0.1) = 4.5;
#ifdef DISPLACEMENT
group_uniforms displacement;
uniform float displacement_strength : hint_range(0.0, 5.0, 0.1) = 0.3;
uniform float displacement_scroll_speed : hint_range(0.0, 1.0, 0.001) = 0.1;
uniform vec2 displacement_scroll_offset = vec2 (-0.2, 0.3);
uniform float displacement_scale_offset = 0.5;
uniform vec2 displacement_scale = vec2(0.04);
uniform sampler2D displacement_texture : hint_default_black, repeat_enable;
#endif
group_uniforms edge;
uniform float edge_thickness : hint_range(0.0, 1.0, 0.001) = 0.3;
uniform float edge_speed : hint_range(0.0, 1.0, 0.001) = 0.35;
uniform vec2 edge_noise_scale = vec2(0.4);
uniform sampler2D edge_noise : repeat_enable;
uniform sampler2D edge_ramp : repeat_disable;
#ifdef PLAYER_WAVES
group_uniforms player;
uniform float influence_size : hint_range(0.0, 4.0, 0.1) = 1.0;
uniform float player_wave_frequenzy : hint_range(0.0, 20.0, 0.1) = 10.0;
uniform float player_wave_speed : hint_range(0.0, 10.0, 0.1) = 5.0;
#endif
#ifdef CAUSTICS
group_uniforms caustics;
uniform float caustic_size : hint_range(0.0, 8.0, 0.01) = 2.0;
uniform float caustic_range : hint_range(0.0, 256.0, 0.1) = 40.0;
uniform float caustic_strength : hint_range(0.0, 1.0, 0.01) = 0.08;
#endif
#ifdef SSR
group_uniforms screen_space_reflections;
uniform float ssr_mix_strength : hint_range(0.0, 1.0, 0.01) = 0.65;
uniform float ssr_travel : hint_range(0.0, 300.0, 0.5) = 100.0;
uniform float ssr_resolution_near : hint_range(0.1, 10.0, 0.1) = 1.0;
uniform float ssr_resolution_far : hint_range(2.0, 20.0, 0.1) = 5.0;
uniform float ssr_tolerance : hint_range(0.0, 2.0, 0.01) = 1.0;
#endif
group_uniforms normal_map;
uniform float refraction_strength : hint_range(0.0, 4.0, 0.01) = 1.25;
uniform float normal_map_strength : hint_range(0.0, 4.0, 0.01) = 1.0;
uniform float scroll_speed : hint_range(0.0, 1.0, 0.01) = 0.3;
uniform vec2 scroll_offset = vec2(0.1, -0.3);
uniform float scale_offset = 0.5;
uniform vec2 normal_map_scale = vec2(0.1);
uniform sampler2D normal_map : hint_normal, filter_linear_mipmap;
// Hidden Uniforms
global uniform float wind_intensity; // Global shader parameter between 0.0 and 1.0
global uniform vec3 wind_direction;
#ifdef PLAYER_WAVES
global uniform vec3 player_position;
#endif
uniform sampler2D screen_texture: hint_screen_texture, filter_linear_mipmap, repeat_disable;
uniform sampler2D depth_texture: hint_depth_texture, filter_linear_mipmap, repeat_disable;
varying vec3 global_position;
#ifdef CAUSTICS
// Permutation polynomial hash credit Stefan Gustavson
vec4 permute(vec4 t) {
return t * (t * 34.0 + 133.0);
}
// Gradient set is a normalized expanded rhombic dodecahedron
vec3 grad(float hash) {
// Random vertex of a cube, +/- 1 each
vec3 cube = mod(floor(hash / vec3(1.0, 2.0, 4.0)), 2.0) * 2.0 - 1.0;
// Random edge of the three edges connected to that vertex
// Also a cuboctahedral vertex
// And corresponds to the face of its dual, the rhombic dodecahedron
vec3 cuboct = cube;
cuboct[int(hash / 16.0)] = 0.0;
// In a funky way, pick one of the four points on the rhombic face
float type = mod(floor(hash / 8.0), 2.0);
vec3 rhomb = (1.0 - type) * cube + type * (cuboct + cross(cube, cuboct));
// Expand it so that the new edges are the same length
// as the existing ones
vec3 grad = fma(cuboct, vec3(1.22474487139), rhomb);
// To make all gradients the same length, we only need to shorten the
// second type of vector. We also put in the whole noise scale constant.
// The compiler should reduce it into the existing floats. I think.
grad *= fma(-0.042942436724648037, type, 1.0) * 3.5946317686139184;
return grad;
}
// BCC lattice split up into 2 cube lattices
vec4 os2NoiseWithDerivativesPart(vec3 X) {
vec3 b = floor(X);
vec4 i4 = vec4(X - b, 2.5);
// Pick between each pair of oppposite corners in the cube.
vec3 v1 = b + floor(dot(i4, vec4(.25)));
vec3 v2 = b + vec3(1, 0, 0) + vec3(-1, 1, 1) * floor(dot(i4, vec4(-.25, .25, .25, .35)));
vec3 v3 = b + vec3(0, 1, 0) + vec3(1, -1, 1) * floor(dot(i4, vec4(.25, -.25, .25, .35)));
vec3 v4 = b + vec3(0, 0, 1) + vec3(1, 1, -1) * floor(dot(i4, vec4(.25, .25, -.25, .35)));
// Gradient hashes for the four vertices in this half-lattice.
vec4 hashes = permute(mod(vec4(v1.x, v2.x, v3.x, v4.x), 289.0));
hashes = permute(mod(hashes + vec4(v1.y, v2.y, v3.y, v4.y), 289.0));
hashes = mod(permute(mod(hashes + vec4(v1.z, v2.z, v3.z, v4.z), 289.0)), 48.0);
// Gradient extrapolations & kernel function
vec3 d1 = X - v1; vec3 d2 = X - v2; vec3 d3 = X - v3; vec3 d4 = X - v4;
vec4 a = max(0.75 - vec4(dot(d1, d1), dot(d2, d2), dot(d3, d3), dot(d4, d4)), 0.0);
vec4 aa = a * a; vec4 aaaa = aa * aa;
vec3 g1 = grad(hashes.x); vec3 g2 = grad(hashes.y);
vec3 g3 = grad(hashes.z); vec3 g4 = grad(hashes.w);
vec4 extrapolations = vec4(dot(d1, g1), dot(d2, g2), dot(d3, g3), dot(d4, g4));
// Derivatives of the noise
vec4 derivative = -8.0 * mat4(vec4(d1,0.), vec4(d2,0.), vec4(d3,0.), vec4(d4,0.)) * (aa * a * extrapolations)
+ mat4(vec4(g1, 0.), vec4(g2, 0.), vec4(g3, 0.), vec4(g4, 0.)) * aaaa;
// Return it all as a vec4
return vec4(derivative.xyz, dot(aaaa, extrapolations));
}
// Rotates domain, but preserve shape. Hides grid better in cardinal slices.
// Good for texturing 3D objects with lots of flat parts along cardinal planes.
vec4 os2NoiseWithDerivatives_Fallback(vec3 X) {
X = dot(X, vec3(2.0/3.0)) - X;
vec4 result = os2NoiseWithDerivativesPart(X) + os2NoiseWithDerivativesPart(X + 144.5);
return vec4(dot(result.xyz, vec3(2.0/3.0)) - result.xyz, result.w);
}
#endif
#ifdef FRESNEL
float fresnel(vec3 normal, vec3 view) {
return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0 )), fresnel_radius);
}
#endif
vec2 refract_uv(inout vec2 uv, vec3 normal, float depth){
float strength1 = refraction_strength * depth;
uv += fma(strength1, length(normal), strength1 * -1.2);
return uv;
}
#ifdef SSR
vec2 get_uv_from_view_position(vec3 position_view_space, mat4 proj_m)
{
vec4 position_clip_space = proj_m * vec4(position_view_space.xyz, 1.0);
vec2 position_ndc = position_clip_space.xy / position_clip_space.w;
return position_ndc.xy * 0.5 + 0.5;
}
vec3 get_view_position_from_uv(vec2 uv, float depth, mat4 inv_proj_m)
{
vec4 position_ndc = vec4((uv * 2.0) - 1.0, depth, 1.0);
vec4 view_position = inv_proj_m * position_ndc;
return view_position.xyz /= view_position.w;
}
#endif
bool in_bounds(vec2 uv) {
vec2 fruv = abs(floor(uv));
return fruv.x + fruv.y < 0.1;
}
void vertex() {
global_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
#ifdef DISPLACEMENT
float time = TIME * displacement_scroll_speed * fma(wind_intensity, 0.7, 0.3);
float displace1 = texture(displacement_texture, fma(global_position.xz, displacement_scale, time * -wind_direction.xz)).r;
float displace2 = texture(displacement_texture, fma(global_position.xz, displacement_scale * displacement_scale_offset, time * (-wind_direction.xz + displacement_scroll_offset))).r;
float displacement_mixed = mix(displace1, displace2, 0.4);
float offset = fma(displacement_mixed, 2.0, -1.0) * displacement_strength;
VERTEX.y += offset;
global_position.y += offset;
#endif
}
void fragment() {
vec3 opposing_color = vec3(1.0) - absorption_color.rgb;
vec3 normalized_wind_direction = normalize(wind_direction);
float wind_intens_factor = fma(wind_intensity, 0.7, 0.3);
#ifdef FRESNEL
float fresnel_value = fresnel(NORMAL, VIEW);
#endif
float time_factor = TIME * scroll_speed * wind_intens_factor;
vec3 n1 = textureLod(normal_map, fma(global_position.xz, normal_map_scale, time_factor * -normalized_wind_direction.xz), 2.0).xyz;
vec3 n2 = textureLod(normal_map, fma(global_position.xz, normal_map_scale * scale_offset, time_factor * 0.8 * (-normalized_wind_direction.xz + scroll_offset)), 2.0).xyz;
NORMAL_MAP = mix(n1, n2, 0.5);
NORMAL_MAP_DEPTH = normal_map_strength;
float depth_tex = texture(depth_texture, SCREEN_UV).r;
vec3 ndc = vec3(fma(SCREEN_UV, vec2(2.0), vec2(-1.0)), depth_tex);
vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
world.y /= world.w;
float vertey_y = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).y;
float relative_depth = vertey_y - world.y;
// Create Edge caused by other Objects
float edge_blend = clamp(relative_depth / -edge_thickness + 1.0, 0.0, 1.0);
vec2 edge_noise_uv = global_position.xz * edge_noise_scale * fma(normalized_wind_direction.xz, vec2(0.5), vec2(0.5));
edge_noise_uv = fma(-normalized_wind_direction.xz * TIME * edge_speed, vec2(wind_intens_factor), edge_noise_uv);
float edge_noise_sample = texture(edge_noise, edge_noise_uv).r;
float edge_mask = normalize( texture(edge_ramp, vec2(edge_noise_sample * fma(edge_blend, -1., 1.))).r);
// Create Ripples caused by player
float player_effect_mask = 0.0;
#ifdef PLAYER_WAVES
vec3 player_relative = vec3(global_position - player_position);
float player_height = smoothstep(1.0, 0.0, abs(player_relative.y));
float player_position_factor = smoothstep(influence_size, 0.0, length(player_relative.xz));
float player_waves = pow( fma( sin(fma(player_position_factor, player_wave_frequenzy, TIME * player_wave_speed)), 0.5, 0.5), 6.0);
float wave_distort = texture( edge_ramp, vec2( player_waves * (edge_noise_sample + 0.2) * player_position_factor * player_height)).x;
player_effect_mask = clamp(normalize( fma(wave_distort, -1.0, 0.4)), 0.0, 1.0);
#endif
// combine Edge Mask with Player Ripples
float ripple_mask = clamp( fma( edge_mask, edge_blend, player_effect_mask), 0.0, 1.0);
// Calculate Fragment Depth
vec4 clip_pos = PROJECTION_MATRIX * vec4(VERTEX, 1.0);
clip_pos.xyz /= clip_pos.w;
DEPTH = clip_pos.z;
// Refract UV
vec2 refracted_uv = SCREEN_UV;
refract_uv(refracted_uv, NORMAL_MAP, sqrt(DEPTH) * relative_depth);
vec3 screen;
float depth_blend;
float refracted_depth_tex = texture(depth_texture, refracted_uv).x;
ndc = vec3(fma(refracted_uv, vec2(2.0), vec2(-1.0)), refracted_depth_tex);
world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
world.xyz /= world.w;
float depth_test = vertey_y - world.y;
// Caustic Effects
#ifdef CAUSTICS
float range_mod = clamp((VERTEX.z + caustic_range) * 0.05, 0.0, 1.0);
float caustic_value = 0.0;
// Protect yourself from calculating Noise at runtime with this handy if statement!
if (range_mod > 0.0) {
vec3 X = vec3(world.xz * caustic_size, mod(TIME, 578.0) * 0.8660254037844386);
vec4 noiseResult = os2NoiseWithDerivatives_Fallback(X);
noiseResult = os2NoiseWithDerivatives_Fallback(X - noiseResult.xyz / 16.0);
caustic_value = fma(noiseResult.w, 0.5, 0.5) * range_mod * range_mod;
}
#endif
/*
Sometimes the Water Refraction would cause the sampling of a screen position that is either
outside the screen bounds or where another object is infront of the water.
Switching back to the unrefracted SCREEN_UV fixes that.
*/
if (depth_test > -0.0001 && in_bounds(refracted_uv)) {
screen = texture(screen_texture, refracted_uv).rgb * 0.9;
depth_blend = clamp(depth_test / depth_distance, 0.0, 1.0);
depth_blend = fma(exp(-depth_blend * beers_law), -1.0, 1.0);
} else {
screen = texture(screen_texture, SCREEN_UV).rgb * 0.9;
depth_blend = clamp(relative_depth / depth_distance, 0.0, 1.0);
depth_blend = fma(exp(-depth_blend * beers_law), -1.0, 1.0);
}
#ifdef SSR
vec3 view_normal_map = mat3(VIEW_MATRIX) * (vec3(NORMAL_MAP.x, 0.0, NORMAL_MAP.y) * 2.0 - 1.0);
vec3 combined_normal = normalize(view_normal_map * (NORMAL_MAP_DEPTH * 0.15) + NORMAL);
vec3 reflacted_path = reflect(-VIEW, combined_normal);
vec2 current_screen_pos = vec2(0.0);
vec3 current_view_pos = VERTEX;
vec3 sampled_color = vec3(-1.0);
float current_stepD = 0.0;
float current_depth = 0.0;
float alpha_hit = 0.0;
for(float i = 0.01; i < ssr_travel; i++) {
current_stepD = mix(ssr_resolution_near, ssr_resolution_far,float(i) / float(ssr_travel));
current_view_pos += reflacted_path * current_stepD;
current_screen_pos = get_uv_from_view_position(current_view_pos, PROJECTION_MATRIX);
if (!in_bounds(current_screen_pos)) {break;}
current_depth = get_view_position_from_uv(current_screen_pos, texture(depth_texture, current_screen_pos).x, INV_PROJECTION_MATRIX).z - current_view_pos.z;
if (current_depth > -0.0001 && current_depth <= ssr_tolerance * current_stepD) {
sampled_color = textureLod(screen_texture, current_screen_pos, 0.5).rgb;
vec2 ruv = 1.0 - abs(current_screen_pos * 2.0 - 1.0);
ruv = pow(ruv, vec2(0.5));
alpha_hit = clamp(min(ruv.x, ruv.y), 0.0, 1.0);
break;
}
i += current_stepD;
}
#endif
vec3 color = clamp(screen - absorption_color.rgb * depth_blend, vec3(0.0), vec3(1.0)); // Absorb Screen Color
color = mix(color, opposing_color, depth_blend*depth_blend); // Apply depth color
#ifdef FRESNEL
color = mix(color, fresnel_color, fresnel_value); // Apply fresnel color
#endif
#ifdef CAUSTICS
color = clamp(color + caustic_value * caustic_strength * (1.0 - depth_blend), vec3(0.0), vec3(1.0));
#endif
#ifdef SSR
color = mix(color, sampled_color, alpha_hit * (1.0 - roughness) * ssr_mix_strength);
#endif
color = mix(color, vec3(0.98), ripple_mask); // Apply Ripples
ALBEDO = color;
ROUGHNESS = roughness;
SPECULAR = specular;
}
Amazing work! Thank you so much for sharing this! The best looking water shader for sure.
I’ve got problem getting player waves to work though. I’ve made shader global parameter of player_position and updating the position with my player’s position, and there do appear bubbles around the player in circular manner, but it doesn’t look as good as in your pic (it was like several bubbles only appearing very shortly, and doesn’t really look like waves).
Do you have any idea what might be causing that? Should I be offsetting the player_position’s height somehow? (I’m using tall humanoid model)
First off all, I appreciate that you like my Shader ^w^
You’re idea is going in the right direction. The area of these waves is influenced by the relative height from the fragment world height and the player_position so they don’t show up when your player is actually above the water.
You could offset the player_position uniform to the bottom of the characters collisions box like I did in my picture, or extend the height range at which the waves are shown by adding a number to player_relative.y in line 228:
By default, this Range is 2 Units high covering 1 Unit above and below. In this modified code this range is extended to 4 Units, but in this solution not the entire range is interpolated, but this could be perfect for tall characters.
This is incredible work and something I’ve been seeking for weeks! Big thanks for sharing this beautiful shader.
how do i make my godot not crash when i try to used this
Could you add more information such as error messages, your Godot version and the rendering method used (Compatibility Renderer is not supported due to the use of fma). I would appreciate if you open an issue on the GitHub page.
Hi first of all Ive to say this is a great shader!!! I love the colors and control so amazing work!!
I am just having a little issue that probably has to do with my lack of experience in Godot. The water looks very still in my scene specially the edges where it connects to other 3D objects. Is the speed of the waves or edges controlled somewhere outside the shader like the wind direction global setting?? I love how it looks on the preview you used and wondered how to get a similar effect. If you need screenshots or something from me please let me know and again thanks for sharing and offering support.
If you mean the vertex displaced waves, you probably need to subdivide the mesh you used with the water shader and adjust the displacement uniforms.
If you mean textures in general, you probably need to modify the global uniform wind_intensity and set it to 1.0. Normally it’s used to adjust the global wind speed at runtime.
Hi Malido! Your water shader is amazing! Even accounts for scaling with triplaning based on global position. Wonderful stuff 🙂 thank you for sharing, I’ll credit you in my project.
Also, I am making a HD2D game and noticed it doesn’t cull 3D sprites properly when putting them behind the water. Any workaround for this or ideas on what is causing it? I might just convert all my animated sprites to planes with a viewport so they are properly 3D.
Example: https://imgur.com/a/BNbn2XD
Thanks again!
The water will not cull anything by itself because it uses the screen texture and is therefore transparent. To fix transparency sorting issues you have to put the Sprite3D’s alpha_cut to Hash or discard so they are rendered before the water. If you want to hide your sprites completely when they are submerged, you can always use an occluder instance.
Hello, this is some great work but I am having trouble with some pixels bleeding possibly from depth texture when there is MSAA anti aliasing enabled. Also there’s some weird ghosting effect going around the edges or objects that are rendered in front of the water. Do you possibly know any fix for that? Please see attached gif.
https://imgur.com/a/6TZV5FH
The artifacts come from the non-aliased depth texture. I’d recommend using screen-space anti-aliasing instead.
I really enjoy this shader – it’s one of the best I’ve seen! I’ve made some slight modifications to better fit my project, and I’ve given you a mention in my last devlog: https://www.indiedb.com/games/theia/news/devlog-4-diving-into-water-effects-and-physics Thank you for your incredible work!
Amazing Stuff! I didn’t know my water could look that realistic.
I would really like to see how you implemented screen-space reflections. I’m currently using reflection probes, but to get good looking reflections you need quite a bit of VRAM.
Amazing work. It works like a charm (the edge part is a bit difficult to tweak tho).
I’ve just modified the water absorption part and the LOD selection (based on depth instead of the screen texture lod).
Here is an example.
The caustic works well too ! I’m just struggling at making it fade properly so it doen’t appear in deep water (only in shallow ones).
Many thanks for this shader !
There was no caustic reduction based on depth lmao… Thx for pointing out. I just added it in by multiplying with the depth value:
And I think your edge ramp gradient texture needs to be steeper, with a very small range of white close to 0. (0.0: white, 0.08: black)
Many thanks for this 🙂 Now that i’m looking at your solution, it seems so obvious !
I have an error in this line of the shader: global uniform float wind_intensity;
You have to create a global shader variable of the same name like explained here.
Or you just remove the global keyword…
Hi, very very cool shader. Thanks for sharing.
If someone else is struggling with the waves effect on the edges, you need to add “edge noise” noise texture and add an “edge ramp” gradient1D and set wind_direction and wind_intensity to not 0.
Do you know what is causing the light change on the pillars of the bridge int the water and how I can fix it? https://i.imgur.com/KYka0Ns.mp4
Hello, I have managed to get everything working except noise affecting the edge foam. I am positive everything is correct but using anything under noise and color ramp does not visually affect the edge. What could I be doing wrong? Godot 4.3
Have you tried using the Material from the GitHub Repo?
Ok I just tried that aswell, can confirm the issue is still present.
Then it may be the issue that the globals wind_direction and wind_intensity may not be 0. The 0 in the direction causes and issue with the texture sampling. Essentially everything is sampling the same pixel from the noise texture.
If you rework the shader to use a global offset instead of direction * TIME, like I did, this issue als resolves itself.
Hello teacher, how to solve the problem that the pixelated orthographic camera cannot display the water surface
Love the shader! Fantastic work!
I’m getting an aura of non-refraction around the player and terrain a good distance away from the water. Is this the SCREEN_UV bug mentioned in the code comment, or something else?
https://imgur.com/a/nUtUENk
Hello, this shader is very beautiful.
It works normally, but I cannot reproduce the wave effect as shown in the picture you presented.
How should I set it to have a wave effect?
In my project, it is calm without any waves
Looking forward to your reply, thank you.
my godot version:4.4.1
I downloaded the project on GitHub, but there are still no white waves
Heya! Lovely shader, sadly am struggling with the foam. I get a ripple to appear around 0,0,0 – I assume this is the player position, since that is what I have it set to. But the ripple is only there, at that one spot – I don’t see the foam near the edges of the plane meshes. I have the global shader params set and they are non-zero. I have used both the material and shader from the repo, as well as copied over the code from this page and adapted it. I added noise textures and color ramps wherever possible, fiddled with noise and colors. I can’t seem to get the foam to show up.
Maybe somebody can share a material with the correctly values on here, or somewhere on Github? Or someone has some idea on what could be the issue? I would love to use this shader in my game! 🙂
If the foam is not showing up I suggest inverting the gradient for the foam, for me when I was messing around with it, I saw the default gradient godot gives is not the correct direction, the white should be on the left not the right. and you can also mess with how close the black is along with adding another white or black part to the gradient. I am just not sure about the player ripples.
Hey amazing shader! I’ve been searching for days trying to find a shader that works in godot 4.x and I came across yours, I am just wondering because I am not very good at shaders. How do I make an underwater shader, do I use the ‘next pass’ option in the material editor? and if I do create an underwater shader, is there a way to move the caustics over to the underwater meshes or to I just have to recreate the caustics in the new shader? Thanks!
I was not seeing the edges and i found out that if the MSAA is set to 8x the edges do not show, so if you are having this issue turn that off or set to 4x and it should work 🙂