Astigmatism

Why an off-axis point can focus at two different image distances — and how a field-oriented line spread reproduces the resulting directional blur.

Visible effect

Peripheral points stretch along a field-oriented line

Near the optical axis, a well-corrected lens can bring both principal ray fans to nearly the same image point. Farther into the field, those fans may reach their narrowest focus at different image distances. A flat sensor intersects the evolving point-spread function at one fixed plane, so peripheral detail becomes a short radial or tangential line instead of a point.

The direction changes around the image centre: the same aberration that makes a horizontal line spread at one edge can make a vertical or diagonal spread elsewhere. That field-oriented behaviour distinguishes astigmatism from a uniform directional motion blur.

Physics

Two principal ray fans reach their narrowest focus separately

Off-axis ray fans reach separate focal surfaces An off-axis scene point enters a lens. Tangential and sagittal ray fans converge at different image distances, so a sensor between them records an oriented blur footprint rather than one point.off-axis pointlensseparate focisensor footprinttangential fansagittal fan
The two principal ray fans do not share one image distance. Moving the sensor through those foci changes the recorded footprint from one line orientation, through an ellipse or circle of least confusion, to the orthogonal line orientation.

For an off-axis object point, rays in the tangential plane and rays in the sagittal plane encounter different effective optical power. Their best-focus locations split into two curved focal surfaces. A flat sensor intersects one evolving three-dimensional bundle, not two independent images.

Mathematics

Blur grows with field height and follows a local basis

Third-order astigmatic focal separation grows approximately with squared image height h. The bounded real-time model uses normalized field radius ρ, an adjustable growth powerp, and either the radial unit vectoreᵣ or its perpendicular tangential vectoreₜ:

The approximation deliberately exposes one sensor-plane slice at a time. A physical through-focus model would evolve continuously between the two orthogonal line foci and the circle of least confusion.

Shader

GLSL field-oriented line gather

Each fragment builds an aspect-correct radial/tangential basis around the optical centre, grows its kernel toward the frame edge, and gathers a normalized Gaussian-weighted line in linear light.

astigmatismLineGLSL
// WHAT: Turn an off-axis point into a radial or tangential line blur.
// HOW: Build a field-relative direction, grow the radius toward the frame edge,
// then gather Gaussian-weighted samples along that local line in linear light.
// WHY: Astigmatism separates sagittal and tangential focus; a directional
// footprint communicates that structure more faithfully than an isotropic blur.
const int ASTIGMATISM_MAX_SAMPLES = 49;

float srgbToLinearChannel(float value) {
  if (value <= 0.04045) return value / 12.92;
  return pow((value + 0.055) / 1.055, 2.4);
}

vec3 srgbToLinear(vec3 value) {
  return vec3(
    srgbToLinearChannel(value.r),
    srgbToLinearChannel(value.g),
    srgbToLinearChannel(value.b)
  );
}

vec2 astigmatismDirection(
  vec2 sourceUv,
  vec2 opticalCenter,
  float sourceAspect,
  bool tangential
) {
  vec2 field = sourceUv - opticalCenter;
  // Correct x before measuring direction so the field is isotropic on screen.
  vec2 isotropicField = vec2(field.x * sourceAspect, field.y);
  float fieldLength = length(isotropicField);
  vec2 radialIso = fieldLength > 0.000001
    ? isotropicField / fieldLength
    : vec2(1.0, 0.0);
  vec2 radialUv = normalize(vec2(
    radialIso.x / max(sourceAspect, 0.000001),
    radialIso.y
  ));
  return tangential ? vec2(-radialUv.y, radialUv.x) : radialUv;
}

vec3 astigmatismLine(
  sampler2D source,
  vec2 sourceUv,
  vec2 sampleStep,
  vec2 opticalCenter,
  float sourceAspect,
  float maxBlurPx,
  float fieldPower,
  bool tangential,
  int sampleCount
) {
  vec2 field = sourceUv - opticalCenter;
  float fieldRadius = clamp(
    length(vec2(field.x * sourceAspect, field.y)) * 2.0,
    0.0,
    1.0
  );
  // fieldPower controls how quickly the aberration appears away from centre.
  float blurRadius = maxBlurPx * pow(fieldRadius, max(fieldPower, 0.1));
  vec2 direction = astigmatismDirection(
    sourceUv,
    opticalCenter,
    sourceAspect,
    tangential
  );
  float safeCount = float(max(sampleCount, 1));
  // Mip prefiltering stabilizes a wide gather when the tap budget stays fixed.
  float lod = max(0.0, log2(max(blurRadius, 1.0)) - 2.2);
  vec3 accumulated = vec3(0.0);
  float totalWeight = 0.0;

  for (int index = 0; index < ASTIGMATISM_MAX_SAMPLES; index += 1) {
    if (index >= sampleCount) break;
    float t = sampleCount <= 1
      ? 0.0
      : float(index) / max(safeCount - 1.0, 1.0) * 2.0 - 1.0;
    // Gaussian weights keep the line soft rather than ending as a hard streak.
    float weight = exp(-3.2 * t * t);
    vec2 offset = direction * sampleStep * blurRadius * t;
    accumulated += srgbToLinear(
      textureLod(source, sourceUv + offset, lod).rgb
    ) * weight;
    totalWeight += weight;
  }

  // Weight normalization preserves brightness as radius and sample count vary.
  return accumulated / max(totalWeight, 0.000001);
}
Processing pipelineBoxes mark actual render-pass boundaries.
  1. Source texturesRGB
  2. Directional Gaussian gatherOne fullscreen render pass
    • Build the aspect-correct field basis
    • Calculate the local line radius
    • Gather Gaussian-weighted samples in linear light
    • Mix the effect
    • Encode to sRGB
  3. Display output

Why these steps are here

  1. Correct the field metric. Multiplying horizontal UV distance by the source aspect ratio keeps radial directions geometrically circular rather than screen-stretched.
  2. Build a local basis. The blur direction rotates around the optical centre instead of remaining fixed across the frame.
  3. Grow from the centre. A field power leaves axial detail sharp and concentrates the evidence near the edges.
  4. Normalize the gather. Dividing by total Gaussian weight avoids a brightness change as the kernel grows.
  5. Bound the pass. Forty-nine taps cap the direct gather while mip sampling suppresses sparse-kernel stepping.

A production optical renderer can replace this slice with a depth-aware two-dimensional PSF atlas.

Notes

  • The procedural grid is the default because it makes the sharp centre, rotating peripheral direction, and edge growth measurable.
  • Real lenses combine astigmatism with field curvature, defocus, coma, distortion, and sensor-plane tilt; this playground isolates only the oriented line-spread cue.
  • The sign or orientation of the observed blur depends on the sensor plane relative to both astigmatic focal surfaces.
  • The model has no scene depth, pupil coordinate, wavelength, or lens prescription, so its pixel radius is an artistic control rather than a calibrated optical measurement.

References

Nikon MicroscopyU — Astigmatism — explains sagittal and tangential focal surfaces and the resulting line-like point images.

Edmund Optics — How aberrations affect imaging lenses — manufacturer reference for spherical, astigmatic, field-curvature, and chromatic aberrations.