# 3d Sky Rays

> Add sun shafts and crepuscular rays to a Three.js or equivalent 3D scene, using scene occlusion, a projected sun position, controlled foreground spill, and scalable post-processing. Use for rays of light from the sky, sunlight through trees or roofs, and golden-hour architectural scenes.

- Skill: `mengto/3d-sky-rays` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add mengto/3d-sky-rays`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mengto/3d-sky-rays/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Meng To (https://skillmd.com/u/mengto)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mengto/3d-sky-rays

---


# 3D Sky Rays

Make the light appear to travel through gaps in the scene. Roof edges and foliage should shape the shafts as the camera moves.

## Fit the existing renderer

Inspect the renderer version, render targets, alpha usage, depth path, tone mapping, and post-processing order. Keep the existing WebGL or WebGPU backend. The shader recipe below describes a WebGL screen-space effect; implement the equivalent in the project's node/render pipeline when using WebGPU.

Use one normalized world-space direction pointing **toward the sun** for the sky, ray source, and directional lighting. If the art direction deliberately offsets the visible sun from the lighting sun, document and control that offset in one place.

## Build an occlusion-aware ray pass

1. Render scene color in linear HDR. Obtain a mask identifying open sky: white for sky, black for opaque occluders. Match foliage alpha cutouts and vertex motion in the mask pass so leaves do not become solid rectangles.
2. A dedicated sky mask is the portable default. Reusing scene alpha is an optimization only when every preceding pass preserves its meaning: sky alpha 0, opaque geometry alpha 1. Transparent glass, particles, canvas compositing, and AO can break that convention; inspect the mask directly.
3. Project a point along the sun direction from the camera. Reject a source behind the camera before using projected coordinates. Convert NDC to UV with `uv = ndc.xy * 0.5 + 0.5`; account for the pipeline's texture orientation.
4. For each ray-buffer pixel, march toward the sun UV. Sample sky visibility multiplied by sky brightness above an HDR threshold. Accumulate decaying samples and divide by the sum of weights so sample-count changes do not change exposure.
5. Reject UVs outside the mask texture. Fade the effect as the sun moves far off-screen; clamping samples to the edge creates bright bands. A small overscan margin is useful for a sun just outside the frame.
6. Composite the ray color additively into linear scene color before the final tone-map/output transform. Preserve the input alpha if later passes need it.

Core accumulation, expressed as pseudocode:

```text
step = (pixelUV - sunUV) * density / sampleCount
sampleUV = pixelUV - step * jitter
sum = 0; weightSum = 0; weight = 1
repeat sampleCount times:
    sampleUV -= step
    if sampleUV is inside the texture:
        energy = max(luminance(scene(sampleUV)) - threshold, 0)
        sum += min(energy, energyCap) * skyMask(sampleUV) * weight
    weightSum += weight
    weight *= decay
rays = sum / max(weightSum, epsilon)
```

Use aspect-correct distance from the source for radial falloff. Keep any angular variation broad and subtle; the scene occlusion should provide most of the structure. On opaque foreground surfaces, attenuate the screen-space overlay strongly so wood and stone retain contrast. This is an artistic approximation, not a substitute for depth-integrated volumetric scattering.

## Tune appearance before cost

Start with a low sun, warm neutral tint, visible gaps, and restrained intensity. View from behind a roofline and through a canopy. Whiteout usually means excessive intensity or a bad mask, not insufficient bloom.

Use roughly 24–48 samples and a half-resolution ray buffer as a starting experiment; increase only when visible banding warrants it. Seijaku's reference uses 72 samples, which is a reference choice rather than a required quality floor. Smooth noise jitter can reduce bands, but animated jitter can shimmer; use stable noise for still or reduced-motion views unless temporal accumulation is available.

Skip the pass when its contribution is zero. Resize its buffers with the drawing buffer, dispose replaced targets, and measure its incremental GPU/frame cost with rays enabled and disabled. Avoid synchronous GPU readback in the render loop.

## Verify

- Roofs and leaf silhouettes visibly interrupt shafts; no rectangular foliage halos.
- Turning away from the sun removes the effect without a mirrored source or flash.
- Entering an interior preserves surface contrast; unrelated emissive objects do not become suns.
- Camera orbit, portrait resize, dusk, and a static reduced-motion frame remain stable.
- Desktop and mobile checks include shader errors, frame time, and the actual ray-buffer size.

## Reference

Read [REFERENCES.md](REFERENCES.md) for the source and renderer documentation. In Seijaku, inspect `GodRayShader`, `makePost`, and the projected-source update. Its alpha-mask convention depends on its complete render pipeline; do not transplant that assumption alone.

