Гипнотический циклон
Гипнотический вихрь на шейдераx
HTML
<canvas></canvas>
<script type="x-shader/vertex">
precision mediump float;
attribute vec2 position;
void main () {
gl_Position = vec4(position, 0, 1.0);
}
</script>
<script type="x-shader/fragment">
precision highp float;
uniform float time;
uniform float width;
uniform float height;
const int ITERS = 120;
const float PI = 3.141592654;
const float DEG = PI / 180.0;
vec2 coords() {
float vmin = min(width, height);
return vec2((gl_FragCoord.x - width * .5) / vmin,
(gl_FragCoord.y - height * .5) / vmin);
}
vec2 rotate(vec2 p, float a) {
return vec2(p.x * cos(a) - p.y * sin(a),
p.x * sin(a) + p.y * cos(a));
}
vec2 repeat(in vec2 p, in vec2 c) {
return mod(p, c) - 0.5 * c;
}
int gpf(int num) {
int result = 1;
int limit = int(sqrt(float(num)));
for (int i = 0; i < ITERS; i++) {
int factor = (i == 0) ? 2 : (1 + i * 2);
if (factor > limit) {
break;
}
for (int j = 0; j < ITERS; j++) {
if (int(mod(float(num), float(factor))) != 0) {
break;
}
num = int(num / factor);
result = factor;
}
if (factor > num) {
break;
}
}
if (num > 1) {
result = num;
}
return result;
}
vec2 complexMul(vec2 a, vec2 b) {
return vec2(a.x * b.x - a.y * b.y, a.y * b.x + a.x * b.y);
}
vec2 complexPow(vec2 a, int n) {
vec2 result = vec2(1.0, 0.0);
for (int i = 0; i < ITERS; i++) {
if (i == n) {
break;
}
result = complexMul(result, a);
}
return result;
}
// exponential generating function EG(gpf; z) = Σn=1∞ gpf(n) z^n / (n+1)!
vec2 exponential(vec2 p) {
vec2 x = vec2(0.0, 0.0);
int j = 1;
for (int i = 0; i < 13; i++) {
j = j * (i + 2);
x = x + float(gpf(i + 1)) * complexPow(p * 8.0, int(i + 1)) / float(j);
}
return x;
}
vec3 palette(float x) {
return vec3(.5 + .5 * sin(time * .1+ x * 4.0), sin(time * .2 + x), sin(time *.1 + x + 1.0));
}
void main () {
vec2 p00 = coords();
vec2 p0 = rotate(p00, time *.05);
vec2 exp = exponential(p0 * .3)*.1;
vec2 logExp = log(exp);
vec3 col = palette(
.5 * sin(log(pow(length(exp), 3.0 +
1.0 * sin(time *.1))) + atan(exp.y, exp.x) + time*.2));
gl_FragColor = vec4(col, 1.0);
}
</script>
CSS
body {
margin: 0;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
JS
import GLea from 'https://terabaud.github.io/hello-webgl/lib/glea/glea.mjs';
const frag = document.querySelector('[type="x-shader/fragment"]').textContent;
const vert = document.querySelector('[type="x-shader/vertex"]').textContent;
const glea = new GLea({
shaders: [
GLea.fragmentShader(frag),
GLea.vertexShader(vert)
],
buffers: {
'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1])
}
}).create();
window.addEventListener('resize', () => {
glea.resize();
});
function loop(time) {
const { gl } = glea;
glea.clear();
glea.uni('width', glea.width);
glea.uni('height', glea.height);
glea.uni('time', time * .005);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(loop);
}
loop(0);