GLSL spirals (Scripted WebGL)

The Scripted WebGL spiral lets you replace Hypna’s built-in spiral with your own fragment shader. The player draws your shader on a full-screen quad behind the session text and feeds it the same colour, speed and zoom settings the built-in spiral uses, so the ordinary colour pickers keep working and YSS can animate any value while the session runs.

This page assumes you know a little GLSL. If you have never written a shader, start from one of the presets and change one number at a time; the live preview shows the result immediately.

Choosing the spiral type

Every Mantra or Brainwash Spiral Task has exactly one spiral feature. On the session editor’s Spiral tab the available types are:

Type

HypnoSpec feature

Use it when

WebGL

space.hypna.feature.spirals.webgl

you want the stock spiral with colour, speed and zoom controls

Scripted WebGL

space.hypna.feature.spirals.customgl

you want to write or paste your own shader

Media

space.hypna.feature.spirals.media

you want a GIF, image or video as the spiral

Switch to Scripted WebGL and the tab shows the standard settings, a row of Presets chips, a live preview, and a code editor with GLSL highlighting.

The settings feed your uniforms

Both WebGL spiral types share these settings. In the scripted spiral each one is delivered to your shader as a uniform.

Setting

Default

Uniform

GLSL type

Opacity

0.6

none, applied as CSS opacity on the canvas

Spiral Color

#FFFFFF

spiralColor

vec3, 0.0 to 1.0 rgb

Background Color

#000000

bgColor

vec3, 0.0 to 1.0 rgb

Spin Speed

1

spinSpeed

float

Throb Speed

2

throbSpeed

float

Throb Strength

1

throbStrength

float

Zoom

1

zoom

float

The speeds are plain multipliers. What they do is entirely up to your shader; the names only describe what the stock spiral uses them for.

The shader contract

  • WebGL 1, GLSL ES 1.00. Write void main() and output through gl_FragColor. Do not add a #version line and do not use in/out. Declare a precision, normally precision highp float;.

  • Fragment shader only. The vertex shader is fixed and just passes through a full-screen quad. There are no varyings. Read the pixel position from gl_FragCoord.xy.

  • Declare only the uniforms you use. A uniform you declare but the player does not know about is simply left at zero unless you drive it from YSS. A player uniform you do not declare is skipped.

Uniforms the player provides:

Uniform

Type

Meaning

iTime

float

seconds since the spiral started

iRes

vec2

canvas size in pixels

u_resolution

vec2

same as iRes

spiralColor

vec3

the Spiral Color setting

bgColor

vec3

the Background Color setting

spinSpeed

float

the Spin Speed setting

throbSpeed

float

the Throb Speed setting

throbStrength

float

the Throb Strength setting

zoom

float

the Zoom setting

There is no mouse, frame counter or date uniform. In VR and XR the canvas is drawn once per eye, so keep your maths centred on u_resolution rather than assuming a fixed aspect ratio.

A common opening line that gives you centred, aspect-correct coordinates:

vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;

The stock shader

This is the shader the feature starts with. It is the same maths as the built-in WebGL spiral, so Classic Rings in the presets row restores it.

// Custom spiral fragment shader (WebGL 1 / GLSL ES 1.0).
// Uniforms provided by the player (declare only the ones you use):
//   float iTime          seconds since the spiral started
//   vec2  iRes           canvas resolution in pixels
//   vec2  u_resolution   same as iRes
//   vec3  spiralColor    the "Spiral Color" setting (0.0-1.0 rgb)
//   vec3  bgColor        the "Background Color" setting (0.0-1.0 rgb)
//   float spinSpeed, throbSpeed, throbStrength, zoom   the numeric settings
precision highp float;

#define PI 3.1415926538

uniform vec2 u_resolution;

uniform vec2 iRes;
uniform float iTime;

uniform vec3 spiralColor;
uniform vec3 bgColor;

uniform float spinSpeed;
uniform float throbSpeed;
uniform float throbStrength;
uniform float zoom;

void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    vec2 uv = (fragCoord - 0.5 * u_resolution) / u_resolution.y;
    vec2 truPos = uv;

    float angle = atan(truPos.y, truPos.x);
    float dist = pow(length(truPos), .4 + sin((iTime + cos(iTime * .05) * 0.1) * throbSpeed) * 0.2 * throbStrength);

    float spiFactor = pow(sin(dist * 40. * zoom - iTime * 5. * spinSpeed) + 1.0, 50.);
    spiFactor = clamp(spiFactor, 0., 1.);

    vec3 color = mix(spiralColor, bgColor, spiFactor);
    gl_FragColor = vec4(color, 1.0);
}

How it works: dist is the distance from the centre, warped by a slow throb. The sine of dist * 40 * zoom produces concentric rings; subtracting iTime * spinSpeed makes the rings travel inward. Raising the sine to the 50th power sharpens the rings into thin lines, and mix paints them in the two chosen colours.

Presets

The editor ships five starting points. Clicking a preset asks for confirmation and replaces the whole shader.

Classic Rings

The stock throbbing ring spiral above.

Spiral Arms

A three-armed logarithmic spiral that winds toward the centre.

Checker Tunnel

A checkerboard tunnel rushing toward the viewer.

Starburst

Rotating rays with a pulsing core.

Plasma Waves

Soft interfering waves, more ambient than hypnotic.

All four non-stock presets share this header, which you can copy for your own work:

precision highp float;

#define PI 3.1415926538

uniform vec2 u_resolution;
uniform float iTime;

uniform vec3 spiralColor;
uniform vec3 bgColor;

uniform float spinSpeed;
uniform float throbSpeed;
uniform float throbStrength;
uniform float zoom;

The Spiral Arms preset in full, as an example of a true spiral rather than rings:

void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
    float angle = atan(uv.y, uv.x);
    float dist = length(uv);

    float throb = 1.0 + sin(iTime * throbSpeed) * 0.15 * throbStrength;
    float v = sin(log(dist + 0.05) * 6.0 * zoom * throb + angle * 3.0 - iTime * 3.0 * spinSpeed);
    float m = smoothstep(-0.2, 0.2, v);

    gl_FragColor = vec4(mix(spiralColor, bgColor, m), 1.0);
}

angle * 3.0 gives three arms; change the 3 to change the arm count. log(dist) makes the arms logarithmic so they keep the same visual width from edge to centre.

Your own uniforms and YSS

Any extra uniform float, vec2, vec3 or vec4 you declare can be changed while the session runs from a YSS command that uses the uniform’s own name:

uniform float pulse;   // 0.0 .. 1.0
uniform vec3 tint;
[setting.spiral.pulse=1;fade=2000]
[setting.spiral.tint=1,0.5,0]

Vector values are written as comma- or space-separated numbers. Colours may also be given as #rrggbb. Add fade= to ease over that many milliseconds. Until YSS sets it, an author-declared uniform holds GLSL’s default of zero, so give it a sensible meaning at zero or set it on the first line of your script.

The standard settings can be driven the same way: [setting.spiral.zoom=2;fade=1500], [setting.spiral.spiral_color=#00ff00], [setting.spiral.opacity=0.4]. See Driving the spiral live.

Compile errors

The editor recompiles about a third of a second after you stop typing and shows Shader compile error or Shader link error with the driver’s message under the preview. Fix errors before saving.

If a saved shader fails to compile in the player, the subject sees a dark red box at the top of the screen reading Custom spiral shader failed to compile followed by the error, and no spiral. The player’s console panel offers Reset spiral to fallback, which swaps in the media spiral for that run only. The same message is written to the in-player console.

Tips

  • Keep u_resolution.y as your divisor so the picture is the same on portrait phones and wide monitors.

  • pow(sin(x) + 1.0, N) with a large N is the cheapest way to turn a smooth wave into crisp lines.

  • Very bright, fast, high-contrast patterns can be uncomfortable. Test at the Opacity you intend to ship, not at 1.0.

  • Shaders run on the subject’s device. Avoid loops with large trip counts; phones and headsets will drop frames.

  • The custom CSS #webgl-spiral-holder selector targets the canvas if you need to blend or transform it further. See Custom CSS.