Screen Space God Rays (Godot.4.3)

This shader re-creates the effect described in the following article:
volumetric-light-scattering-as-a-custom-renderer-feature-in-urp

You can effectively mimic the behaviours of volumetric light by adding a radial blur to a white circle that is positioned on a light source and multiplied by an object occlusion mask. This can be achieved in Godot using a SubViewport that renders the scene with the background missing, which is then passed to a ColorRect containing a shader which calculates the light source circle and radial blur.

Instructions:

1. Add the following nodes to your scene :

  • Control
    • ColorRect_LightRays
  • SubViewportContainer
    • SubViewport
    • Camera3D_Viewport

2. Add a RemoteTransform3D node to your current camera, or the node that controls your main camera. 

  • Player
    • Head
      • Camera3D [current]
      • RemoteTransform3D

       Note: See screenshots for final node structure.

3. Adjust node settings accordingly:

RemoteTransform3D
    Remote Path: Camera3D_Viewport
    Use Global Coordinates: true
    Update -> Position: true
    Update -> Rotation: true
    Update -> Scale: false 

ColorRect_LightRays
    Layout -> Anchor Preset: Full Rect
    Material -> Resource -> Local to Scene: true

SubviewportContainer
    Layout -> Anchor Preset: Full Rect
    Visibility -> Visibility Layer: null  // make sure no layers are selected

Subviewport
    Size: [ set to your screen resolution ]
    RenderTarget -> Clear Mode: NextFrame
    RenderTarget -> Update Mode: Always
    Viewport -> Transparent BG: true

4. Create a new script, copy/paste the ‘GD Script’ section from the Shader code below and then add the script to the ColorRect_LightRays node in your scene.

5. Set the Light, Camera node paths for the ColorRect node:

  • The ‘Light’ path should point to a DirectionalLight3D node in your scene.
  • The Camera path should point to your main/current camera.

6. Add a new Shader Material to the ColorRect_LightRays node, create a new shader script and then copy/paste the ‘Shader’ section from the Shader code below.

7. Open the shader material parameters section and click on the ‘Subviewport Tex’ paramater.

8. Select ‘New ViewportTexture’ and then select the Subviewport node in your scene.

9. Adjust the parameters below to change the behaviour of the light rays:

  • Ray Length
  • Ray Intensity
  • Light Source Scale
  • Light Source Feather

Limitations:

  • Objects that are not within the camera’s view will not occlude light and if the light source is behind the camera no light rays will be drawn.
  • Light may not be occluded correctly on transparent materials. Addative shading appears to work best when dealing with shader materials -> render_mode blend_add; 
  • This will only work with DirectionalLight3D nodes.
    Omni lights can work with the same method but creating the occlusion mask requires finding objects that are in-front of and behind a given light source.

 

Shader code
// GD Script
// Add to ColorRect_LightRays

extends ColorRect

@export var light_path : NodePath
@onready var light : DirectionalLight3D = get_node(light_path)   

@export var camera_path : NodePath
@onready var camera : Camera3D = get_node(camera_path)

func _process(_delta: float) -> void:
	var pos = camera.unproject_position(camera.global_position - (-light.global_basis.z.normalized()))   
	material.set_shader_parameter("light_source_pos", pos)
	material.set_shader_parameter("light_source_dir", -light.global_basis.z)
	material.set_shader_parameter("camera_dir", -camera.global_basis.z)

// Shader
// Add to ColorRect_LightRays -> Materials -> ShaderMaterial

shader_type canvas_item;
render_mode unshaded, blend_add;

uniform sampler2D subviewport_tex : filter_linear;
uniform sampler2D light_color : hint_default_white;

uniform float ray_length : hint_range(0.0, 1.0) = 1.0;
uniform float ray_intensity : hint_range(0.0, 1.0) = 1.0;
uniform float light_source_scale = 1.0;
uniform float light_source_feather = 2.0;
uniform float noise_strength : hint_range(0.0, 1.0) = 0.2;

uniform vec2 light_source_pos = vec2(0.0, 0.0);
uniform vec3 light_source_dir = vec3(0.5, -1.0, 0.25);
uniform vec3 camera_dir = vec3(-0.5, 1.0, -0.25);

const int SAMPLE_COUNT = 200;

float random (vec2 uv) {
    return fract(sin(dot(uv.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

void fragment() {
	// divide light source pos by screen size for correct UV positioning
	vec2 light_pos = light_source_pos / (1.0 / SCREEN_PIXEL_SIZE);
	vec2 dir = UV - light_pos;
	
	// light source uv coords with correct aspect ratio for drawing circle
	vec2 ratio = vec2(SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y, 1.0);
	vec2 dir2 = UV / ratio - light_pos / ratio;
	
	float light_rays = 0.0;
	vec2 uv2;
	float scale, l;
	for (int i = 0; i < SAMPLE_COUNT; i++){
		scale = 1.0f - ray_length * (float(i) / float(SAMPLE_COUNT - 1));
		l = (1.0 - texture(subviewport_tex, dir * scale + light_pos).a);
		uv2 = dir2 * scale * pow(light_source_scale, 2.0);
		l *= smoothstep(1.0, 0.999 - light_source_feather, dot(uv2, uv2) * 4.0);
		light_rays += l / float(SAMPLE_COUNT);
	}
	
	// multiply with noise to reduce color banding
	float n = 1.0 - random(UV) * noise_strength * smoothstep(0.999 - 1.25, 1.0, dot(dir2, dir2) * 2.0);
	light_rays *= n;
	
	// multiply with camera/light dot to fade based on view angle 
	float d = clamp(-dot(normalize(camera_dir), normalize(light_source_dir)), 0.0, 1.0);
	light_rays *= d;
	
	COLOR.rgb = vec3(light_rays * ray_intensity) * texture(light_color, UV).rgb;
}
Tags
god rays, rays of light, screen-space, sun rays
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 Qtan1

Camera Distance UV Scaling

Dynamic Depth of Field

Related shaders

God rays

Local Screen Space UV

screen space refraction shader

Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
eldskald
9 days ago

These are really good results! Very detailed instructions as well, thanks for making this shader!