SUN Script

This a sun shader that works in a Node3D as a MeshInstance3D in godot 4.5 as a GDScript, feel free to use this as you like.

Shader code
# AnimatedSunProcedural.gd
# Fully self-contained Sun generator with animated shaders and pulse
# Now renders in the editor
@tool
extends Node3D

@export var pulse_min_interval : float = 3.5
@export var pulse_max_interval : float = 7.5
@export var pulse_scale_factor : float = 1.07
@export var pulse_half_duration : float = 4.2

var sun_mesh : MeshInstance3D
var time_accum : float = 0.0
var next_pulse_at : float = 0.0
var shader_materials : Array[ShaderMaterial] = []

func _enter_tree() -> void:
	# Ensure the sun is visible in editor
	_create_sun_mesh()
	_collect_materials()
	_schedule_next_pulse()

func _process(delta: float) -> void:
	# Skip pulse timing in editor
	if Engine.is_editor_hint():
		time_accum += delta
		for mat in shader_materials:
			if mat:
				mat.set_shader_parameter("time", time_accum)
		return
	
	time_accum += delta
	for mat in shader_materials:
		if mat:
			mat.set_shader_parameter("time", time_accum)
	if Time.get_unix_time_from_system() > next_pulse_at:
		_trigger_pulse()
		_schedule_next_pulse()

# ─── Single Sun Mesh ──────────────────────────────
func _create_sun_mesh() -> void:
	if sun_mesh:
		return
	
	sun_mesh = MeshInstance3D.new()
	sun_mesh.mesh = SphereMesh.new()
	var shader = Shader.new()
	shader.code = _sun_core_shader_code()
	
	var mat = ShaderMaterial.new()
	mat.shader = shader
	mat.set_shader_parameter("base_color", Color(1.0, 0.92, 0.38))
	
	sun_mesh.material_override = mat
	shader_materials.append(mat)
	
	add_child(sun_mesh)
	if Engine.is_editor_hint():
		sun_mesh.visible = true
	print("Created single Sun mesh (editor ready)")

# ─── Pulsing ──────────────────────────────
func _trigger_pulse() -> void:
	var tween = create_tween()
	var base_scale = sun_mesh.scale
	var peak_scale = base_scale * pulse_scale_factor
	tween.tween_property(sun_mesh, "scale", peak_scale, pulse_half_duration)\
		.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
	tween.tween_property(sun_mesh, "scale", base_scale, pulse_half_duration)\
		.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)

func _schedule_next_pulse() -> void:
	var delay = randf_range(pulse_min_interval, pulse_max_interval)
	next_pulse_at = Time.get_unix_time_from_system() + delay

func _collect_materials() -> void:
	if sun_mesh and sun_mesh.material_override is ShaderMaterial:
		shader_materials = [sun_mesh.material_override]

# ────────────────────────────────────────────────
# Shaders
# ────────────────────────────────────────────────
func _sun_core_shader_code() -> String:
	return """
shader_type spatial;
render_mode unshaded, cull_back;
uniform float time;
varying vec3 vert_pos;
varying vec3 vNormal;

vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }

float snoise(vec3 v) {
    const vec2 C = vec2(1.0/6.0, 1.0/3.0);
    const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
    vec3 i  = floor(v + dot(v, C.yyy) );
    vec3 x0 =   v - i + dot(i, C.xxx) ;
    vec3 g = step(x0.yzx, x0.xyz);
    vec3 l = 1.0 - g;
    vec3 i1 = min( g.xyz, l.zxy );
    vec3 i2 = max( g.xyz, l.zxy );
    vec3 x1 = x0 - i1 + C.xxx;
    vec3 x2 = x0 - i2 + C.yyy;
    vec3 x3 = x0 - D.yyy;
    i = mod289(i);
    vec4 p = permute( permute( permute( 
               i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
             + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
             + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
    float n_ = 0.142857142857; vec3  ns = n_ * D.wyz - D.xzx;
    vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
    vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_);
    vec4 x = x_ * ns.x + ns.yyyy; vec4 y = y_ * ns.x + ns.yyyy;
    vec4 h = 1.0 - abs(x) - abs(y);
    vec4 b0 = vec4( x.xy, y.xy ); vec4 b1 = vec4( x.zw, y.zw );
    vec4 s0 = floor(b0)*2.0 + 1.0; vec4 s1 = floor(b1)*2.0 + 1.0;
    vec4 sh = -step(h, vec4(0.0));
    vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
    vec3 p0 = vec3(a0.xy,h.x); vec3 p1 = vec3(a0.zw,h.y);
    vec3 p2 = vec3(a1.xy,h.z); vec3 p3 = vec3(a1.zw,h.w);
    vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
    p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
    vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
    m = m * m;
    return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}

float fbm(vec3 p) {
    float v = 0.0; float a = 0.9; vec3 shift = vec3(100.0);
    for (int i = 0; i < 8; ++i) { v += a * snoise(p); p = p * 2.0 + shift; a *= 0.9; }
    return v;
}

float fbmDetail(vec3 p) {
    float v = 0.0; float a = 0.9; vec3 shift = vec3(50.0);
    for (int i = 0; i < 8; ++i) { v += a * snoise(p); p = p * 3.0 + shift; a *= 0.9; }
    return v;
}

void vertex() {
    vert_pos = VERTEX;
    vNormal = NORMAL;
}

void fragment() {
    vec3 pos = vert_pos * 0.9 + time * -0.05;
    float n1 = fbm(pos); n1 = n1 * 2.0 + 0.9;
    float n2 = fbm(pos + vec3(40.0)); n2 = n2 * 0.9 + 0.9;
    float baseNoise = (n1 * 0.6 + n2 * 0.9); baseNoise = clamp(baseNoise, 0.0, 1.0);
    float detail = fbmDetail(pos * 5.0 + vec3(30.0));
    detail = detail * 0.5 + 0.5; baseNoise += detail * 0.15; baseNoise = clamp(baseNoise, 0.0, 1.0);
    vec3 viewDir = normalize(CAMERA_POSITION_WORLD - (MODEL_MATRIX * vec4(vert_pos,1.0)).xyz);
    float rim = 1.0 - max(dot(vNormal, viewDir), 0.0); rim = pow(rim, 2.0);
    float glowFactor = rim;
    vec3 glowColor = vec3(1.0, 0.9, 0.6);
    vec3 radiantGlow = glowColor * glowFactor * 0.9;
    vec3 yellow = vec3(1.0, 0.55, 0.0);
    vec3 orange = vec3(1.0, 0.27, 0.0);
    vec3 red = vec3(0.8, 0.2, 0.0);
    vec3 color = mix(yellow, orange, baseNoise);
    color = mix(color, red, detail * 0.9);
    color += radiantGlow;
    ALBEDO = color;
    EMISSION = color * 0.5;
}
"""

func _atmosphere_shader_code(_has_glow: bool) -> String:
	return """
shader_type spatial;
render_mode unshaded, cull_front, blend_add;
uniform vec3 color_start : source_color;
uniform vec3 color_end : source_color;
uniform float glow_intensity = 1.5;
void fragment() {
    float dist = length(VERTEX) / 1.0;
    vec3 color = mix(color_start, color_end, dist);
    float intensity = pow(0.8 - dot(NORMAL, VIEW), 2.0);
    ALBEDO = color * intensity * glow_intensity;
    ALPHA = intensity * glow_intensity;
}
"""
Live Preview
Tags
Sun
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.

Related shaders

guest

2 Comments
Oldest
Newest Most Voted
binbun
4 months ago

This is cool, but I’m having trouble understanding why this is GDScript instead of just a shader

CheapDev
CheapDev
5 days ago

shader_type spatial;
// Added fog_disabled so environmental fog will no longer wash out or affect this shader
render_mode unshaded, cull_back, fog_disabled;

// — NEW OPTIONS —
group_uniforms Colors;
uniform vec4 color_bright : source_color = vec4(1.0, 0.92, 0.38, 1.0);
uniform vec4 color_mid : source_color = vec4(1.0, 0.27, 0.0, 1.0);
uniform vec4 color_dark : source_color = vec4(0.8, 0.2, 0.0, 1.0);
uniform vec4 glow_color : source_color = vec4(1.0, 0.9, 0.6, 1.0);

group_uniforms Animation;
uniform float animation_speed : hint_range(0.0, 5.0) = 0.5;
uniform vec3 flow_direction = vec3(-0.1, 0.05, 0.02);
// Adds a “boiling” effect by pushing vertices out based on the noise
uniform float displacement_amount : hint_range(0.0, 1.0) = 0.05;
// Throbbing/pulsing glow animation
uniform float pulse_speed : hint_range(0.0, 10.0) = 2.0;
uniform float pulse_intensity : hint_range(0.0, 1.0) = 0.15;

group_uniforms Noise_Settings;
uniform float noise_scale : hint_range(0.1, 10.0) = 0.9;
uniform float detail_scale : hint_range(0.1, 20.0) = 5.0;

group_uniforms Glow_Settings;
uniform float rim_power : hint_range(0.1, 10.0) = 2.0;
uniform float rim_intensity : hint_range(0.0, 5.0) = 0.9;
uniform float emission_multiplier : hint_range(0.0, 5.0) = 0.5;

varying vec3 vert_pos;

// — NOISE FUNCTIONS —
vec3 mod289_3(vec3 x) { return x – floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289_4(vec4 x) { return x – floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289_4(((x * 34.0) + 1.0) * x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 – 0.85373472095314 * r; }

float snoise(vec3 v) {
const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v – i + dot(i, C.xxx);
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 – g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 – i1 + C.xxx;
vec3 x2 = x0 – i2 + C.yyy;
vec3 x3 = x0 – D.yyy;
i = mod289_3(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
 + i.y + vec4(0.0, i1.y, i2.y, 1.0))
 + i.x + vec4(0.0, i1.x, i2.x, 1.0));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz – D.xzx;
vec4 j = p – 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j – 7.0 * x_);
vec4 x = x_ * ns.x + ns.yyyy;
vec4 y = y_ * ns.x + ns.yyyy;
vec4 h = 1.0 – abs(x) – abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
vec4 m = max(0.6 – vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}

float fbm(vec3 p) {
float v = 0.0; float a = 0.9; vec3 shift = vec3(100.0);
for (int i = 0; i < 8; ++i) { v += a * snoise(p); p = p * 2.0 + shift; a *= 0.9; }
return v;
}

float fbmDetail(vec3 p) {
float v = 0.0; float a = 0.9; vec3 shift = vec3(50.0);
for (int i = 0; i < 8; ++i) { v += a * snoise(p); p = p * 3.0 + shift; a *= 0.9; }
return v;
}

void vertex() {
vert_pos = VERTEX;

// ANIMATION: Vertex displacement
// Pushes the vertices in and out based on the noise to give it a 3D “boiling” shape
if (displacement_amount > 0.0) {
float displacement = fbm(VERTEX * noise_scale + (TIME * animation_speed * flow_direction));
VERTEX += NORMAL * (displacement * displacement_amount);
}
}

void fragment() {
// Using Godot’s built-in TIME variable instead of requiring a manual uniform
vec3 pos = vert_pos * noise_scale + TIME * animation_speed * flow_direction;

float n1 = fbm(pos); n1 = n1 * 2.0 + 0.9;
float n2 = fbm(pos + vec3(40.0)); n2 = n2 * 0.9 + 0.9;
float baseNoise = (n1 * 0.6 + n2 * 0.9); 
baseNoise = clamp(baseNoise, 0.0, 1.0);

float detail = fbmDetail(pos * detail_scale + vec3(30.0));
detail = detail * 0.5 + 0.5; 
baseNoise += detail * 0.15; 
baseNoise = clamp(baseNoise, 0.0, 1.0);

// Rim Glow
float rim = 1.0 – max(dot(NORMAL, VIEW), 0.0);
rim = pow(rim, rim_power);

// ANIMATION: Pulsing Effect
float pulse = 1.0 + (sin(TIME * pulse_speed) * pulse_intensity);

float glowFactor = rim * pulse;
vec3 radiantGlow = glow_color.rgb * glowFactor * rim_intensity;

// Coloring
vec3 color = mix(color_bright.rgb, color_mid.rgb, baseNoise);
color = mix(color, color_dark.rgb, detail * 0.9);
color += radiantGlow;

ALBEDO = color;
// Applying the pulse to emission as well so the light level throbs organically
EMISSION = color * emission_multiplier * pulse;
}

Maybe i made this Better enjoy its yours but dont license it (GD shader)