<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/"
    }
  }
</script>

<div id="courses"><a href="https://niklever.com/courses" target="_blank">niklever.com/courses</a></div>
body {
  padding: 0;
  margin: 0;
}

#courses {
  font: bold 30px "Arial";
  position: fixed;
  left: 20px;
  top: 20px;
  color: #ffffff;
  text-decoration: none;
}

a:link {
  color: white;
  text-decoration: none;
}

a:hover {
  color: #dddd33;
  text-decoration: underline;
}

a:visited {
  color: white;
  text-decoration: none;
}
import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
            
let camera, scene, renderer, clock, mesh, uniforms;

const vertexShader = `
    varying vec2 vUv;

    void main(){
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
`

const fragmentShader = `

varying vec2 vUv;

uniform float uTime;
uniform float uContrast;
uniform float uBrightness;
uniform float uFrequency;
uniform vec3 uPhase;
uniform int uCount;

float mandelbrot( vec2 c )
{
    float c2 = dot(c, c);
    // skip computation inside M1 - https://iquilezles.org/articles/mset1bulb
    if( 256.0*c2*c2 - 96.0*c2 + 32.0*c.x - 3.0 < 0.0 ) return 0.0;
    // skip computation inside M2 - https://iquilezles.org/articles/mset2bulb
    if( 16.0*(c2+2.0*c.x+1.0) - 1.0 < 0.0 ) return 0.0;  

    float B = float(uCount);
    float n = 0.0;
    vec2 z  = vec2(0.0);
    for( int i=0; i<uCount; i++ )
    {
        z = vec2( z.x*z.x - z.y*z.y, 2.0*z.x*z.y ) + c;
        if( dot(z,z)>B ) break;
        n += 1.0;
    }

    if( n>float(uCount - 1) ) return 0.0;
    
    // ------------------------------------------------------

    // optimized smooth interation count
    float sn = n - log2(log2(dot(z,z))) + 4.0;
    
    return sn;
}

void main(  )
{
    vec3 col = vec3(0.0);
    
    int AA = 2;
    float AAsq = float(AA*AA);

    for( int m=0; m<AA; m++ ){
        for( int n=0; n<AA; n++ )
        {
            vec2 p = (-1.0 + 2.0*(vUv+vec2(float(m) * 0.001,float(n) * 0.001)/float(AA)));
            float w = float(AA*m+n);
            float time = uTime + 0.5*(1.0/24.0)*w/AAsq;
        
            float zoo = 0.72 + 0.28*cos(.09*time);
            float theta = 0.05*(1.0-zoo)*time;
            float coa = cos( theta );
            float sia = sin( theta );
            zoo = pow( zoo, 8.0);
            vec2 xy = vec2( p.x*coa-p.y*sia, p.x*sia+p.y*coa);
            //vec2 c = vec2(-.745,.186) + xy*zoo;
            vec2 c = vec2(-0.720,-0.26) + xy*zoo;

            float l = mandelbrot(c);

            //color(t) = a + b ⋅ cos[ 2π(c⋅t+d)]
            //a: contrast
            //b: brightness
            //c: frequency
            //d: phase

            col += (l<0.5) ? vec3(0.0,0.0,0.0) : 
                uContrast + uBrightness*cos( 6.28*(uFrequency*(l/float(uCount)) + uPhase) );
        }
    }
    col /= AAsq;

    gl_FragColor = vec4( col, 1.0 );
}
`;
    
init();

function init() {
    clock = new THREE.Clock();

    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x0561a0 );

    camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 40 );
    camera.position.set( 0, 0, 1.4 );

    uniforms = {
        uTime: { value: 0 },
        uCount: { value: 100 },
        uPhase: { value: new THREE.Vector3( 0.1, 0.8, 1.0 ) },
        uContrast: { value: 0.746 },
        uBrightness: { value: 0.402 },
        uFrequency: { value: 4.1 }
    }

    const geometry = new THREE.PlaneGeometry( );
    const material = new THREE.ShaderMaterial({
        fragmentShader,
        vertexShader,
        uniforms
    })
    mesh = new THREE.Mesh( geometry, material );

    scene.add(mesh);

    const options = {
        phase: 0x978672
    }

    const color = new THREE.Color(options.phase);
    uniforms.uPhase.value.set( color.r, color.g, color.b );

    const gui = new GUI();
    gui.add( uniforms.uCount, 'value', 10, 512, 1 ).name( 'count' );
    gui.add( uniforms.uContrast, 'value', 0, 2 ).name( 'contrast' );
    gui.add( uniforms.uBrightness, 'value', 0, 2 ).name( 'brightness' );
    gui.add( uniforms.uFrequency, 'value', 0, 10).name( 'frequency' );
    gui.addColor( options, 'phase' ).onChange( value => {
        color.setHex( value );
        uniforms.uPhase.value.set( color.r, color.g, color.b );
    } )

    renderer.setAnimationLoop( render );
    
    window.addEventListener( 'resize', onWindowResize );
}

function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );
}

//

function render() {
    uniforms.uTime.value += clock.getDelta();

    renderer.render( scene, camera );

}   

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.