Migrate WebGL themes to vividgl package

main
Warinyourself 3 months ago
parent 61bea1b774
commit a381f92d14

7
package-lock.json generated

@ -11,6 +11,7 @@
"@lucide/vue": "^1.17.0",
"@vueuse/core": "^14.3.0",
"pinia": "^3.0.4",
"vividgl": "^0.1.0",
"vue": "^3.5.32",
"vue-i18n": "^11.4.5",
"vue-router": "^5.0.4"
@ -3690,6 +3691,12 @@
"vue": ">=3.2.13"
}
},
"node_modules/vividgl": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/vividgl/-/vividgl-0.1.0.tgz",
"integrity": "sha512-JADDWl6pgKm8h3LSB1N5cK48MCVjWwTd+l4VIln7S1lO7rPT7+mCNv7OSVxdb2LPhlJWAzTFhubw8AniJWJ7cA==",
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",

@ -15,6 +15,7 @@
"@lucide/vue": "^1.17.0",
"@vueuse/core": "^14.3.0",
"pinia": "^3.0.4",
"vividgl": "^0.1.0",
"vue": "^3.5.32",
"vue-i18n": "^11.4.5",
"vue-router": "^5.0.4"

@ -1,47 +1,89 @@
import { defineComponent, computed, h } from 'vue'
import { defineComponent, ref, watch, onMounted, onBeforeUnmount } from 'vue'
import { VividGL } from 'vividgl'
import type { ParamValues } from 'vividgl'
import { useThemeStore } from '@/store/theme'
import type { AppInputTheme } from '@/models/app'
import flow from '@/components/themes/flow'
import sphere from '@/components/themes/sphere'
import rings from '@/components/themes/rings'
import plasma from '@/components/themes/plasma'
import destruction from '@/components/themes/destruction'
import tenderness from '@/components/themes/tenderness'
import planet from '@/components/themes/planet'
import random from '@/components/themes/random'
import tunnel from '@/components/themes/tunnel'
import contour from '@/components/themes/contour'
import rings3d from '@/components/themes/rings3d'
import zappy from '@/components/themes/zappy'
const themeMap: Record<string, any> = {
flow,
sphere,
rings,
plasma,
destruction,
tenderness,
planet,
random,
tunnel,
contour,
rings3d,
zappy,
// Maps osmos setting names (kebab-case) → VividGL param names (camelCase)
// Auto-converts kebab to camelCase; explicit overrides for renames
const PARAM_MAP: Record<string, string> = {
'animation-speed': 'speed',
'color-glow': 'glowColor',
'color-fog': 'fogColor',
}
function toVividParam(name: string): string {
return PARAM_MAP[name] ?? name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
}
function buildParams(settings: AppInputTheme[] | undefined): ParamValues {
const params: ParamValues = {}
for (const input of settings ?? []) {
if (input.type === 'button') continue
const key = toVividParam(input.name)
if (key !== 'pxratio' && !Array.isArray(input.value))
params[key] = input.value as string | number | boolean
}
return params
}
function getPxratio(settings: AppInputTheme[] | undefined): number {
return (settings?.find(i => i.name === 'pxratio')?.value as number) ?? 0.8
}
export default defineComponent({
name: 'BackgroundImage',
setup() {
const themeStore = useThemeStore()
const theme = computed(() => themeStore.activeTheme)
return () => {
const component = themeMap[theme.value.component ?? 'random'] ?? themeMap.random
return (
<div class="background-image">
{h(component)}
</div>
)
const canvasRef = ref<HTMLCanvasElement | null>(null)
let bg: VividGL | null = null
const init = (themeName: string) => {
bg?.destroy()
bg = null
if (!canvasRef.value) return
const knownThemes = VividGL.themes()
const name = knownThemes.includes(themeName) ? themeName : knownThemes[0]!
const params = buildParams(themeStore.activeTheme.settings)
try {
bg = new VividGL(canvasRef.value, name, params)
bg.pxratio = getPxratio(themeStore.activeTheme.settings)
bg.start()
} catch (e) {
console.warn('[BackgroundImage] VividGL error:', e)
}
}
onMounted(() => init(themeStore.activeTheme.component ?? 'random'))
// Switch theme when selection changes
watch(() => themeStore.currentTheme, () => {
init(themeStore.activeTheme.component ?? 'random')
})
// Sync individual param updates in real time
watch(
() => themeStore.activeTheme.settings,
(settings) => {
if (!bg) return
for (const input of settings ?? []) {
if (input.type === 'button') continue
if (input.name === 'pxratio') { bg.pxratio = input.value as number; continue }
if (!Array.isArray(input.value))
bg.setParam(toVividParam(input.name), input.value as string | number | boolean)
}
},
{ deep: true }
)
onBeforeUnmount(() => bg?.destroy())
return () => (
<div class="background-image">
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,117 +0,0 @@
#version 300 es
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Zoom/scale of the noise pattern — higher values zoom in, revealing more detail
uniform float uScale;
// Number of stripes/contour lines across the noise field
uniform float uScaleContour;
// Line width: controls how much of the contour field is visible (0 = thin hairlines, 1 = wide bands)
uniform float uWidth;
// Max line band: caps the upper visible threshold so all lines have similar spatial width
// smaller = more uniform thin lines, larger = allows wide bands
uniform float uMaxLine;
// First gradient color (RGB)
uniform vec3 uColor1;
// Second gradient color (RGB)
uniform vec3 uColor2;
// Background color — shown where lines and glow are absent
uniform vec3 uBackground;
const float M_PI = 3.14159265;
const int NUM_OCTAVES = 2;
// Hash functions — map domain to pseudo-random [0, 1)
float hash11(float t) {
return fract(sin(t * 56789.0) * 56789.0);
}
float hash21(vec2 uv) {
return hash11(hash11(uv[0]) + 2.0 * hash11(uv[1]));
}
// Returns a unit-length gradient vector for a given grid cell
vec2 hashGradient2(vec2 uv) {
float t = hash21(uv);
return vec2(cos(2.0 * M_PI * t), sin(2.0 * M_PI * t));
}
// Bilinear mix helper
float mix2(float f00, float f10, float f01, float f11, vec2 uv) {
return mix(mix(f00, f10, uv[0]), mix(f01, f11, uv[0]), uv[1]);
}
// Rotate a 2D vector by angle r (radians)
vec2 rotate2(vec2 uv, float r) {
mat2 R = mat2(cos(r), sin(r), -sin(r), cos(r));
return R * uv;
}
// Gradient (Perlin) noise with optional gradient rotation for animation
float gradientNoise(vec2 uv, float r) {
vec2 uvi = floor(uv);
vec2 uvf = uv - uvi;
vec2 g00 = rotate2(hashGradient2(uvi + vec2(0.0, 0.0)), r);
vec2 g10 = rotate2(hashGradient2(uvi + vec2(1.0, 0.0)), r);
vec2 g01 = rotate2(hashGradient2(uvi + vec2(0.0, 1.0)), r);
vec2 g11 = rotate2(hashGradient2(uvi + vec2(1.0, 1.0)), r);
float f00 = dot(g00, uvf - vec2(0.0, 0.0));
float f10 = dot(g10, uvf - vec2(1.0, 0.0));
float f01 = dot(g01, uvf - vec2(0.0, 1.0));
float f11 = dot(g11, uvf - vec2(1.0, 1.0));
float t = mix2(f00, f10, f01, f11, smoothstep(vec2(0.0), vec2(1.0), uvf));
// Normalize: theoretical bounds are +-1/sqrt(2) ≈ +-0.7
return (t / 0.7 + 1.0) * 0.5;
}
// Fractional Brownian Motion — stacks octaves for richer noise detail
float noise(vec2 uv, float r) {
float result = 0.0;
for (int i = 0; i < NUM_OCTAVES; i++) {
float p = pow(2.0, float(i));
result += gradientNoise(uv * p, r) / p;
}
// Normalize result back to [0, 1]
result /= (pow(2.0, float(NUM_OCTAVES)) - 1.0) / pow(2.0, float(NUM_OCTAVES - 1));
return result;
}
// Maps noise value to a periodic wave that creates the contour bands
float wave(float t) {
return 0.5 * (1.0 - cos(uScaleContour * M_PI * t));
}
out vec4 fragColor;
void main() {
vec2 uv = uScale * gl_FragCoord.xy / uResolution.y;
// Animate by rotating noise gradients over time (0.628 ≈ 0.1 * 2π)
float r = 0.628 * uTime;
float noise_fac = noise(uv, r);
float contour_fac = wave(noise_fac);
vec3 color = mix(uColor1, uColor2, noise_fac);
float lo = 1.0 - uWidth;
float hi = lo + uMaxLine;
float fw = fwidth(contour_fac);
// Crisp anti-aliased line: only pixels in the [lo, hi] band are visible,
// bounding maximum spatial width so all lines appear roughly equal-sized
float line = smoothstep(lo - fw, lo + fw, contour_fac)
- smoothstep(hi - fw, hi + fw, contour_fac);
float brightness = line;
fragColor = vec4(mix(uBackground, color, brightness), 1.0);
}

@ -1,86 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import PostProcessGL from '@/utils/postprocess'
import { useThemeStore } from '@/store/theme'
import { hexToRgb } from '@/utils/color'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: PostProcessGL
export default defineComponent({
name: 'ContourTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 0.7)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const scale = computed(() => themeStore.getThemeInput('scale')?.value as number || 3.0)
const contour = computed(() => themeStore.getThemeInput('contour')?.value as number || 32.0)
const width = computed(() => themeStore.getThemeInput('width')?.value as number || 0.7)
const maxLine = computed(() => themeStore.getThemeInput('max-line')?.value as number || 0.3)
const color1 = computed(() => themeStore.getThemeInput('color-active')?.value as string || '#FF00FF')
const color2 = computed(() => themeStore.getThemeInput('color-second')?.value as string || '#00FFFF')
const background = computed(() => themeStore.getThemeInput('color-bg')?.value as string || '#000000')
onMounted(() => {
render = new PostProcessGL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.scale) {
gl.programInfo.uniforms.scale = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uScale')
gl.programInfo.uniforms.contour = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uScaleContour')
gl.programInfo.uniforms.width = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uWidth')
gl.programInfo.uniforms.maxLine = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uMaxLine')
gl.programInfo.uniforms.color1 = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uColor1')
gl.programInfo.uniforms.color2 = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uColor2')
gl.programInfo.uniforms.background = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uBackground')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
const c1 = hexToRgb(color1.value)
const c2 = hexToRgb(color2.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.scale, scale.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.contour, contour.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.width, width.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.maxLine, maxLine.value)
const bg = hexToRgb(background.value)
gl.ctx.uniform3f(gl.programInfo.uniforms.color1, c1[0] / 255, c1[1] / 255, c1[2] / 255)
gl.ctx.uniform3f(gl.programInfo.uniforms.color2, c2[0] / 255, c2[1] / 255, c2[2] / 255)
gl.ctx.uniform3f(gl.programInfo.uniforms.background, bg[0] / 255, bg[1] / 255, bg[2] / 255)
}
},
{
radiusB: .5, intensityB: 1.0,
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,63 +0,0 @@
#version 300 es
/*
* Original shader from: https://www.shadertoy.com/view/ldyyWm
*/
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform float position;
uniform float perspective;
uniform vec2 uResolution;
float burn;
float time;
mat2 rot(float a) {
float s=sin(a), c=cos(a);
return mat2(s, c, -c, s);
}
float map(vec3 p) {
float d = max(max(abs(p.x), abs(p.y)), abs(p.z)) - perspective;
burn = d;
mat2 rm = rot(-time/3. + length(p));
p.xy *= rm, p.zy *= rm;
vec3 q = abs(p) - time;
q = abs(q - round(q));
rm = rot(time);
q.xy *= rm, q.xz *= rm;
d = min(d, min(min(length(q.xy), length(q.yz)), length(q.xz)) + .01);
burn = pow(d - burn, 2.);
return d;
}
void mainImage( out vec4 col, in vec2 fragCoord ) {
vec3 rd = normalize(vec3(2. * fragCoord - uResolution.xy, uResolution.y * position)), ro = vec2(0, -2).xxy;
mat2 r1 = rot(time/4.), r2 = rot(time/2.);
rd.xz *= r1, ro.xz *= r1, rd.yz *= r2, ro.yz *= r2;
float t = 0.0, i = 24.0 * (1.0 - exp(-0.2 * time - 0.1));
for (int ii = 0; ii < 100; ++ii) {
if (i <= 0.0) break;
t += map(ro + rd * t) / 2.0;
i -= 1.0;
}
col = vec4(1.1 - burn, exp(-t), exp(-t/2.), 1);
// col = vec4(exp(-t) * 4.1, exp(-t/2.) * 0.9, 0.4 - burn * 2.9, 1);
}
out vec4 fragColor;
void main(void) {
time = uTime + 200.0;
mainImage(fragColor, gl_FragCoord.xy);
}

@ -1,56 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'DestructionTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const perspective = computed(() => themeStore.getThemeInput('perspective')?.value as number || 0.2)
const position = computed(() => themeStore.getThemeInput('position')?.value as number || 0.2)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'position')
gl.programInfo.uniforms.perspective = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'perspective')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.position, position.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.perspective, perspective.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,78 +0,0 @@
#version 300 es
precision highp float;
uniform vec2 uResolution;
uniform float uTime;
uniform float size;
float random (in vec2 point) {
return fract(100.0 * sin(point.x + fract(100.0 * sin(point.y)))); // http://www.matteo-basei.it/noise
}
float noise (in vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1., 0.));
float c = random(i + vec2(0., 1.));
float d = random(i + vec2(1., 1.));
vec2 u = f * f * (3. - 2. * f);
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
#define octaves 10
float fbm (in vec2 p) {
float value = 0.;
float freq = size;
float amp = .5;
for (int i = 0; i < octaves; i++) {
value += amp * (noise((p - vec2(1.)) * freq));
freq *= 1.9;
amp *= .6;
}
return value;
}
float pattern(in vec2 p) {
vec2 offset = vec2(-.5);
vec2 aPos = vec2(sin(uTime * .05), sin(uTime * .1)) * 6.;
vec2 aScale = vec2(3.);
float a = fbm(p * aScale + aPos);
vec2 bPos = vec2(sin(uTime * .1), sin(uTime * .1)) * 1.;
vec2 bScale = vec2(.5);
float b = fbm((p + a) * bScale + bPos);
vec2 cPos = vec2(-.6, -.5) + vec2(sin(-uTime * .01), sin(uTime * .1)) * 2.;
vec2 cScale = vec2(2.);
float c = fbm((p + b) * cScale + cPos);
return c;
}
vec3 palette(in float t) {
vec3 a = vec3(.5, .5, .5);
vec3 b = vec3(.45, .25, .14);
vec3 c = vec3(1. ,1., 1.);
vec3 d = vec3(0., .1, .2);
return a + b * cos(6.28318 * (c * t + d));
}
out vec4 fragColor;
void main() {
vec2 p = gl_FragCoord.xy / uResolution.xy;
p.x *= uResolution.x / uResolution.y;
float value = pow(pattern(p), 2.);
vec3 color = palette(value);
fragColor = vec4(color, 1.);
}

@ -1,60 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'FlowTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => themeStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => themeStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => themeStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const size = computed(() => themeStore.getThemeInput('size')?.value as number || 0.2)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, size.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,186 +0,0 @@
#version 300 es
/*
* Original shader from: https://www.shadertoy.com/view/ttKBDd
*/
#ifdef GL_ES
precision highp float;
#endif
uniform float camR;
uniform float uTime;
uniform vec2 uResolution;
uniform vec2 iMouse;
#define texture(s, uv) vec4(0.0)
#define R uResolution.xy
#define m vec2(R.x/R.y*(iMouse.x/R.x-.5),iMouse.y/R.y-.5)
#define ss(a, b, t) smoothstep(a, b, t)
#define rot(a) mat2(cos(a), -sin(a), sin(a), cos(a))
const float pi = 3.14159;
float hsh(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float perlin(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
float a = hsh(i);
float b = hsh(i+vec2(1., .0));
float c = hsh(i+vec2(0. ,1 ));
float d = hsh(i+vec2(1., 1. ));
vec2 u = smoothstep(0., 1., f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float octnse(vec2 p, int oct, float t) {
float a = 1.;
float n = 0.;
for(int i = 0; i < 10; i++){
if (i >= oct) break;
p.x += t;
n += perlin(p) * a *.5;
p*=2.;
a *= .5;
}
return n;
}
// 3D simplex noise stuff from: https://www.shadertoy.com/view/XsX3zB
const float F3 = .3333333;
const float G3 = .1666667;
vec3 random3(vec3 c) {
float j = 4096.*sin(dot(c,vec3(17., 59.4, 15.)));
vec3 r;
r.z = fract(512.*j);
j *= .125;
r.x = fract(512.*j);
j *= .125;
r.y = fract(512.*j);
return r-.5;
}
float simplex3d(vec3 p) {
vec3 s = floor(p + dot(p, vec3(F3)));
vec3 x = p - s + dot(s, vec3(G3));
vec3 e = step(vec3(0.), x - x.yzx);
vec3 i1 = e*(1. - e.zxy);
vec3 i2 = 1. - e.zxy*(1. - e);
vec3 x1 = x - i1 + G3;
vec3 x2 = x - i2 + 2.*G3;
vec3 x3 = x - 1. + 3.*G3;
vec4 w, d;
w.x = dot(x, x);
w.y = dot(x1, x1);
w.z = dot(x2, x2);
w.w = dot(x3, x3);
w = max(.6 - w, 0.);
d.x = dot(random3(s), x);
d.y = dot(random3(s + i1), x1);
d.z = dot(random3(s + i2), x2);
d.w = dot(random3(s + 1.), x3);
w *= w;
w *= w;
d *= w;
float nse = dot(d, vec4(52.));
return 1.-exp(-(nse+1.)*.5);
}
vec4 sphere(vec3 ro, vec3 rd, vec3 cn, float r) {
float b = 2.*dot(rd, ro - cn);
float c = dot(ro - cn, ro - cn) - (r*r);
float d = (b*b) - (4.*c);
if (d < 0.) {
return vec4(0);
} else {
float t = .5*(-b - sqrt(d));
return vec4(ro+rd*t, t);
}
}
const float rad = 2.7;
void mainImage( out vec4 f, in vec2 u ) {
vec2 uv = vec2(u.xy - 0.5*R.xy)/R.y;
float ux = uv.x;
uv *= rot(-uTime*.12 + 2.2);
vec3 ro = vec3(0., 0., 0.);
vec3 rd = normalize(vec3(uv, 1.));
float camP = camR;
float ang = uTime*.12 + 7.;
if (iMouse.y > 0.){
camP -= m.y * 6.;
ang += m.x;
}
ro.x += camP * cos(ang);
ro.z += camP * sin(ang);
rd.xz *= rot(ang + pi/2. + .04);
vec3 ld = normalize(vec3(0.4, 0.3, -0.5));
float ts = .5;
vec3 pp = vec3(0);
vec3 n = vec3(0);
vec3 cntr = vec3(0., 0., 0.);
vec4 p = sphere(ro, rd, cntr, rad);
vec3 col = vec3(0);
pp = p.xyz;
n = pp - cntr;
n = normalize(n);
vec2 cuv = abs(vec2(atan(n.z, n.x), acos(p.y / rad)));
cuv *= rot(-uTime*.05 * ts);
float n1 = 2.*octnse(cuv, 10, -uTime*.08 * ts) - 1.;
float n2 = 2.*octnse((cuv+3.), 10, -uTime*.03 * ts) - 1.;
vec2 os = vec2(n1, n2);
float val = octnse((cuv + vec2(n1, n2)*3.6), 8, -uTime*.1 * ts);
col += .35+.35*cos(vec3(1.4, .7, 0.9)* n1 * 10. + uTime * .35);
col += .48+.37*sin(vec3(2.2, .1, 0.3)* n2 * 20. + vec3(.7, 1.2, .7));
col += .48+.23*cos(vec3(1.4, .7, 0.9)* val * 30. + vec3(.2, 0.8, 4.7)+ uTime*.25);
col*=.38;
vec3 ref = reflect(n, rd);
float val2 = octnse((ref.xy + os*10. + val*5.), 8, -uTime*.1 * ts);
col *= val2*3. * vec3(.9, .8, .8);
col *= max(dot(n, -rd), 0.0)*vec3(.9, .8, .7);
col = col*col*1.7;
col = 1.-exp(-col);
f = vec4(col, 1.0);
}
out vec4 fragColor;
void main(void) {
mainImage(fragColor, gl_FragCoord.xy);
}

@ -1,53 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'PlanetTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const position = computed(() => themeStore.getThemeInput('position')?.value as number || 2.14)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'camR')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * animationSpeed.value / 10))
gl.ctx.uniform1f(gl.programInfo.uniforms.position, position.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,50 +0,0 @@
#version 300 es
/**
* Original shader: https://www.shadertoy.com/view/MsjSW3
*/
precision highp float;
uniform float uTime;
uniform vec2 uResolution;
#define t uTime
mat2 m(float a){float c=cos(a), s=sin(a);return mat2(c,-s,s,c);}
float map(vec3 p){
p.xz*= m(t * 0.4);
p.xy*= m(t * 0.3);
float complexity = 2.;
vec3 q = p * complexity + t;
float zoom = 0.1;
float size = .01;
return length(p + vec3(sin(t * .7))) * log(length(p) + size) + sin(q.x + sin(q.z + sin(q.y))) * 0.5 - zoom;
}
out vec4 fragColor;
void main() {
vec2 p = gl_FragCoord.xy / uResolution.y - vec2(0.9,.5);
vec3 cl = vec3(0.);
float saturation = 4.;
float scale = -1.0;
for (int i=0; i <= 5; i++) {
vec3 p = vec3(0, 0, 5.) + normalize(vec3(p, scale)) * saturation;
float rz = map(p);
float glow = 0.5;
float varietyOfColors = 0.1;
float f = clamp((rz - map(p + varietyOfColors)) * glow, - .1, 1.);
vec3 l = vec3(0.102, 0.1059, 0.4) + vec3(5., 2.5, 3.) * f;
float contrast = 0.2;
float hue = 0.;
cl = cl * l + smoothstep(2.5, .0, rz) * .7 * l;
float smoothness = .9;
saturation += min(rz, smoothness);
}
fragColor = vec4(cl, 1.);
}

@ -1,53 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'PlasmaTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => themeStore.getThemeInput('hue')?.value as number || 0)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg)`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,30 +0,0 @@
#version 300 es
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
uniform float symmetry;
uniform float thickness;
float f(vec3 x) {
x.z -= uTime;
float a = x.z * symmetry;
x.xy *= mat2(cos(a), sin(a), -sin(a), cos(a));
return thickness - length(cos(x.xy) + sin(x.yz));
}
vec3 lambda_0(vec3 x) {
return x + f(x) * (0.5 - (vec3(gl_FragCoord.xy, 1.0) / uResolution.x));
}
out vec4 fragColor;
void main() {
vec3 i_0 = vec3(0.0,0.0,0.0);
for (float i_1 = 0.0; i_1 < 32.; i_1++) i_0 = lambda_0(i_0);
vec3 p = i_0;
fragColor = vec4(((vec3(2.0,5.0,9.0) + sin(p)) / length(p)),1.0);
}

@ -1,63 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'RandomTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => themeStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => themeStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => themeStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const symmetry = computed(() => themeStore.getThemeInput('symmetry')?.value as number || 64)
const thickness = computed(() => themeStore.getThemeInput('thickness')?.value as number || 64)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.symmetry) {
gl.programInfo.uniforms.symmetry = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'symmetry')
gl.programInfo.uniforms.thickness = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'thickness')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.symmetry, symmetry.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.thickness, thickness.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * animationSpeed.value / 10))
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,41 +0,0 @@
#version 300 es
precision highp float;
uniform float uTime;
uniform float hue;
uniform float zoom; // 32.415
uniform vec2 uResolution;
float linearity = 2.; // 2.
out vec4 fragColor;
void main() {
float mx = max( uResolution.x, uResolution.y );
vec2 uv = (gl_FragCoord.xy - uResolution.xy * 0.5) / mx;
uv.x += sin(uTime + uv.y * linearity) * .2;
uv.y -= cos(uTime) * 0.1;
uv.x = dot(uv,uv)*2.0;
float angle = .4;
uv *= mat2(cos(angle), sin(angle), sin(angle), cos(angle));
float fineness = mx * 0.4; // 0 + 0.4
// float fineness = mx * (abs(hue) + 0.2); // 0 + 0.4
float sy = uv.y * fineness;
float c = fract(-sin(floor(sy) / fineness * 14.) * 437.);
float f = fract(sy);
c *= min(f, 1. - f) * 3.;
// float zoom = 32.415;
float intensity = 2.5;
// highlights
c += cos(uv.y * zoom - uTime) * intensity;
// background
float r = -uv.y + (.5 + hue);
float b = uv.y + (.5 - hue);
fragColor = vec4(mix(vec3(r, r*.3, b), vec3(c), .3), 1.0);
}

@ -1,56 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'RingsTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => themeStore.getThemeInput('hue')?.value as number || 0)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const zoom = computed(() => themeStore.getThemeInput('zoom')?.value as number || 32)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.hue) {
gl.programInfo.uniforms.hue = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'hue')
gl.programInfo.uniforms.zoom = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'zoom')
}
gl.time += animationSpeed.value / 500
gl.pxratio = pxratio.value
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.hue, hue.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.zoom, zoom.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,152 +0,0 @@
#version 300 es
// Inspiration: https://www.shadertoy.com/view/WdB3Dw
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Torus tube cross-section radius — thicker values widen the ring walls
uniform float uTubeSize;
// Clipping sphere radius — controls how large the bubble appears
uniform float uBubbleSize;
// Ray march step fraction — lower = slower rays, denser glow/fog accumulation
uniform float uFogDensity;
// Spectrum cycling frequency — higher = more colour bands visible per ray
uniform float uSpectrumSpeed;
// Surface glow colour (normalised; internally scaled ×2.1 to allow overbright)
uniform vec3 uGlowColor;
// Ambient scatter/fog colour accumulated on every ray march step
uniform vec3 uFogColor;
#define PI 3.14159265359
void pR(inout vec2 p, float a) {
p = cos(a)*p + sin(a)*vec2(p.y, -p.x);
}
float smax(float a, float b, float r) {
vec2 u = max(vec2(r + a, r + b), vec2(0.0));
return min(-r, max(a, b)) + length(u);
}
// ─── Spectrum palette ────────────────────────────────────────────────────────
// Cosine colour palette — IQ https://www.shadertoy.com/view/ll2GD3
vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
return a + b*cos(6.28318*(c*t+d));
}
vec3 spectrum(float n) {
return pal(n, vec3(0.5,0.5,0.5), vec3(0.5,0.5,0.5),
vec3(1.0,1.0,1.0), vec3(0.0,0.33,0.67));
}
// ─── SDF via inverse stereographic projection ────────────────────────────────
vec4 inverseStereographic(vec3 p, out float k) {
k = 2.0 / (1.0 + dot(p, p));
return vec4(k*p, k - 1.0);
}
float fTorus(vec4 p4) {
float d1 = length(p4.xy) / length(p4.zw) - 1.0;
float d2 = length(p4.zw) / length(p4.xy) - 1.0;
float d = d1 < 0.0 ? -d1 : d2;
d /= PI;
return d;
}
float fixDistance(float d, float k) {
float sn = sign(d);
d = abs(d);
d = d / k * 1.82;
d += 1.0;
d = pow(d, 0.5);
d -= 1.0;
d *= 5.0 / 3.0;
d *= sn;
return d;
}
float gTime;
float map(vec3 p) {
float k;
vec4 p4 = inverseStereographic(p, k);
pR(p4.zy, gTime * -PI / 2.0);
pR(p4.xw, gTime * -PI / 2.0);
float d = fTorus(p4);
d = abs(d);
d -= uTubeSize;
d = fixDistance(d, k);
d = smax(d, length(p) - uBubbleSize, 0.2);
return d;
}
// ─── Rendering ───────────────────────────────────────────────────────────────
mat3 calcLookAtMatrix(vec3 ro, vec3 ta, vec3 up) {
vec3 ww = normalize(ta - ro);
vec3 uu = normalize(cross(ww, up));
vec3 vv = normalize(cross(uu, ww));
return mat3(uu, vv, ww);
}
out vec4 fragColor;
void main() {
gTime = mod(uTime / 2.0, 1.0);
vec3 camPos = vec3(1.8, 5.5, -5.5) * 1.75;
vec3 camTar = vec3(0.0, 0.0, 0.0);
vec3 camUp = vec3(-1.0, 0.0, -1.5);
mat3 camMat = calcLookAtMatrix(camPos, camTar, camUp);
float focalLength = 5.0;
vec2 p = (-uResolution.xy + 2.0 * gl_FragCoord.xy) / uResolution.y;
vec3 rayDir = normalize(camMat * vec3(p, focalLength));
vec3 rayPos = camPos;
float rayLen = 0.0;
float dist = 0.0;
vec3 color = vec3(0.0);
const int ITER = 82;
const float MIN_STEP = 0.001;
const float MAX_DIST = 20.0;
for (int i = 0; i < ITER; i++) {
rayLen += max(MIN_STEP, abs(dist) * uFogDensity);
rayPos = camPos + rayDir * rayLen;
dist = map(rayPos);
// Surface glow: bright flare near the SDF surface, tinted by uGlowColor
vec3 c = vec3(max(0.0, 0.01 - abs(dist)) * 0.5);
c *= uGlowColor * 2.1; // ×2.1 restores the original overbright range
// Ambient fog: coloured scatter accumulated on every step
c += uFogColor * uFogDensity / 160.0;
c *= smoothstep(20.0, 7.0, length(rayPos));
float rl = smoothstep(MAX_DIST, 0.1, rayLen);
c *= rl;
c *= spectrum(rl * uSpectrumSpeed - 0.6);
color += c;
if (rayLen > MAX_DIST) break;
}
color = pow(color, vec3(1.0 / 1.8)) * 2.0;
color = pow(color, vec3(2.0)) * 3.0;
color = pow(color, vec3(1.0 / 2.2));
fragColor = vec4(color, 1.0);
}

@ -1,75 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import { hexToRgb } from '@/utils/color'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'Rings3dTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 5)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const tubeSize = computed(() => themeStore.getThemeInput('tube-size')?.value as number || 0.2)
const bubbleSize = computed(() => themeStore.getThemeInput('bubble-size')?.value as number || 1.85)
const fogDensity = computed(() => themeStore.getThemeInput('fog-density')?.value as number || 0.8)
const spectrumSpeed = computed(() => themeStore.getThemeInput('spectrum-speed')?.value as number || 6.0)
const glowColor = computed(() => themeStore.getThemeInput('color-glow')?.value as string || '#AAFFCE')
const fogColor = computed(() => themeStore.getThemeInput('color-fog')?.value as string || '#9940B3')
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.tubeSize) {
gl.programInfo.uniforms.tubeSize = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uTubeSize')
gl.programInfo.uniforms.bubbleSize = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uBubbleSize')
gl.programInfo.uniforms.fogDensity = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uFogDensity')
gl.programInfo.uniforms.spectrumSpeed = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uSpectrumSpeed')
gl.programInfo.uniforms.glowColor = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uGlowColor')
gl.programInfo.uniforms.fogColor = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uFogColor')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
const gc = hexToRgb(glowColor.value)
const fc = hexToRgb(fogColor.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.tubeSize, tubeSize.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.bubbleSize, bubbleSize.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.fogDensity, fogDensity.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.spectrumSpeed, spectrumSpeed.value)
gl.ctx.uniform3f(gl.programInfo.uniforms.glowColor, gc[0] / 255, gc[1] / 255, gc[2] / 255)
gl.ctx.uniform3f(gl.programInfo.uniforms.fogColor, fc[0] / 255, fc[1] / 255, fc[2] / 255)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,63 +0,0 @@
#version 300 es
/*
* Original shader from: https://www.shadertoy.com/view/lslcWj
*/
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
uniform float size;
#define R(p, a) p = p * cos(a) + vec2(-p.y, p.x) * sin(a)
float Sin01(float t) {
return 0.5 + 0.5 * sin(6.28319 * t);
}
float SineEggCarton(vec3 p) {
return 1.0 - abs(sin(p.x) + sin(p.y) + sin(p.z)) / 3.0;
}
float Map(vec3 p, float scale) {
float dSphere = length(p) - 1.0;
return max(dSphere, (0.95 - SineEggCarton(scale * p)) / scale);
}
vec3 GetColor(vec3 p) {
float amount = clamp((1.5 - length(p)) / 2.0, 0.0, 1.0);
vec3 col = 0.5 + 0.5 * cos(6.28319 * (vec3(0.2, 0.0, 0.0) + amount * vec3(1.0, 1.0, 0.5)));
return col * amount;
}
void mainImage(out vec4 col, in vec2 fragCoord) {
vec3 rd = normalize(vec3(2.0 * fragCoord.xy - uResolution.xy, -uResolution.y));
vec3 ro = vec3(0.0, 0.0, size);
R(rd.xz, 0.5 * uTime);
R(ro.xz, 0.5 * uTime);
R(rd.yz, 0.5 * uTime);
R(ro.yz, 0.5 * uTime);
float t = 0.0;
col.rgb = vec3(0.0353, 0.0275, 0.1255);
float scale = mix(3.5, 9.0, Sin01(0.068 * uTime));
for (int i = 0; i < 64; i++) {
vec3 p = ro + t * rd;
float d = Map(p, scale);
if (t > 5.0 || d < 0.001) {
break;
}
t += .9 * d;
col.rgb += 0.05 * GetColor(p);
}
}
out vec4 fragColor;
void main(void) {
mainImage(fragColor, gl_FragCoord.xy);
fragColor.a = 1.0;
}

@ -1,60 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'SphereTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => themeStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => themeStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => themeStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const size = computed(() => themeStore.getThemeInput('size')?.value as number || 0.2)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, size.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,28 +0,0 @@
#version 300 es
// Modified so it doesn't really move. Very childish and easy fix.
#ifdef GL_ES
precision highp float;
#endif
uniform vec2 uResolution;
uniform float uTime;
const int complexity = 30; // More points of color.
const float fluidSpeed = 60.0; // Drives speed, higher number will make it slower.
const float color_intensity = 0.5;
const float position = 1.0;
out vec4 fragColor;
void main() {
vec2 p= (position * gl_FragCoord.xy - uResolution) / max(uResolution.x, uResolution.y);
for(int i = 1; i < complexity;i++) {
vec2 newp= p + uTime * 0.005;
newp.x+=0.6/float(i)*sin(float(i)*p.y+uTime/fluidSpeed+20.3*float(i)) + 0.5; // + mouse.y/mouse_factor+mouse_offset;
newp.y+=0.6/float(i)*sin(float(i)*p.x+uTime/fluidSpeed+0.3*float(i+10)) - 0.5; // - mouse.x/mouse_factor+mouse_offset;
p=newp;
}
vec3 col=vec3(color_intensity*sin(5.0*p.x)+color_intensity,color_intensity*sin(3.0*p.y)+color_intensity,color_intensity*sin(p.x+p.y)+color_intensity);
fragColor=vec4(col, 1.0);
}

@ -1,48 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'TendernessTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
gl.time += animationSpeed.value / 500
gl.pxratio = pxratio.value
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,45 +0,0 @@
#version 300 es
// Original: http://www.pouet.net/prod.php?which=57245
// Credits: Danilo Guanabara
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Per-iteration z offset — controls how far apart the RGB channels are in time.
// Higher values spread colors into wider rainbow bands; lower values keep it monochromatic.
uniform float uStep;
// Frequency of the radial sine wave — controls ring/wave density.
// Higher values produce tighter, more numerous rings; lower values produce broad sweeping waves.
uniform float uFrequency;
// Tunnel pull strength — scales the UV distortion toward the center.
// 0.0 = flat pattern with no perspective; higher values create a deeper vortex effect.
uniform float uAmplitude;
// Color channel intensity — inversely controls brightness (smaller = brighter).
// Too high darkens the image; too low causes overexposure / color clipping.
uniform float uBrightness;
#define t uTime
#define r uResolution
out vec4 fragColor;
void main() {
vec3 c;
float l, z = t;
for (int i = 0; i < 3; i++) {
vec2 uv, p = gl_FragCoord.xy / r;
uv = p;
p -= .5;
p.x *= r.x / r.y;
z += uStep;
l = length(p);
uv += p / l * (sin(z) + 1.) * uAmplitude * abs(sin(l * uFrequency - z - z));
c[i] = uBrightness / length(mod(uv, 1.) - .5);
}
fragColor = vec4(c / l, 1.0);
}

@ -1,64 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'TunnelTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 5)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const step = computed(() => themeStore.getThemeInput('step')?.value as number || 0.07)
const frequency = computed(() => themeStore.getThemeInput('frequency')?.value as number || 9.0)
const amplitude = computed(() => themeStore.getThemeInput('amplitude')?.value as number || 1.0)
const brightness = computed(() => themeStore.getThemeInput('brightness')?.value as number || 0.01)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.step) {
gl.programInfo.uniforms.step = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uStep')
gl.programInfo.uniforms.frequency = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uFrequency')
gl.programInfo.uniforms.amplitude = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uAmplitude')
gl.programInfo.uniforms.brightness = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uBrightness')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.step, step.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.frequency, frequency.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.amplitude, amplitude.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.brightness, brightness.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -1,71 +0,0 @@
#version 300 es
// Inspiration: https://www.shadertoy.com/view/cfjGzV — Zippy Zaps
// Credits: -13 thanks to Nguyen2007 ⚡
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Overall zoom / scale of the electric pattern
uniform float uZoom;
// Shifts the base colour phase vector, cycling through hue combinations
uniform float uColorShift;
out vec4 fragColor;
void main() {
vec2 res = uResolution;
vec2 u = gl_FragCoord.xy;
// Centre and normalise to screen height; uZoom replaces the hardcoded 0.2
u = uZoom * (u + u - res) / res.y;
// Base colour phase vector — uColorShift scrolls through hue space
// z.wxzw swizzle = vec4(z.w, z.x, z.z, z.w) = vec4(0,1,3,0) used in mat2 rotation
vec4 z = vec4(1.0 + uColorShift, 2.0 + uColorShift, 3.0 + uColorShift, 0.0);
vec4 o = z;
float a = 0.5;
float t = uTime;
float i = 0.0;
// Original: for (float a=.5, t=iTime, i; ++i<19.; o+=...) v=..., u+=...
// Rewritten: float i counts 1..18 (pre-increment in condition);
// ++t and a+=.03 moved to explicit statements preserving order;
// comma-operator body split into sequential statements.
vec2 v = res;
for (int iter = 0; iter < 18; iter++) {
i += 1.0; // pre-increment: matches ++i in original condition
// ++t happens inside the v= expression (pre-increment before the rhs is evaluated)
t += 1.0;
a += 0.03;
v = cos(t - 7.0 * u * pow(a, i)) - 5.0 * u;
// mat2 from explicit components avoids mat2(vec4) constructor rejection on ANGLE/Mesa
// vec4(scalar) - vec4 ensures float-vec4 subtraction is unambiguous
vec4 r = cos(vec4(i + 0.02 * t) - z.wxzw * 11.0);
u *= mat2(r.x, r.y, r.z, r.w);
// If black/NaN artifacts appear, replace tanh with stanh (smoothstep-tanh approximation)
u += tanh(40.0 * dot(u, u) * cos(100.0 * u.yx + t)) / 200.0
+ 0.2 * a * u
+ cos(4.0 / exp(dot(o, o) / 100.0) + t) / 300.0;
// for-increment: accumulate glow using the freshly updated v and u
o += (1.0 + cos(z + t))
/ length((1.0 + i * dot(v, v))
* sin(1.5 * u / (0.5 - dot(u, u)) - 9.0 * u.yx + t));
}
// Tonemapping: multiply numerator/denominator by o to avoid float/vec4 division
// Equivalent to: 25.6 / (min(o,13.) + 164./o)
o = 25.6 * o / (min(o, 13.0) * o + 164.0) - dot(u, u) / 250.0;
fragColor = vec4(o);
}

@ -1,59 +0,0 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'ZappyTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 5)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const zoom = computed(() => themeStore.getThemeInput('zoom')?.value as number || 0.2)
const colorShift = computed(() => themeStore.getThemeInput('color-shift')?.value as number || 0.0)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.zoom) {
gl.programInfo.uniforms.zoom = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uZoom')
gl.programInfo.uniforms.colorShift = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uColorShift')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.zoom, zoom.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.colorShift, colorShift.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -1,6 +0,0 @@
#version 300 es
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -33,6 +33,9 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
optimizeDeps: {
exclude: ['vividgl']
},
css: {
preprocessorOptions: {
stylus: {}

Loading…
Cancel
Save