Please help me understand/program buoyancy

Godot Version

4.6

Question

Abstract: I’m trying to program a buoyancy system for RigidBody objects that uses the actual vertex height of the water as set by a shader instead of just the objects world postion. Programming wizzards please save me.

Details: I’ve watched a few tutorials but fundamentally don’t understand shaders lol. From what I’ve found, I need to make a GDscript version of the shader’s height code, any help doing so would be greatly appreciated.

Resources:

One buoyancy solution I found (I’d have used this project directly but I did’t like the water style) -

Current water shader –

shader_type spatial;

render_mode depth_draw_always;

const float EPSILON = 1e-5;

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_nearest;
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_nearest;

uniform vec3 color_shallow : source_color = vec3(0.01, 0.2, 0.3);
uniform vec3 color_deep : source_color = vec3(0.3, 0.5, 0.6);

uniform float transparency : hint_range(0.0, 1.0, 0.01) = 0.6;
uniform float metallic : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform float roughness : hint_range(0.0, 1.0, 0.01) = 0.25;

uniform float max_visible_depth : hint_range(0.1, 100.0, 0.1) = 20.0;

uniform sampler2D wave_a;
uniform sampler2D wave_b;
uniform vec2 wave_move_direction_a = vec2(-1.0, 0.0);
uniform vec2 wave_move_direction_b = vec2(0.0, 1.0);
uniform float wave_noise_scale_a = 15.0;
uniform float wave_noise_scale_b = 15.0;
uniform float wave_time_scale_a = 0.15;
uniform float wave_time_scale_b = 0.15;
uniform float wave_height_scale = 1.0;
uniform float wave_normal_flatness : hint_range(0.1, 100.0, 0.1) = 50.0;

uniform sampler2D surface_normals_a;
uniform sampler2D surface_normals_b;
uniform vec2 surface_normals_move_direction_a = vec2(-1.0, 0.2);
uniform vec2 surface_normals_move_direction_b = vec2(0.2, 1.0);
uniform float surface_texture_roughness : hint_range(0.0, 1.0, 0.01) = 0.15;
uniform float surface_texture_scale : hint_range(0.001, 2.0, 0.001) = 0.1;
uniform float surface_texture_time_scale : hint_range(0.001, 2.0, 0.001) = 0.06;

uniform float ssr_resolution   : hint_range(0.01, 10.0, 0.1)	= 1.0;
uniform float ssr_max_travel   : hint_range(0.0, 200.0, 0.1) 	= 30.0;
uniform float ssr_max_diff     : hint_range(0.1, 10.0, 0.1) 	= 4.0;
uniform float ssr_mix_strength : hint_range(0.0, 1.0, 0.01) 	= 0.7;
uniform float ssr_screen_border_fadeout: hint_range(0.0, 1.0, 0.1)	= 0.3;

uniform float refraction_intensity : hint_range(0.0, 1.0, 0.01) = 0.4;

uniform vec3  border_color : source_color = vec3(1.0);
uniform float border_scale : hint_range(0.0, 5.0, 0.01) = 2.0;
uniform float border_near = 0.5;
uniform float border_far = 300.0;

uniform float cut_out_x = 0.0;
uniform float cut_out_z = 0.0;

varying vec2 vertex_uv;
varying vec3 local_position;

float get_wave_height(vec2 uv_a, vec2 uv_b)
{
float height1 = texture(wave_a, uv_a).y;
float height2 = texture(wave_b, uv_b).y;
return (height1 + height2) / 2.0;
}

vec3 get_mixed_normals(vec3 color1, vec3 color2)
{
vec3 normal1 = normalize(color1 * 2.0 - 1.0);
vec3 normal2 = normalize(color2 * 2.0 - 1.0);
vec3 up = vec3(0.0, 0.0, 1.0);
vec3 tangent = normalize(normal1 + normal2);
vec3 binormal = normalize(cross(up, tangent));
vec3 mixedNormal = normalize(cross(tangent, binormal));
return mixedNormal * 0.5;
}

void vertex()
{
local_position = VERTEX;

vertex_uv = (MODEL_MATRIX * vec4(local_position, 1.0)).xz;
vec2 uv_a = vertex_uv / wave_noise_scale_a + (wave_move_direction_a * TIME * wave_time_scale_a);
vec2 uv_b = vertex_uv / wave_noise_scale_b + (wave_move_direction_b * TIME * wave_time_scale_b);
VERTEX.y += get_wave_height(uv_a, uv_b) * wave_height_scale;

float normal_height_scale = wave_height_scale / wave_normal_flatness;
vec2 e = vec2(0.01, 0.0);
vec3 normal = normalize(vec3(
	get_wave_height(uv_a - e, uv_b - e) * normal_height_scale - get_wave_height(uv_a + e, uv_a + e) * normal_height_scale, 
	2.0 * e.x, 
	get_wave_height(uv_a - e.yx, uv_b - e.yx) * normal_height_scale - get_wave_height(uv_a + e.yx, uv_b + e.yx) * normal_height_scale
	));
NORMAL = normal;

}

bool is_within_screen_boundaries(vec2 position)
{
return position.x > 0.0 && position.x < 1.0 && position.y > 0.0 && position.y < 1.0;
}

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;
}

bool is_zero(float value)
{
return abs(value) < EPSILON;
}

float get_screen_border_alpha(vec2 screen_position)
{
vec2 shifted_screen_position = 4.0 * screen_position * (1.0 - screen_position);
float mask = shifted_screen_position.x * shifted_screen_position.y; // ranging from 0.0 at the edges to 1.0 in the center

// An offset in the [0.0, 0.5] range for ssr_screen_border_fadeout values > 0.75
// which is subtracted from the result of smoothstep.
// This ensure alpha smoothly transitions to zero when ssr_screen_border_fadeout is approaching 1.0.	
float offset = mix(0.0, 0.5, (clamp(ssr_screen_border_fadeout, 0.75, 1.0)-0.75) / 0.25);
float alpha = clamp(smoothstep(0.0, 2.0 * ssr_screen_border_fadeout, mask) - offset, 0.0, 1.0);

return is_zero(ssr_screen_border_fadeout) ? 1.0 : alpha;

}

vec4 get_ssr_color(vec3 surface_view_position, vec3 normal_view_space, vec3 view_view_space, mat4 proj_m, mat4 inv_proj_m)
{
if (ssr_max_travel < EPSILON)
{
return vec4(0);
}

vec3 current_position_view_space = surface_view_position;
vec3 view_direction_view_space = view_view_space * -1.0;
vec3 reflect_vector_view_space = normalize(reflect(view_direction_view_space.xyz, normal_view_space.xyz));

vec2 current_screen_position = vec2(0.0);

vec3 resulting_color = vec3(-1.0);
for(float travel=0.0; resulting_color.x < 0.0 && travel < ssr_max_travel; travel = travel + ssr_resolution)
{
	current_position_view_space += reflect_vector_view_space * ssr_resolution;
	current_screen_position = get_uv_from_view_position(current_position_view_space, proj_m);

	float depth_texture_probe_raw = texture(DEPTH_TEXTURE, current_screen_position).x;
	vec3 depth_texture_probe_view_position = get_view_position_from_uv(current_screen_position, depth_texture_probe_raw, inv_proj_m);
	
	float depth_diff = depth_texture_probe_view_position.z - current_position_view_space.z;
	
	vec3 ssr_screen_color = texture(SCREEN_TEXTURE, current_screen_position.xy).rgb;
	resulting_color = (is_within_screen_boundaries(current_screen_position) && depth_diff >= 0.0 && depth_diff < ssr_max_diff) ? ssr_screen_color : vec3(-1.0);
}

float alpha = get_screen_border_alpha(current_screen_position);
return vec4(resulting_color,alpha);

}

float linear_depth(float cur_depth)
{
return border_far * border_near / (border_near + cur_depth * (border_far - border_near));
}

float normalize_float(float min_v, float max_v, float value)
{
float clamped_value = clamp(value, min_v, max_v);
return  (clamped_value - min_v) / (max_v - min_v);
}

vec2 get_refracted_uv(vec2 raw_screen_uv, float screen_depth_raw, vec3 view, vec3 normal, mat4 proj_m, mat4 inv_proj_m)
{
vec3 screen_view_position_original = get_view_position_from_uv(raw_screen_uv, screen_depth_raw, inv_proj_m);
float screen_center_distance = clamp(abs(length(raw_screen_uv - vec2(0.5, 0.5))) * 2.0, 0.0, 1.0);
float refraction_intensity_deglitched = mix(1.0 - refraction_intensity, 1.0, screen_center_distance);
vec3 refraction_position_view_space = screen_view_position_original + normalize(refract(view, -normal, refraction_intensity_deglitched));
vec2 refraction_uv = get_uv_from_view_position(refraction_position_view_space, proj_m);
return refraction_uv;
}

void fragment()
{
vec3 normal = NORMAL;

float screen_depth_raw = texture(DEPTH_TEXTURE, SCREEN_UV).x;

vec2 refraction_uv = refraction_intensity > 0.0 ? get_refracted_uv(SCREEN_UV, screen_depth_raw, VIEW, normal, PROJECTION_MATRIX, INV_PROJECTION_MATRIX) : SCREEN_UV;

float screen_depth = texture(DEPTH_TEXTURE, refraction_uv).x;
float surface_depth = FRAGCOORD.z;

float border_diff = linear_depth(screen_depth_raw) - linear_depth(surface_depth);

vec2 time_vector = (TIME * surface_normals_move_direction_a) * surface_texture_time_scale;
vec2 time_vector2 = (TIME * surface_normals_move_direction_b) * surface_texture_time_scale;

vec3 normal_texture_blend 	= get_mixed_normals(texture(surface_normals_a, vertex_uv * surface_texture_scale + time_vector).xyz, texture(surface_normals_b, vertex_uv * surface_texture_scale + time_vector2).xyz);
vec3 normal_blend 			= mix(normal, normal_texture_blend, surface_texture_roughness);

vec3 screen_view_position 	= get_view_position_from_uv(refraction_uv, screen_depth, INV_PROJECTION_MATRIX);
vec3 surface_view_position 	= get_view_position_from_uv(SCREEN_UV, surface_depth, INV_PROJECTION_MATRIX);
float depth_visibility 		= 1.0 - normalize_float(0.0, max_visible_depth, length(surface_view_position - screen_view_position));

vec3 screen_color 				= texture(SCREEN_TEXTURE, refraction_uv).rgb;
vec4 ssr_color 					= get_ssr_color(surface_view_position, normal, VIEW, PROJECTION_MATRIX, INV_PROJECTION_MATRIX);
vec3 surface_color_transparency	= mix(color_shallow, screen_color, transparency);
vec3 surface_depth_color_mix 	= mix(color_deep, surface_color_transparency, depth_visibility);
vec3 surface_color_ssr_mix		= (ssr_max_travel > EPSILON) ? mix(surface_depth_color_mix, ssr_color.rgb, ssr_mix_strength * ssr_color.a) : vec3(0);
vec3 water_color 				= (ssr_color.x >= 0.0) ? surface_color_ssr_mix : surface_depth_color_mix;
vec3 final_color				= mix(border_color, water_color, step(border_scale, border_diff));

float cut_out_x_half = cut_out_x / 2.0;
float cut_out_z_half = cut_out_z / 2.0;

if((local_position.x < cut_out_x_half && local_position.x > -cut_out_x_half) &&
(local_position.z < cut_out_z_half && local_position.z > -cut_out_z_half)) 
{
	discard;
}

ALBEDO.rgb = final_color;
METALLIC = metallic;
ROUGHNESS = roughness;
NORMAL = normal_blend;

}

Current Water code (What I wrote this with NO idea what’s going on) -

extends MeshInstance3D
class_name Water


var material: ShaderMaterial;

var noise_a: Image;
var noise_scale_a: float;
var wave_speed_a: float;

var noise_b: Image;
var noise_scale_b: float;
var wave_speed_b: float;

var height_scale: float;

var time: float;

# Called when the node enters the scene tree for the first time.
func _ready():
	material = mesh.surface_get_material(0);
	
	
	
	noise_a = material.get_shader_parameter("wave_a").noise.get_seamless_image(512, 512);
	noise_scale_a = material.get_shader_parameter("wave_noise_scale_a");
	wave_speed_a = material.get_shader_parameter("wave_time_scale_a");
	
	noise_b = material.get_shader_parameter("wave_b").noise.get_seamless_image(512, 512);
	noise_scale_b = material.get_shader_parameter("wave_noise_scale_b");
	wave_speed_b = material.get_shader_parameter("wave_time_scale_b");
	
	height_scale = material.get_shader_parameter("wave_height_scale");
	return;



# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	time += delta;
	material.set_shader_parameter("wave_time", time);
	return;

func getHeightAt(world_position: Vector3) -> float:

	
	## A
	var uv_xA = wrapf(world_position.x / noise_scale_a + time * wave_speed_a, 0, 1);
	var uv_yA = wrapf(world_position.z / noise_scale_a + time * wave_speed_a, 0, 1);
	
	var pixel_posA = Vector2(uv_xA * noise_a.get_width(), uv_yA * noise_a.get_height());
	var heightA: float =  + noise_a.get_pixelv(pixel_posA).r * height_scale;
	
	
	## B
	var uv_xB = wrapf(world_position.x / noise_scale_b+ time * wave_speed_a, 0, 1);
	var uv_yB = wrapf(world_position.z / noise_scale_b+ time * wave_speed_a, 0, 1);
	
	var pixel_posB = Vector2(uv_xB * noise_a.get_width(), uv_yB * noise_a.get_height());
	var heightB: float = noise_a.get_pixelv(pixel_posB).r * height_scale;
	
	
	print("A: " + str(heightA) + " B: " + str(heightB));
	
	# Together
	
	return global_position.y + (heightA + heightB)/2;

Why not just doing what the tutorial does?

Thanks for the suggestion, but this was actually my plan originally. Unfortunately, I didn’t like how the water looked in the project and couldn’t find a way to tweak it to my liking. From there I did as the video suggested and actually tried to copy the vertex height code being ran in my preferred shader to my GDscipt. I couldn’t get this to work. Any help with said vertex code or links to good learning resources would be greatly appreciated.

What exactly is not to your liking in that tutorial? It does exactly what you ask for. First make it work like that and then tweak and experiment with it.

I didn’t like the look of the water. I’ve tried adjusting it already. Really all I need is too learn how to copy this code for the shader shader I like.

You’ll learn that by completing the tutorial.

You haven’t shown us what the original shader and code for buoyancy looks like. Instead, you’ve asked us to watch a YouTube video, then look at your code and do the conversion for you.

We are here to help you, but we are volunteers. No one wants to have to dig through a YouTube video for details. It seems like a really nest video.

Where did the shader you like come from? (Because you clearly didn’t write it.) It also appears insanely overcomplicated for a water shader.

It might help you to read this: Posting guidelines in #Help channel

If ypu can get water height theres some numbers to take into account. 1. Archimedes principle:

[Density of water] *[submerged volume] =[mass of boat]

  1. Geometric centroid
  2. Center of buoyancy: Centroid of submerged part
  3. Center of mass: center of mass
  4. Metacentric height. The meta center is the point about what the boat seems to rotate, sometimes high, sometimes low depending on…
  5. Ballast:: or mass distribution. Boats stack heavy weights at the bottom and there were cases of ships with non attached ballast actually sinking.
  6. Sloshing of bilge water.

But if you just want to fake it … why not usr the tilt of the boat to change the local wave angle