35
←→

Breakdown of Eidolon 1

Eidolon 1 shader breakdown: curled UV rotations, smoothed boxes, cosine palette color from sphere distance, triple masking, simple mixing, and a lovely film grain texture.

Loading...
←Fragments Season 2 Launch
←

Ready to start learning?

What's included in the course ↓

Access to the course: master shader techniques, use workflow-enhancing utilities, learn from shader breakdowns with full code, and get downloadable R3F and vanilla projects.

One single payment. No subscription required. 30-day money-back guarantee. No questions asked.

Loading...

Fragments

Learn creative coding with shaders. For design engineers, creative coders and shader artists: techniques, tools, deep dives. Powered by ThreeJS and TSL.

Loading...

2025 Phobon

phobon.ioShadercraft

Pages

HomeTechniquesUtilitiesBreakdownsWorksWriting

Contact

X @thenoumenonhey@fragments.supplyOKAY DEV @phobon
All rights reserved.
Works171
Writing35

One of my favourite shaders

This breakdown walks through Eidolon 1, one of my favourite pieces in the Eidolon series. It shows how layering simple geometry, motion, and colour can turn into something that feels bigger than the sum of its parts.

Eidolon 1 main still

Inspiration

I'm a sucker for chromatic aberration — the way RGB channels peel apart and warp the image still hits for me, even though the "trick" is basically everywhere now.

These shaders started as experiments with literal chromatic aberration. They slid sideways into something quieter. Creating more ethereal, abstract planes and a glow that keeps the mood of that channel split.

Breakdown

The structure is straightforward: layered shapes, warped UVs, simple palette-driven colour, and motion pushed by a sine wave. Colour on each layer comes from a cosine palette.

Here's the shader in full:

import { abs, Fn, mix, oneMinus, rotate, screenSize, sin, smoothstep, time, uv, vec3 } from 'three/tsl'
import { grainTexturePattern } from '@/components/tsl/patterns/grain_texture_pattern'
import { cosinePalette } from '@/tsl/utils/color/cosine_palette'
import { screenAspectUV } from '@/tsl/utils/function'
import { sdBox2d, sdSphere } from '@/tsl/utils/sdf/shapes'
 
export const eidolon1 = Fn(() => {
  const _uv = screenAspectUV(screenSize).toVar()
  const _vuv = uv().toVar()
 
  const finalColor = vec3(0.0).toVar()
 
  const rotationScalar = sin(_vuv.x.mul(Math.PI)).toVar()
 
  const uvR = rotate(_uv, rotationScalar.add(time))
  const uvR2 = rotate(_uv, rotationScalar.add(time.mul(0.5)))
  const uvR3 = rotate(_uv, rotationScalar.add(time.mul(0.75)))
 
  const rotateGeometry = Fn(([_st, edge1 = 0.7, edge2 = 0.8]) => {
    const shape = oneMinus(sdBox2d(abs(_st))).toVar()
    shape.assign(smoothstep(edge1, edge2, shape))
    return shape
  })
 
  const rotatedGeometry1 = rotateGeometry(uvR2)
  const rotatedGeometry2 = rotateGeometry(uvR)
  const rotatedGeometry3 = rotateGeometry(uvR3)
 
  const colorCalc = Fn(([_st, timeScalar]) => {
    const a = vec3(0.5, 0.5, 0.5)
    const b = vec3(0.5, 0.5, 0.5)
    const c = vec3(2.0, 1.0, 0.0)
    const d = vec3(0.5, 0.2, 0.25)
    const col = cosinePalette(sdSphere(_st.mul(0.5)).add(time.mul(timeScalar)), a, b, c, d).toVar()
    return col
  })
 
  const col1 = colorCalc(_uv, 0.25)
  const col2 = colorCalc(_uv, 0.27)
  const col3 = colorCalc(_uv, 0.28)
 
  col1.mulAssign(rotatedGeometry1)
  col2.mulAssign(rotatedGeometry2)
  col3.mulAssign(rotatedGeometry3)
 
  const mixedColorStep = mix(col1, col2, 0.5)
  finalColor.assign(mix(mixedColorStep, col3, 0.5))
 
  const grainEffect = grainTexturePattern(_vuv).mul(0.2).toVar()
  finalColor.addAssign(grainEffect)
 
  return finalColor
})

A few Fragments utilities hold it together:

  • screenAspectUV keeps geometry from stretching when the viewport isn't square
  • rotate applies a 2D rotation in shader space—more “spin the sampling grid” than rotating a JPEG
  • cosinePalette is the usual cosine ramp from Inigo Quilez

Curling UVs before the spin

_vuv.x feeds a sine that becomes rotationScalar. Curling UVs along one axis keeps the swirl from feeling like a tidy pinwheel—you get gentle bias across the horizontal field.

The three rotate calls share that curl but offset time by 1.0, 0.5, and 0.75, so the layers drift apart without bolting on three unrelated systems.

const _uv = screenAspectUV(screenSize).toVar()
const _vuv = uv().toVar()
 
const rotationScalar = sin(_vuv.x.mul(Math.PI)).toVar()
 
const uvR = rotate(_uv, rotationScalar.add(time))
const uvR2 = rotate(_uv, rotationScalar.add(time.mul(0.5)))
const uvR3 = rotate(_uv, rotationScalar.add(time.mul(0.75)))

Soft boxes instead of razor edges

sdBox2d makes a rectangular distance field. Absolute value folds it into symmetrical shards. smoothstep(edge1, edge2, …) smears the cutoff so blobs touch without harsh aliasing.

How hard those edges read is mostly taste. I keep them soft, though you can narrow the smoothstep band if you want a harder cut.

const rotateGeometry = Fn(([_st, edge1 = 0.7, edge2 = 0.8]) => {
  const shape = oneMinus(sdBox2d(abs(_st))).toVar()
  shape.assign(smoothstep(edge1, edge2, shape))
  return shape
})
 
const rotatedGeometry1 = rotateGeometry(uvR2)
const rotatedGeometry2 = rotateGeometry(uvR)
const rotatedGeometry3 = rotateGeometry(uvR3)

Palette colour keyed to sphere distance

colorCalc is doing most of the mood work. Same cosine palette scaffolding for each lane, with timeScalar bumped in tiny steps (0.25, 0.27, 0.28—barely perceptible gaps). Those slivers of difference show up once each layer is masked and blended.

Driving the cosine input from sdSphere(_st.mul(0.5)).add(time.mul(timeScalar)) ties the ramp to spherical distance—you don't read it as literal spheres, but the field stays smooth and luminous.

const colorCalc = Fn(([_st, timeScalar]) => {
  const a = vec3(0.5, 0.5, 0.5)
  const b = vec3(0.5, 0.5, 0.5)
  const c = vec3(2.0, 1.0, 0.0)
  const d = vec3(0.5, 0.2, 0.25)
  const col = cosinePalette(sdSphere(_st.mul(0.5)).add(time.mul(timeScalar)), a, b, c, d).toVar()
  return col
})
 
const col1 = colorCalc(_uv, 0.25)
const col2 = colorCalc(_uv, 0.27)
const col3 = colorCalc(_uv, 0.28)

Masking and grain

Per-layer multiply is blunt and predictable: when a mask collapses toward zero, that layer's colour goes dark. Stacks stay readable even when the geometry piles up.

Nested mix calls at fixed 0.5 read like translucent gels—calmer than stacking additive blends. Snap comes from palette tweaks and grain, not flashy blend gymnastics.

col1.mulAssign(rotatedGeometry1)
col2.mulAssign(rotatedGeometry2)
col3.mulAssign(rotatedGeometry3)
 
const mixedColorStep = mix(col1, col2, 0.5)
finalColor.assign(mix(mixedColorStep, col3, 0.5))

Grain knocks down banding and hides spots where cosine ramps want to jitter. Scaling it by 0.2 keeps the texture from chewing through contrast.

const grainEffect = grainTexturePattern(_vuv).mul(0.2).toVar()
finalColor.addAssign(grainEffect)

Variations

You can treat each piece as LEGO and swap freely. Starting points:

  • Tune the three time ratios if you want more “ribbon” drift or quieter motion
  • Nudge cosine palette knobs c/d for mood jumps without rewiring masking
  • Try a sharper smoothstep window for crisper slabs
  • Try some different shapes for the boxes, such as diamonds or triangles
  • Fractionate or repeat the sampling domain if you want a busier tiling read
Eidolon 2Eidolon 3Eidolon 4Eidolon 5
More from the Eidolon series—swap in your own stills whenever you refresh the set

Fragments is open until June 1

Fragments is open until June 1. If shaders and creative coding are on your list, it's worth a look.

FundamentalsJoin 207+ developers learning shader techniquesAccess to foundational shader techniques and utilities
  • ✓ 5 foundational long-form technique lessons
  • ✓ 6 fundamentals lessons
  • ✓ 24 foundational workflow enhancing utilities
  • ✓ 71 full shader breakdowns
  • ✓ Downloadable R3F and Vanilla projects
  • ✓ Access to community Discord
→$99$89USD
ProJoin 207+ developers learning shader techniquesFull access to the entire Fragments collection. Includes all techniques, utilities, breakdowns and all future updates
  • ✓ 12 long-form technique lessons
  • ✓ 6 fundamentals lessons
  • ✓ 44 workflow enhancing utilities
  • ✓ 171+ full shader breakdowns
  • ✓ Downloadable R3F and Vanilla projects
  • ✓ Access to community Discord
→$199$179USD
You'll be redirected to our secured payment platform and get instant access.

Be the first to know what's next