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
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-raysFiles 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);
}