Mini Lecture - GLSL Spiral Walk-through
How does GLSL work anyways?
Mini Lecture - GLSL Spiral Walk-through
More detailed documentation is coming soon. For now, enjoy the mini interactive GLSL shader below. Please edit, modify, and explore!
Resources
Live GLSL compiler: https://shawnlawson.github.io/The_Force/
GLSL Uniforms: https://thebookofshaders.com/03/
GLSL Functions: https://thebookofshaders.com/glossary/
Polar Coordinates: https://en.wikipedia.org/wiki/Polar_coordinate_system
Coord. Rotation Matrix: https://en.wikipedia.org/wiki/Rotation_matrix
Golden Spiral: https://en.wikipedia.org/wiki/Golden_spiral
GLSL Example: Interactive Spiral (Shadertoy Implementation)
Interact with the shader by clicking and dragging on the screen.
You can run and modify this code in real time by copying the code below and visiting The Force!
https://shawnlawson.github.io/The_Force/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Shader Inputs (on by default) //
// more info: https://thebookofshaders.com/03/
// uniform vec3 gl_FragCoord; // window coordinates of fragment shader (x,y,z)
// uniform vec2 resolution; // viewport resolution (in pixels)
// uniform float time; // shader playback time (in seconds)
// uniform vec4 mouse; // mouse X,Y coordinate, mouse click X,Y coordinate
// Defined Variables
float goldenRatio = 0.618;
float spiral_width = 20.;
float zoom_speed = 2.;
float num_spirals = 3.;
float decay = .99;
// Function: Generaqte Spiral
float spiralWave(vec2 uv_centered, float curve_ratio, float rate, float num_spirals) {
// Calculate polar coordinates of centered UV space (r, theta). https://en.wikipedia.org/wiki/Polar_coordinate_system
float r = length(uv_centered);
float theta = atan(uv_centered.x,uv_centered.y);
// Generate circle, and add theta to offset into spirals
float circles = r;
float circles_zoom = sin(r*1. + time); // sin(r*10 + time);
float spiral_zoom = sin(r*10. + theta*0.05 + time); // sin(r*10. + theta + time)
float n_spirals = sin(r*10. + theta * num_spirals + time);
// Modifying curve rate to be a golden ratio. https://en.wikipedia.org/wiki/Golden_spiral
float golden_ratio_spiral = log(r)/curve_ratio + theta;
float one_golden_ratio_spiral = sin( golden_ratio_spiral + time);
float n_golden_ratio_spiral = sin( golden_ratio_spiral * num_spirals + time);
return n_golden_ratio_spiral;
}
// Function: Rotation Matrix. https://en.wikipedia.org/wiki/Rotation_matrix
mat2 rotate(float angle) {
return mat2(
cos(angle), -sin(angle),
sin(angle), cos(angle)
);
}
void main () {
// Interactive inputs
float mouse_pos_x = mouse.x/resolution.x;
float mouse_pos_y = mouse.y/resolution.y;
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
vec2 uv = (gl_FragCoord.xy / resolution.xy);
// Generate spiral pattern
// Create new UV, centered at the screen center, for polar coordinates.
vec2 square_aspect_ratio = resolution.xy/resolution.x;
vec2 uv_centered = (2.*uv - 1.) * square_aspect_ratio;
// Create Spiral
float gray_spiral = (-50. + spiral_width) + 50. * spiralWave(uv_centered, goldenRatio, zoom_speed, num_spirals);
// Invert half of the spiral
float invert_rotate = 0.25*time; // - bands.x*1.
float gray_spiral_split = gray_spiral*sin( sin(invert_rotate)*uv_centered.y + cos(invert_rotate)*uv_centered.x);
// Invert half of the spiral again, but in the opposite direction
float gray_spiral_rotate = gray_spiral_split*(sin( cos(invert_rotate+0.5)*uv_centered.y + sin(invert_rotate+0.5)*uv_centered.x)*20.);
// Apply fract
float gray_spiral_fract = fract(gray_spiral_rotate/100.);
vec4 rbga_spiral_fract = vec4(vec3(gray_spiral_fract),1.0);
// FEEDBACK!
// Linear displacement
//vec2 uv_transform = uv + vec2(0.01, 0.000);
// Rotate coordinate system, about the center of the screen.
vec2 uv_transform = uv - vec2(0.5); // 2
uv_transform = uv_transform*rotate(0.05*mouse_pos_y); // 1 // uv_transform*rotate(0.05*mouse_pos_y)
uv_transform = uv_transform + vec2(0.5); // 3
vec4 buffer_frame = (texture2D(backbuffer, uv_transform) - vec4(0.0, 0.01, 0.05, 1.0)) * decay; // adding slight color shift per buffer
// average the previous frame with current frame
vec4 rbga_feedback = mix(buffer_frame, rbga_spiral_fract, 0.01);
// fun fract
vec4 rbga_feedback_fract = fract(max(fract(rbga_feedback*1.02), 1.-rbga_spiral_fract)*1.1);
// Interactive mode switcher
float slider = mouse_pos_x*4.;
vec4 frag_rgba_slider; // empty vec4
if(uv.x < 0.25*slider)
frag_rgba_slider = vec4(vec3(gray_spiral_rotate),1.0);
else if(uv.x < 0.5*slider)
frag_rgba_slider = rbga_spiral_fract;
else if(uv.x < 0.75*slider)
frag_rgba_slider = buffer_frame;
else
frag_rgba_slider = rbga_feedback_fract;
// Output to screen
//gl_FragColor = vec4(uv.x, gray_spiral_fract, uv.y, 1.);
//gl_FragColor = vec4(vec3(gray_spiral), 1.);
gl_FragColor = frag_rgba_slider;
}
// Shader Outputs //
// uniform vec4 gl_FragColor; // color of fragment shader [R, G, B, A]
// uniform vec4 backbuffer; // Stores gl_FragColor from previous frame
Code Walkthrough - Web version (Active Draft):
NOTE: I am actively building up this documentation. Please check back in a month or so for the complete version!
1) Blank Screen:
1
2
3
4
5
void main () {
// Output to screen
gl_FragColor = vec4(0., 0., 0., 0.);
}
Figure 1: Blank Screen
2) Colored Coordinates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Shader Inputs (on by default) //
// more info: https://thebookofshaders.com/03/
// uniform vec3 gl_FragCoord; // window coordinates of fragment shader (x,y,z)
// uniform vec2 resolution; // viewport resolution (in pixels)
// uniform float time; // shader playback time (in seconds)
// uniform vec4 mouse; // mouse X,Y coordinate, mouse click X,Y coordinate
void main () {
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
vec2 uv = (gl_FragCoord.xy / resolution.xy);
// Output to screen
gl_FragColor = vec4(uv.x, 0., uv.y, 1.);
}
// Shader Outputs //
// uniform vec4 gl_FragColor; // color of fragment shader [R, G, B, A]
// uniform vec4 backbuffer; // Stores gl_FragColor from previous frame
Figure 2: Colored Unit Coordinates (U,V)
3) Center the coordinate system
1
2
3
4
5
6
7
8
9
10
11
12
void main () {
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
vec2 uv = (gl_FragCoord.xy / resolution.xy);
// Create new UV, centered at the screen center, for polar coordinates.
vec2 square_aspect_ratio = resolution.xy/resolution.x;
vec2 uv_centered = (2.*uv - 1.) * square_aspect_ratio;
// Output to screen
gl_FragColor = vec4(uv.x, 0., uv.y, 1.);
}
Figure 3: Centered Unit Coordinate System
4) Create fuction to generate a spiral
Explain polar coordinates.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Function: Generate Spiral
float spiralWave(vec2 uv_centered, float curve_ratio, float rate, float num_spirals) {
// Calculate polar coordinates of centered UV space (r, theta)
float r = length(uv_centered);
float theta = atan(uv_centered.x,uv_centered.y);
// Generate circle, and add theta to offset into spirals
float circles = r;
float circles_zoom = sin(r*1. + time); // sin(r*10 + time);
float spiral_zoom = sin(r*10. + theta*0.05 + time); // sin(r*10. + theta + time)
float n_spirals = sin(r*10. + theta * num_spirals + time);
return n_spirals;
}
// use the fuction in our main output
void main () {
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
vec2 uv = (gl_FragCoord.xy / resolution.xy);
// Create new UV, centered at the screen center, for polar coordinates.
vec2 square_aspect_ratio = resolution.xy/resolution.x;
vec2 uv_centered = (2.*uv - 1.) * square_aspect_ratio;
// Create sin spiral
float gray_spiral = spiralWave(uv_centered, goldenRatio, zoom_speed, num_spirals);
// Output to screen
gl_FragColor = vec4(vec3(gray_spiral), 1.);
}
5) Make spiral aesthetic with the Golden Ratio
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Function: Generate Golden Spiral
float spiralWave(vec2 uv_centered, float curve_ratio, float rate, float num_spirals) {
// Calculate polar coordinates of centered UV space (r, theta)
float r = length(uv_centered);
float theta = atan(uv_centered.x,uv_centered.y);
// Modifying curve rate to be a golden ratio
float golden_ratio_spiral = log(r)/curve_ratio + theta;
float one_golden_ratio_spiral = sin( golden_ratio_spiral + time);
float n_golden_ratio_spiral = sin( golden_ratio_spiral * num_spirals + time);
return n_golden_ratio_spiral;
}
// use the fuction in our main output
void main () {
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
// Create new UV, centered at the screen center, for polar coordinates.
...
// Create sin spiral
float gray_spiral = spiralWave(uv_centered, goldenRatio, zoom_speed, num_spirals);
// Output to screen
gl_FragColor = vec4(vec3(gray_spiral), 1.);
}
6) Make spiral pattern more interesting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main () {
// Normalized pixel coordinates to unit vectors: x,y -> u,v (from 0 to 1)
// Create new UV, centered at the screen center, for polar coordinates.
...
// Create sin spiral
float gray_spiral = spiralWave(uv_centered, goldenRatio, zoom_speed, num_spirals);
// Over saturate values beyond 0:1 for clipping / threshold effect.
float gray_spiral_clipped = (-50. + spiral_width) + 50. * gray_spiral;
// Invert half of the spiral
float invert_rotate = 0.25*time;
float gray_spiral_split = gray_spiral*sin( sin(invert_rotate)*uv_centered.y + cos(invert_rotate)*uv_centered.x);
// Invert half of the spiral again, but in the opposite direction
float gray_spiral_rotate = gray_spiral_split*(sin( cos(invert_rotate+0.5)*uv_centered.y + sin(invert_rotate+0.5)*uv_centered.x)*20.);
// Apply fract
float gray_spiral_fract = fract(gray_spiral_rotate/100.);
vec4 rbga_spiral_fract = vec4(vec3(gray_spiral_fract),1.0);
// Output to screen
gl_FragColor = rbga_spiral_fract;
}
Over saturate values beyond 0:1 for clipping / threshold effect.
Invert half of the spiral, twice.
Take the fractional value of the oversaturated sin to see contours of values that were above 1 (or 255), using the “fract()” effect. More info on fract() at The Book of Shaders
7) Video Feedback - Frame Linear Translation
(backbuffer) …
8) Video Feedback - Frame Linear Rotation about center
(rotation matrix) …
9) Add final complexity with fract() effect, and adding Mouse interactivity
(embed)
The files within this web page are licensed under a CC BY-NC-SA 4.0 Attribution-NonCommercial-ShareAlike International license.