Weird 3D visual blur when moving character

4.5.stable

Hello, I am having a weird visual blur occur only when moving my character. This does not occur when moving the camera only when moving the character itself. I have attached a video link that shows the issue. If you look at the white wall when I am moving the camera the edges of it look smooth. However when looking at when I am moving, there is blur or ghosting happening. Wondering if anyone has any tips or ideas to resolve this? Thank you!

Video: https://youtu.be/UUSA2i-eUIU

extends CharacterBody3D

#nodes
@export_group("Nodes")

#Character root node.
@export var character : CharacterBody3D

#Head node.
@export var head : Node3D

#Settings.
@export_group("Settings")

#Mouse settings.
@export_subgroup("Mouse settings")

#mouse sensitivity.
@export_range(1, 100, 1) var mouse_sensitivity: int = 50

#pitch clamp settings.
@export_subgroup("Clamp settings")

#max pitch in degrees.
@export var max_pitch : float = 60

#min pitch in degrees.
@export var min_pitch : float = -89

@onready var pivot = $CamOrigin
@onready var pivot2 = $CamOrigin/Node3D
@onready var body = $CollisionShape3D
@onready var FPP = $CollisionShape3D/MeshInstance3D/Head/FPP
@onready var TPP = $CamOrigin/Node3D/SpringArm3D/TPP

@export var TPPSense = .01

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var speed = 5
var jump_height = 5
var jump_speed = 1.5

func _ready():
	Input.set_use_accumulated_input(false)
	pivot.set_as_top_level(true)

#Game pause + close
func _unhandled_input(event)->void:
	if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
		if event is InputEventKey:
			if event.is_action_pressed("ui_cancel"):
				get_tree().quit()

		if event is InputEventMouseButton:
			if event.button_index == 1:
				Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

		return

	if event is InputEventKey:
		if event.is_action_pressed("ui_cancel"):
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

		return

	if FPP.is_current() and event is InputEventMouseMotion:
		aim_look(event)

#Handles aim look with the mouse.
func aim_look(event: InputEventMouseMotion)-> void:
		var viewport_transform: Transform2D = get_tree().root.get_final_transform()
		var motion: Vector2 = event.xformed_by(viewport_transform).relative
		var degrees_per_unit: float = 0.001
	
		motion *= mouse_sensitivity
		motion *= degrees_per_unit
	
		add_yaw(motion.x)
		add_pitch(motion.y)
		clamp_pitch()

#Rotates the character around the local Y axis by a given amount (In degrees) to achieve yaw.
func add_yaw(amount)->void:
		if is_zero_approx(amount):
			return

		character.rotate_object_local(Vector3.DOWN, deg_to_rad(amount))
		character.orthonormalize()

#Rotates the head around the local x axis by a given amount (In degrees) to achieve pitch.
func add_pitch(amount)->void:
		if is_zero_approx(amount):
			return

		head.rotate_object_local(Vector3.LEFT, deg_to_rad(amount))
		head.orthonormalize()

#Clamps the pitch between min_pitch and max_pitch.
func clamp_pitch()->void:
	if head.rotation.x > deg_to_rad(min_pitch) and head.rotation.x < deg_to_rad(max_pitch):
		return

	head.rotation.x = clamp(head.rotation.x, deg_to_rad(min_pitch), deg_to_rad(max_pitch))
	head.orthonormalize()

#Jump + Movement + Gravity
func _physics_process(delta):
	camera_follows_player()
	velocity.y += -gravity * delta

	move_and_slide()
	if is_on_floor() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and Input.is_action_just_pressed("jump"):
		velocity.y = jump_height

	if FPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and velocity.y != 0:
		var input = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
		var movement_dir = transform.basis * Vector3(input.x, 0, input.y)
		velocity.x = movement_dir.x * speed  * jump_speed
		velocity.z = movement_dir.z * speed  * jump_speed
	elif FPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and velocity.y == 0:
		var input = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
		var movement_dir = transform.basis * Vector3(input.x, 0, input.y)
		velocity.x = movement_dir.x * speed
		velocity.z = movement_dir.z * speed
		
	if TPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and velocity.y != 0:
		var input = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
		var movement_dir = pivot.global_basis * Vector3(input.x, 0, input.y)
		velocity.x = movement_dir.x * speed * jump_speed
		velocity.z = movement_dir.z * speed * jump_speed
	elif TPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and velocity.y == 0:
		var input = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
		var movement_dir = pivot.global_basis * Vector3(input.x, 0, input.y)
		velocity.x = movement_dir.x * speed
		velocity.z = movement_dir.z * speed

	if TPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and velocity.length() > 0:
		character.rotation.y = atan2(-velocity.x,-velocity.z)
		character.orthonormalize()

func camera_follows_player():
	var player_pos = global_transform.origin
	pivot.global_transform.origin = Vector3(player_pos.x, player_pos.y + 2, player_pos.z)
	pivot.orthonormalize()

#TPP camera controls + camera swap
func _input(event):
	if TPP.is_current() and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and event is InputEventMouseMotion:
		pivot.rotate_object_local(Vector3.DOWN, event.relative.x * TPPSense)
		pivot.orthonormalize()
		pivot2.rotate_object_local(Vector3.LEFT, -event.relative.y * TPPSense)
		pivot2.rotation.x = clamp(pivot2.rotation.x, deg_to_rad(-75.0), deg_to_rad(25))
		pivot2.orthonormalize()
		

	if Input.is_action_pressed("switch_perspective"):
		if FPP.is_current():
			FPP.clear_current(true)
		elif TPP.is_current():
			TPP.clear_current(true)

Looks like jitter to me, not blur/ghosting. This is a common problem in early game development.

There’s a couple of problems in your script and there are two ways of fixing the jitter issue.

Problem #1

You’re calling move_and_slide() before applying all changes to velocity. Remember that move_and_slide()'s behaviour is dependent on velocity. Therefore, you should modify velocity and then call move_and_slide() – otherwise your inputs will be used one physics frame later than it needs to.

Execution order is important.

Problem 2

In line with problem #1, you’re updating the camera position before the player is moved. Your camera is dependent on the player position so it should only be moved once the player position is up-to-date (after move_and_slide() has been called).


You may find that fixing these issues still doesn’t resolve your camera jitter. In that case, it’s likely a consequence of updating the camera at two different frequencies. Right now, your camera’s rotation is updated in _input() while its position is updated in _physics_process(). Moving your camera_follows_player() into _process() will make the camera’s transform update as fast as the screen is refreshed.

However… this likely still does not fix the jitter issue. To completely fix it, you have to enable physics interpolation and use the physics object’s interpolated transform (get_global_transform_interpolated()) when setting the camera’s transform.

Enabling physics interpolation means ticking the Physics Interpolation setting in the project settings under Physics > Common. This setting is only visible when Advanced Settings are visible which can be toggled in the top-right part of the project settings window.

Here’s an example for your case:

func camera_follows_player():
	var player_pos = get_global_transform_interpolated().origin
	pivot.global_position = Vector3(player_pos.x, player_pos.y + 2, player_pos.z)
	#pivot.orthonormalize()
	# Orthonormalization has nothing to do with position. DELETE or MOVE it.

Just to be clear, physics are not updated at the same rate the screen is. This is why physics interpolation exists: to eliminate/mitigate the discrepancy in update rate and achieve a smooth result.
(See Godot 4.4 release page for a visual example)


I hope that clears it up for you. Let me know if you have any more questions.

2 Likes

Hey thanks for the reply! Sorry it took a bit to get back to you, had a bit of a workplace accident.. but I found some time to rearrange some things which seemed to help in general but did not fix the issue of the jittering. I tried to work with interpolation in the code but was giving me errors so I decided to look around even more on the internet for a bit but still wasn’t getting anywhere with it. TILL I found a one sentence response on a reddit post mentioning that there is a project setting for physics interpolation. Looked into, and sure enough, there it was. Turned it on and the jittering seems to be gone!

Thanks again for the help and detailed response, got me taking more careful note of how I order my code.

1 Like

Good to hear! I’ll edit my initial post to provide more explicit instruction on how to enable interpolation – and fix a minor mistake in the code example. Please, then, mark it as the solution.


Happy development!

1 Like

Sounds good! Thanks again for the help!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.