The Sun, a Mass of Incandescent Gas
A giant nuclear furnace.
Uses technique similar to a few fluid simulations I found on shader toy to resemble churning plasma and create dark areas for sunspots.
Shader code
shader_type canvas_item;
render_mode blend_premul_alpha;
// --- colors, cool -> hot ---
uniform vec3 cool_color : source_color = vec3(0.55, 0.08, 0.0);
uniform vec3 mid_color : source_color = vec3(1.0, 0.45, 0.05);
uniform vec3 hot_color : source_color = vec3(1.0, 0.92, 0.65);
uniform bool use_color_ramp = false; // use a GradientTexture1D instead of the three colors
uniform sampler2D color_ramp : source_color, repeat_disable, filter_linear;
uniform float emission = 1.0; // above 1 blooms if HDR 2D + glow are on
uniform float contrast = 1.5;
uniform float limb_darkening : hint_range(0.0, 1.0) = 0.6;
// --- surface ---
uniform float granulation : hint_range(0.0, 1.0) = 0.35; // strength of the small convection cells
uniform float granule_size = 10.0; // higher = smaller cells
// --- sunspots ---
uniform bool spot_independent = true; // own slow field (persistent) vs. reusing the plasma field
uniform float spot_threshold : hint_range(0.0, 0.5) = 0.08; // length(field) below this = spot, raise for more spots
uniform float spot_scale = 0.6; // multiplies the swirl scale; lower = bigger spots (independent only)
uniform float spot_drift = 0.15; // fraction of the flow clock; lower = longer-lived (independent only)
uniform float spot_darkness : hint_range(0.0, 1.0) = 0.85; // how dark the umbra gets
uniform float spot_relief = 0.5; // ripple shading strength in the penumbra
uniform bool spot_band_only = true; // restrict spots to the specific latitudes
uniform vec2 spot_light_dir = vec2(-0.7, 0.7); // relief light direction, screen space, y up
// --- corona ---
uniform float sun_radius : hint_range(0.1, 1.0) = 0.75; // disk size
uniform vec3 corona_color : source_color = vec3(1.0, 0.5, 0.1);
uniform float corona_strength = 0.8;
uniform float corona_falloff = 6.0; // higher = tighter glow
// --- custom noise ---
uniform bool use_noise_texture = false;
uniform sampler2D noise_texture : repeat_enable, filter_linear;
uniform float noise_texture_scale = 0.2; // texture repeats per noise unit (lower = bigger features)
// --- motion ---
uniform float scale = 2.0; // swirl size (higher = smaller swirls)
uniform float speed = 0.1;
uniform float swirl = 1.5; // how hard the flow twists
uniform float spin_speed = 0.03; // sphere rotation (0 = still)
const mat2 ROT = mat2(vec2(0.8, 0.6), vec2(-0.6, 0.8));
uint lowbias(uint x) {
x ^= x >> 16u; x *= 2146121005u;
x ^= x >> 15u; x *= 2221713035u;
x ^= x >> 16u;
return x;
}
vec2 hash2(vec2 p) {
uvec2 q = uvec2(ivec2(p));
float a = float(lowbias(q.x + lowbias(q.y)) >> 8u) * (TAU / 16777216.0);
return vec2(cos(a), sin(a));
}
float simplex(vec2 p) {
const float K1 = 0.366025404; // (sqrt(3)-1)/2
const float K2 = 0.211324865; // (3-sqrt(3))/6
vec2 i = floor(p + (p.x + p.y) * K1);
vec2 a = p - i + (i.x + i.y) * K2;
float m = step(a.y, a.x);
vec2 o = vec2(m, 1.0 - m);
vec2 b = a - o + K2;
vec2 c = a - 1.0 + 2.0 * K2;
vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0);
vec3 n = h * h * h * h * vec3(dot(a, hash2(i)), dot(b, hash2(i + o)), dot(c, hash2(i + 1.0)));
return dot(n, vec3(70.0));
}
vec4 cubic(float v) {
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = 6.0 - x - y - z;
return vec4(x, y, z, w) * (1.0 / 6.0);
}
float tex_noise(vec2 p) {
vec2 tres = vec2(textureSize(noise_texture, 0));
vec2 uv = p * noise_texture_scale * tres - 0.5;
vec2 f = fract(uv);
uv -= f;
vec4 xc = cubic(f.x);
vec4 yc = cubic(f.y);
vec4 c = uv.xxyy + vec2(-0.5, 1.5).xyxy;
vec4 s = vec4(xc.xz + xc.yw, yc.xz + yc.yw);
vec4 o = (c + vec4(xc.yw, yc.yw) / s) / tres.xxyy;
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
float s0 = texture(noise_texture, o.xz).r;
float s1 = texture(noise_texture, o.yz).r;
float s2 = texture(noise_texture, o.xw).r;
float s3 = texture(noise_texture, o.yw).r;
return mix(mix(s3, s2, sx), mix(s1, s0, sx), sy) * 2.0 - 1.0;
}
// use noise texture if chosen, simplex noise otherwise
float noise(vec2 p) {
if (use_noise_texture) {
return tex_noise(p);
}
return simplex(p);
}
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * (0.5 + 0.5 * noise(p));
p = ROT * p * 2.02 + vec2(3.1, 1.7);
a *= 0.5;
}
return v;
}
// churning plasma flow
vec3 field(vec2 uv, float t) {
vec2 p = uv * scale;
vec2 q = vec2(fbm(p + vec2(0.0, t)), fbm(p + vec2(5.2, 1.3) - t));
vec2 r = vec2(fbm(p + swirl * q + vec2(1.7, 9.2) + 0.15 * t),
fbm(p + swirl * q + vec2(8.3, 2.8) - 0.12 * t));
float f = fbm(p + swirl * r);
return vec3(r, f) - 0.5;
}
vec3 ramp(float h) {
if (use_color_ramp) {
return texture(color_ramp, vec2(h, 0.5)).rgb;
}
return mix(mix(cool_color, mid_color, smoothstep(0.0, 0.5, h)), hot_color, smoothstep(0.5, 1.0, h));
}
// screen-space point
vec2 sphere_uv(vec2 c) {
vec2 cc = c / max(length(c), 1.0);
vec3 N = vec3(cc, sqrt(max(1.0 - dot(cc, cc), 0.0)));
return vec2(atan(N.x, N.z) / PI + 0.5 + TIME * spin_speed,
0.5 - asin(clamp(N.y, -1.0, 1.0)) / PI);
}
// height map for the sunspot ripples: low = spot center
float spot_height(vec2 suv, float t) {
if (spot_independent) {
return length(field(suv * spot_scale, t * spot_drift));
}
return length(field(suv, t));
}
void fragment() {
// screen-space derivatives first
float aspect = abs(dFdy(UV.y) / dFdx(UV.x)); // node width / height
vec2 fit = vec2(max(aspect, 1.0), max(1.0 / aspect, 1.0));
vec2 c = (UV - 0.5) * 2.0 * fit * vec2(1.0, -1.0) / sun_radius; // 1.0 = edge of the sun, y up
float r = length(c);
float edge = fwidth(r);
float px = 1.4 * abs(dFdx(c.x)); // gradient step for the spot relief
float t = TIME * speed;
float disk = 1.0 - smoothstep(1.0 - edge, 1.0, r); // antialiased rim
vec3 sun = vec3(0.0);
if (disk > 0.0) {
// point on the front of a unit sphere
vec2 cc = c / max(r, 1.0);
vec3 N = vec3(cc, sqrt(max(1.0 - dot(cc, cc), 0.0)));
float mu = N.z; // 1 at the center, 0 at the edge
float lon = atan(N.x, N.z);
float lat = asin(clamp(N.y, -1.0, 1.0));
vec2 suv = vec2(lon / PI + 0.5 + TIME * spin_speed, 0.5 - lat / PI);
// large churning flows
vec3 fld = field(suv, t);
float heat = fld.z + 0.5;
// sunspot masks
float h = spot_independent ? spot_height(suv, t) : length(fld);
float band = 1.0;
if (spot_band_only) {
float al = abs(lat);
band = smoothstep(0.05, 0.15, al) * (1.0 - smoothstep(0.4, 0.6, al));
}
float penumbra = (1.0 - smoothstep(spot_threshold * 0.6, spot_threshold, h)) * band;
float umbra = (1.0 - smoothstep(spot_threshold * 0.2, spot_threshold * 0.55, h)) * band;
// convection cells
float gn = noise(suv * granule_size + fld.xy * 3.0 + vec2(t * 0.7, -t * 0.5));
float cells = smoothstep(0.0, 0.45, abs(gn));
heat *= mix(1.0, cells, granulation * (1.0 - penumbra) * smoothstep(0.0, 0.3, mu));
heat = clamp((heat - 0.45) * contrast + 0.55, 0.0, 1.0);
heat *= 1.0 - 0.5 * penumbra;
// limb darkening: dimmer/redder toward the edge
float limb = 1.0 - limb_darkening * (1.0 - sqrt(mu));
sun = ramp(heat * mix(1.0, limb, 0.5)) * limb * emission;
// umbra: dark core
sun *= 1.0 - umbra * spot_darkness;
// penumbra: bump-lit ripple from the height gradient
if (penumbra > 0.0) {
vec2 g = vec2(spot_height(sphere_uv(c + vec2(px, 0.0)), t) - h,
spot_height(sphere_uv(c + vec2(0.0, px)), t) - h) / px;
float lit = dot(g, normalize(spot_light_dir) * vec2(-1.0, -1.0));
sun *= max(1.0 + spot_relief * penumbra * mu * lit, 0.0);
}
}
// corona
vec2 dir = c / max(r, 1e-4);
float flare = fbm(dir * 3.0 + (r * 1.5 - t * 4.0));
float glow = exp(-max(r - 1.0, 0.0) * corona_falloff) * (0.4 + 1.2 * flare) * corona_strength;
float rr = r * sun_radius; // 1.0 = edge of the node
glow *= 1.0 - smoothstep(sun_radius, 1.0, rr);
vec3 corona = corona_color * glow * emission;
COLOR = vec4(sun * disk + corona * (1.0 - disk), disk);
}
