Sci-Fi Scanner Pulse (Godot 3.5)

A basic scanner “pulse” effect that’s often found in sci-fi games.

It needs to be added to a Quad mesh that is size (2,2) and rendered in the frame. I would suggest making the mesh instance a child of the camera to keep track of it.

There also needs to be an associated script on the quad that keeps track of starting the effect, speed and distance travelled. Example script included below.

extends MeshInstance

# Internal Variables.
var distance := 0.0
var is_running := false

# Play with these variables.
export(float) var max_distance := 20.0
export(float) var speed := 2.0

# Get the reference to the material to pass data to shader parameters.
onready var SHADER:ShaderMaterial = self.get_active_material(0)

# Don't forget to assign the start point node to this variable.
export(NodePath) onready var origin_point = get_node(origin_point) as Spatial


func _ready():
	# Set the start point of the effect in the shader to the world position of
	# of the origin_point.
	SHADER.set_shader_param("start_point", origin_point.get_global_transform().origin)


func _process(delta):
	if is_running:
		distance += delta * speed
		if distance > max_distance:
			is_running = false
			# Set distance to 0 to stop shader from rendering the effect.
			distance = 0.0
		SHADER.set_shader_param("radius", distance)
	
	if Input.is_action_just_pressed("ui_accept"):
		is_running = true
		distance = 0.0
Shader code
/* 
Basic sci-fi pulse post-processing effect.
Required to be put on a quad mesh that is rendered in the scene.
Required associated script to increase the radius over time.
Video tutorial on YouTube: https://youtu.be/x1dIJdz8Uj8
Written by Michael Watt
*/

shader_type spatial;
render_mode unshaded;

// Settings to play with
uniform vec3 start_point = vec3(0.0);
uniform float pulse_width = 2.0;
uniform vec4 color :hint_color;

// Updated by Script
uniform float radius = 0.0;

// Not sure this is actually needed, but used in Documentation.
varying mat4 CAMERA;

void vertex() {
	// Set the Quad to cover the entire screen.
	// If it doesn't, resize the quad (x,y) to (2,2)
	POSITION = vec4(VERTEX, 1.0);
	
	// Again, from the docs, we need the interpolated camera matrix
	// Which I think you can just get in the fragment shader?
	CAMERA = CAMERA_MATRIX;
}

void fragment() {
	// Get the original screen rendered texture at the screen uv coordinates.
	vec4 original = texture(SCREEN_TEXTURE, SCREEN_UV);
	
	// Get the depth value form the depth buffer. Stored in the x value
	// of the depth texture.
	float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
	
	// Normalized Device Coordinates (clip space... mostly)
	vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
	
	// Convert to world space from NDC
	vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
	vec3 world_position = world.xyz / world.w;
	
	// Calculate how far the fragment (in world space) is from the start point.
	// If you want a cylinder effect not a sphere use the code below instead
	//	float dist = distance(world_position.xz, start_point.xz);
	float dist = distance(world_position, start_point);
	float mix_ratio = 0.0;
	
	if (dist < radius && dist > radius - pulse_width){
		mix_ratio = 1.0 - (radius - dist);
		
		// Clamp the mix ratio because you can get some unintential visuals
		
		mix_ratio = clamp(mix_ratio, 0.0, 1.0);
	}
	
	ALBEDO = mix(original.rgb, color.rgb, mix_ratio);
}
Tags
3.5, Depth buffer, depth texture, Post processing, Science fiction, scifi
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Watt Interactive

Quick Example Fresnel Outline

X-ray Vision Effect

Sci-Fi Scanner Pulse (Godot 4 update)

Related shaders

Sci-Fi Scanner Pulse (Godot 4 update)

SciFi Hologram

Pulse Aura for 3D Character

Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Narta Donfeud
Narta Donfeud
1 year ago

Hello! This is some great work. I have used it in a project in godot 4 so i have updated it. Here is the code.

Its important to note that you have to flip the faces for the plane mesh the camera faces

script:
########################
extends MeshInstance3D

# Internal Variables.
var distance := 0.0
var is_running := false

# Play with these variables.
@export var max_distance := 20.0
@export var speed := 2.0

# Get the reference to the material to pass data to shader parameters.
@onready var SHADER:ShaderMaterial = self.get_active_material(0)

# Don’t forget to assign the start point node to this variable.

@onready var origin_point = $”../../../..”

func _ready():
   # Set the start point of the effect in the shader to the world position of
   # of the origin_point.
   SHADER.set_shader_parameter(“start_point”, origin_point.get_global_transform().origin)

func _process(delta):
   if is_running:
      distance += delta * speed
      if distance > max_distance:
         is_running = false
         # Set distance to 0 to stop shader from rendering the effect.
         distance = 0.0
      SHADER.set_shader_parameter(“radius”, origin_point.get_global_transform().origin)

   if Input.is_action_just_pressed(“battle_menu_accept”):
      SHADER.set_shader_parameter(“start_point”, origin_point.get_global_transform().origin)

      is_running = true
      distance = 0.0
      print(SHADER.get_shader_parameter(“radius”))
      print(origin_point.get_global_transform().origin)
####################
shader:
#######################
shader_type spatial;
render_mode unshaded;

// Settings to play with
uniform vec3 start_point = vec3(0.0);
uniform float pulse_width = 2.0;
uniform vec4 color : source_color;
// Updated by Script
uniform float radius = 0.0;

// Not sure this is actually needed, but used in Documentation.
varying mat4 CAMERA;

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

void vertex() {
//   POSITION = vec4(UV*2.0-1.0, 0.0, 1.0);
   POSITION = vec4(VERTEX, 1.0);

   CAMERA = INV_VIEW_MATRIX;

}

void fragment() {
   // Get the original screen rendered texture at the screen uv coordinates.
   vec4 original = texture(SCREEN_TEXTURE, SCREEN_UV);

   // Get the depth value form the depth buffer. Stored in the x value
   // of the depth texture.
   float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;

   // Normalized Device Coordinates (clip space… mostly)
   vec3 ndc = vec3(SCREEN_UV * 2.0 – 1.0, depth);

   // Convert to world space from NDC
   vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
   vec3 world_position = world.xyz / world.w;

   // Calculate how far the fragment (in world space) is from the start point.
   // If you want a cylinder effect not a sphere use the code below instead
//   float dist = distance(world_position.xz, start_point.xz);
//   vec3 adusted_position = (inverse(shape_transform) * vec4(world_position, 1.0)).xyz;
   float dist = distance(world_position, start_point);
   float mix_ratio = 0.0;

   if (dist < radius && dist > radius – pulse_width){
      mix_ratio = 1.0 – (radius – dist);

      // Clamp the mix ratio because you can get some unintential visuals

      mix_ratio = clamp(mix_ratio, 0.0, 1.0);
   }

   ALBEDO = mix(original.rgb, color.rgb, mix_ratio);
}

###############