god-rays@0.1.0

God rays

Volumetric light shafts from the frame's top-right corner, no march required: noise sampled on the unit direction from the light is constant along every ray, which is exactly a shaft pattern. Two counter-rotating layers shimmer against each other while radial decay fades the beams out.

Controls

Light color
#A3A3A3
Sky color
#000000
Ray detail3.1
Decay0.60
Sharpness2.5
Exposure1.10
Speed1.40

Install

One command copies the composition into your project (with the @vshaders namespace registered in components.json; the plain URL https://vshaders.com/r/god-rays.json works with no setup). npm dependencies install alongside.

npx shadcn@latest add @vshaders/god-rays

Files that land in your tree: shaders/god-rays.wgsl, components/god-rays.tsx, lib/run-effect.ts, lib/shared-gpu.ts, wgsl-env.d.ts.

The shader imports WGSL modules from npm, resolved by vgpu's loader. In next.config.ts, wire @vgpu/wgsl/loader-webpack for *.wgsl under both turbopack.rules and the webpack() hook, and keep the installed wgsl-env.d.ts at your project root so .wgsl imports typecheck. Loader setup: https://vgpu.sh/docs. Effect gallery and sources: https://vshaders.com. Color params are linear-light RGB, not sRGB hex/255: convert hex through the sRGB transfer function, or use Copy params in the live editor (https://vshaders.com/effects/god-rays) for exact values.

Imports

What this entry shader composes; every module resolves from npm.

import { fbmSimplex2d } from "@vgpu/wgsl-std/noise/simplex";

import { smootherstep } from "@vshaders/ease/shape";

import { mixOklab } from "@vshaders/color/oklab";

import { rotate2d, safeNormalize2 } from "@vgpu/wgsl-std/math";

import { linearToSrgb3, tonemapAces } from "@vgpu/wgsl-std/color";

Source

The full entry shader, god-rays.wgsl. Copy it and it is yours.

// effect 006 · god-rays — volumetric light shafts from the top-right corner.
// Screen-space rays without a march: fBM sampled on the *unit direction* from
// the light is constant along every ray and varies across them, which is
// exactly a shaft pattern. Two counter-rotating layers shimmer against each
// other, radial decay fades the shafts out, and a tight Gaussian core plays
// the sun. The constant-along-ray-noise idea is standard folklore for cheap
// godrays; this implementation is original.
// Works in linear light (ACES tonemapped); encodes to sRGB once at the end.

import { fbmSimplex2d } from "@vgpu/wgsl-std/noise/simplex";
import { smootherstep } from "@vshaders/ease/shape";
import { mixOklab } from "@vshaders/color/oklab";
import { rotate2d, safeNormalize2 } from "@vgpu/wgsl-std/math";
import { linearToSrgb3, tonemapAces } from "@vgpu/wgsl-std/color";

// Tunable members follow resolution/time. Defaults live in lib/effects.ts and
// must be set by the runner: an unset uniform member reads as zero. Members
// are ordered so each vec3f lands on a 16-byte boundary with an f32 in its pad.
struct Uniforms {
  resolution: vec2f,
  time: f32,
  // Angular detail: how many shaft clusters fit around the light.
  spread: f32,
  // Core light color, linear light.
  colorA: vec3f,
  // Radial falloff rate: higher pulls the shafts in tighter.
  decay: f32,
  // Ambient sky color, linear light.
  colorB: vec3f,
  // Overall brightness before tonemapping.
  exposure: f32,
  // Multiplies time in the shimmer.
  speed: f32,
  // Shaft contrast: higher carves fewer, harder beams.
  sharpness: f32,
}

@group(0) @binding(0) var<uniform> uniforms: Uniforms;

@fragment
fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
  let res = max(uniforms.resolution, vec2f(1.0));
  let uv = position.xy / res;
  let aspect = res.x / res.y;
  let p = (uv - vec2f(0.5)) * vec2f(aspect, 1.0);
  // The light sits at the frame's top-right corner: rays sweep the canvas
  // diagonally, the source itself stays implied at the edge.
  let lightField = vec2f(aspect * 0.5, -0.5);
  let t = uniforms.time * 0.1 * uniforms.speed;

  let offset = p - lightField;
  let dist = length(offset);
  let dir = safeNormalize2(offset, vec2f(0.0, 1.0));
  let spread = max(uniforms.spread, 0.1);

  // Shafts: noise over direction (constant along a ray), two layers rotating
  // against each other. The small dist terms bend the beams just enough to
  // kill the perfect-starburst look.
  let s1 = fbmSimplex2d(rotate2d(dir, t * 0.5) * spread + dist * 0.3, 4, 2.0, 0.5);
  let s2 = fbmSimplex2d(rotate2d(dir, -t * 0.35) * spread * 1.9 + vec2f(3.7, 1.9) + dist * 0.2, 3, 2.0, 0.5);
  var shaft = smootherstep(-0.35, 0.95, s1 * 0.65 + s2 * 0.35);
  shaft = pow(shaft, max(uniforms.sharpness, 0.1));

  // Radial terms: exponential decay for the shafts and a wide, faint halo
  // that keeps the light's neighbourhood lit between beams. No sun disc:
  // the source stays implied, only its rays are drawn.
  let decay = max(uniforms.decay, 1e-3);
  let radial = exp(-dist * decay);
  let halo = exp(-dist * dist * 2.2);

  let rays = shaft * radial;
  // Hue drifts from the light color to the sky color as energy falls off.
  let beamColor = mixOklab(uniforms.colorB, uniforms.colorA, clamp(radial, 0.0, 1.0));
  var light = beamColor * (rays * 1.5 + halo * 0.15)
    + uniforms.colorB * 0.05;
  light *= max(uniforms.exposure, 0.0);
  var color = tonemapAces(light);

  // Sink the edges into the page ink.
  let vignette = smoothstep(1.35, 0.5, length(p));
  // The edge sinks into a deep shade of the sky itself, so the effect sits
  // on any page without a foreign ink at its corners.
  color = mixOklab(uniforms.colorB * 0.15, color, 0.25 + 0.75 * vignette);
  return vec4f(linearToSrgb3(clamp(color, vec3f(0.0), vec3f(1.0))), 1.0);
}