Solar Corona Limb Brightening via GLSL Radial Decay

This article explains how to simulate and evaluate solar corona limb brightening within a GLSL fragment shader using radial exponential decay functions. It covers the mathematical formulation of optical thickness at the stellar boundary, the translation of radial distance into exponential falloff, and the implementation details required to generate a real-time, performant coronal glow.

Mathematical Formulation of Radial Exponential Decay

Simulating a stellar corona in screen-space or on a billboard quad relies on approximating the line-of-sight optical depth through a spherically symmetric, thinning volume of gas. Because atmospheric and coronal density thins exponentially with altitude, the radiant intensity \(I(r)\) at a radial distance \(r\) from the star's center is modeled as:

\[I(r) = I_0 \cdot \exp(-k \cdot (r - R))\]

Where:

  • \(R\) is the normalized radius of the stellar body.
  • \(r\) is the Euclidean distance from the center of the star to the current fragment.
  • \(k\) is the decay constant controlling how rapidly the corona dissipates into space.
  • \(I_0\) is the peak intensity at the stellar limb (where \(r = R\)).

At the limb (\(r = R\)), the exponential power evaluates to zero, yielding full intensity \(I_0\). As \(r\) increases beyond the stellar surface, the value decays asymptotically toward zero.

GLSL Implementation

In a GLSL fragment shader, this evaluation is performed per-pixel by mapping screen or texture coordinates to a centered coordinate system, calculating the radial length, and evaluating the exponential equation without dynamic branching.

#version 330 core

in vec2 v_texCoords;
out vec4 fragColor;

uniform vec2 u_resolution;
uniform vec3 u_coronaColor; // e.g., vec3(1.0, 0.7, 0.3)
uniform float u_radius;     // Normalized star radius (e.g., 0.25)
uniform float u_decay;      // Decay rate k (e.g., 12.0)
uniform float u_intensity;  // Peak intensity multiplier

void main() {
    // Normalize coordinates to [-1.0, 1.0] and correct aspect ratio
    vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution.xy) / min(u_resolution.x, u_resolution.y);

    // Compute radial distance from star center
    float dist = length(uv);

    // Calculate distance relative to the limb
    float delta = max(0.0, dist - u_radius);

    // Evaluate radial exponential decay
    float coronaIntensity = exp(-u_decay * delta) * u_intensity;

    // Mask the interior of the stellar disk
    float diskMask = step(u_radius, dist);
    coronaIntensity *= diskMask;

    // Output final color
    fragColor = vec4(u_coronaColor * coronaIntensity, coronaIntensity);
}

Enhancing Limb Edge Definition

A pure exponential decay starting at the surface can sometimes produce a harsh geometric boundary at \(r = R\). In physical systems, limb brightening occurs because the optical path through the gas is longest along the line tangent to the sphere's surface.

To achieve a smoother, physically plausible limb transition:

  1. Power Adjustments: Applying a secondary power function to the decay variable, such as \(\exp(-k \cdot (r - R)^\alpha)\) where \(\alpha < 1.0\), steepens the falloff close to the edge while extending the long-distance tail of the corona.
  2. Integrated Core Transition: Replace the hard step(u_radius, dist) with a smooth inner-limb falloff to simulate the transition between the opaque photosphere and the semi-transparent corona:
float edgeSoftness = 0.005;
float limbFactor = smoothstep(u_radius - edgeSoftness, u_radius, dist);
float finalCorona = exp(-u_decay * max(0.0, dist - u_radius)) * limbFactor;
  1. High Dynamic Range (HDR) Tone Mapping: Exponential functions scale rapidly. Outputting these values directly into an unsigned normalized buffer can cause clipping artifacts at the limb. The result should be passed to a tone mapper (such as Reinhard or ACES) to preserve color saturation and avoid white-out banding near the stellar border.