How to make a shader that squash and stretches a 3D object based on its acceleration

Godot Version

4.3

Question

I’m making a dodge ball like game and I want the ball to stretch when thrown and squish when it hits an object. I’m guessing I would do this based off its acceleration and would use a vertex shader. I’m pretty new to shaders so any guidance would be helpful.

You can try to change the scale for it. Otherwise, I found a tutorial on realistic squash and stretch in 3d, you can check it.

Thanks! I tried that tutorial but it seems to not be quite what I’m looking for. I don’t like how it squashes in the middle. Here is a Unity asset that does more like what I’m looking for: https://www.youtube.com/watch?v=RjFjx_sC_XM&t=60s

Would this effect be difficult to create?

1 Like

You can also use tweens tied to the velocity of the object, but i doubt the effect would be that smooth.
Still a workaround.

1 Like

This is what I managed to do so far:

The problem I’m facing now is the squash when hitting objects doesn’t look correct. It seems the object flips directions instead of squashing. Here is my code:

extends MeshInstance3D

@export var factor: float = 0.6  # Adjust as needed for stretching
@export var threshold: float = 0.1  # Acceleration threshold for stretching
@export var jerk_threshold: float = 1.0  # Jerk threshold for collision detection
@export var min_stretch: float = 1
@export var max_stretch: float = 2
@export var min_squash: float = .5  # Minimum squash factor
@export var smoothing_factor: float = 11 # Between 0 and 1
@export var squash_factor_multiplier: float = 0.7  # Adjust for squashing

var prev_pos: Vector3
var velocity: Vector3              = Vector3.ZERO
var prev_velocity: Vector3         = Vector3.ZERO
var acceleration: Vector3          = Vector3.ZERO
var prev_acceleration: Vector3     = Vector3.ZERO
var smoothed_acceleration: Vector3 = Vector3.ZERO
var prev_stretch_factor: float     = 1.0
var prev_axis: Vector3             = Vector3.UP


func _ready() -> void:
	prev_pos = global_transform.origin


func _physics_process(delta: float) -> void:
	# Update velocity and acceleration
	velocity = (global_transform.origin - prev_pos) / delta
	print(velocity.length())
	acceleration = (velocity - prev_velocity) / delta
	prev_velocity = velocity
	prev_pos = global_transform.origin  # Update prev_pos here
	# Apply smoothing to acceleration
	smoothed_acceleration = smoothed_acceleration.lerp(acceleration, smoothing_factor)

	# Calculate jerk (rate of change of acceleration)
	var jerk = (smoothed_acceleration - prev_acceleration) / delta
	prev_acceleration = smoothed_acceleration

	# Calculate magnitudes
	var accel_length = smoothed_acceleration.length()
	var jerk_length  = jerk.length()

	# Determine if a collision occurred based on jerk threshold
	var collision_detected = false
	if jerk_length > jerk_threshold:
		#print(jerk_length)
		collision_detected = true

	# Determine target stretch values
	var target_stretch_factor = 1.0
	var target_stretch_axis   = prev_axis
	if get_parent().get_attached_status() == false:
		
		if velocity.length() > threshold:
			print("threshhold")
		# No collision detected, apply stretch based on acceleration
			target_stretch_factor = clamp(1.0 + (velocity.length() * factor), min_stretch, max_stretch)
			target_stretch_axis = velocity.normalized()
			if target_stretch_axis.length() == 0:
				target_stretch_axis = prev_axis
		else:
		# Optional: Use previous values for smoother transition
			target_stretch_factor = 1.0
			target_stretch_axis = prev_axis
	# Prevent abrupt flipping of the stretch axis
		if prev_axis.dot(target_stretch_axis) < 0:
			pass
		#target_stretch_axis = -target_stretch_axis
	else:
		target_stretch_axis = Vector3.UP
		target_stretch_factor = 1.0
	#Smoothly interpolate current values towards target values
	
	var t = 1.0 - exp(-smoothing_factor * delta)
	var material = mesh.surface_get_material(0)
	var current_stretch_factor = prev_stretch_factor
	var new_stretch_factor = lerp(current_stretch_factor, target_stretch_factor, t)
	
	material.set("shader_parameter/stretch_factor", new_stretch_factor)
	prev_stretch_factor = new_stretch_factor

	var current_stretch_axis = prev_axis
	var new_stretch_axis = current_stretch_axis.lerp(target_stretch_axis, t)
	var inv_basis : Basis= global_transform.basis.inverse()
	var target_stretch_axis_local = inv_basis * new_stretch_axis
	material.set("shader_parameter/stretch_axis", target_stretch_axis_local.normalized())
	prev_axis = new_stretch_axis

I think the issue is with the lerping of the axis.

1 Like