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 |
|
you want the stock spiral with colour, speed and zoom controls |
Scripted WebGL |
|
you want to write or paste your own shader |
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 |
|
|
|
Background Color |
|
|
|
Spin Speed |
1 |
|
|
Throb Speed |
2 |
|
|
Throb Strength |
1 |
|
|
Zoom |
1 |
|
|
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 throughgl_FragColor. Do not add a#versionline and do not usein/out. Declare a precision, normallyprecision 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 |
|---|---|---|
|
|
seconds since the spiral started |
|
|
canvas size in pixels |
|
|
same as |
|
|
the Spiral Color setting |
|
|
the Background Color setting |
|
|
the Spin Speed setting |
|
|
the Throb Speed setting |
|
|
the Throb Strength setting |
|
|
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.yas your divisor so the picture is the same on portrait phones and wide monitors.pow(sin(x) + 1.0, N)with a largeNis 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-holderselector targets the canvas if you need to blend or transform it further. See Custom CSS.