<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.webgpu.js",
      "three/webgpu": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.webgpu.js",
      "three/tsl": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.tsl.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;
}
//TSL refactor of https://www.shadertoy.com/view/4df3Rn
//Created by inigo quilez - iq/2013
//https://www.youtube.com/c/InigoQuilez
//https://iquilezles.org
//TSL conversion by Nik Lever with permission
//https://niklever.com
import * as THREE from "three";
import { uniform, uv, vec2, dot, Break, float, mul, If, int, Loop, log2, Fn, vec3, time, cos, add, sub, sin, pow, select, vec4 } from 'three/tsl';
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";

let container;

let camera, scene, renderer, mesh;

init();

async function init() {
  renderer = new THREE.WebGPURenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setAnimationLoop(render);
  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.3);

  new OrbitControls(camera, renderer.domElement);

  await tsl();

  window.addEventListener("resize", onWindowResize);
}

async function tsl() {
  const geometry = new THREE.PlaneGeometry( );
    const material = new THREE.MeshBasicNodeMaterial( { transparent: true } );
    mesh = new THREE.Mesh( geometry, material );

    const uContrast = uniform( float(0.5) );
    const uBrightness = uniform( float(0.402) );
    const uFrequency = uniform( float(4.1) );
    const uPhase = uniform( vec3() );
    const uCount = uniform( int(100) );

    const mandelbrot = /*#__PURE__*/ Fn( ( [ c_immutable ] ) => {

        const c = vec2( c_immutable ).toVar();
        const c2 = float( dot( c, c ) ).toVar();
    
        If( mul( 256.0, c2 ).mul( c2 ).sub( mul( 96.0, c2 ) ).add( mul( 32.0, c.x ).sub( 3.0 ) ).lessThan( 0.0 ), () => {
    
            return 0.0;
    
        } );
    
        If( mul( 16.0, c2.add( mul( 2.0, c.x ) ).add( 1.0 ) ).sub( 1.0 ).lessThan( 0.0 ), () => {
    
            return 0.0;
    
        } );
    
        const B = float( uCount ).toVar();
        const n = float( 0.0 ).toVar();
        const z = vec2( 0.0 ).toVar();
    
        Loop( { start: int( 0 ), end: uCount }, ( { i } ) => {
    
            z.assign( vec2( z.x.mul( z.x ).sub( z.y.mul( z.y ) ), mul( 2.0, z.x ).mul( z.y ) ).add( c ) );
    
            If( dot( z, z ).greaterThan( B.mul( B ) ), () => {
    
                Break();
    
            } );
    
            n.addAssign( 1.0 );
    
        } );
    
        If( n.greaterThan( float( uCount.sub( int( 1 ) ) ) ), () => {
    
            return 0.0;
    
        } );
    
        const sn = float( n.sub( log2( log2( dot( z, z ) ) ) ).add( 4.0 ) ).toVar();
    
        return sn;
    
    } ).setLayout( {
        name: 'mandelbrot',
        type: 'float',
        inputs: [
            { name: 'c', type: 'vec2' }
        ]
    } );
    
    const fragTSL = /*#__PURE__*/ Fn( () => {
    
        const col = vec3( 0.0 ).toVar();
        const AA = int( int( 2 ) ).toVar();
        const AAsq = float( float( AA ).mul( float( AA ) ) ).toVar();
        const vUv = uv().toVar();

        Loop( { end: AA, name: 'm' }, ( { m } ) => {
    
            Loop( { end: AA, name: 'n' }, ( { n } ) => {
                
                const p = vec2( float( - 1.0 ).add( mul( 2.0, vUv.add( vec2( float( m ).mul( 0.001 ), float( n ).mul( 0.001 ) ).div( float( AA ) ) ) ) ) ).toVar();
                const w = float( AA.mul( m ).add( n ) ).toVar();
                const tm = float( time.add( mul( 0.5 * 1.0 / 24.0, w ).div( AAsq ) ) ).toVar();
                const zoo = float( add( 0.72, mul( 0.28, cos( mul( .09, tm ) ) ) ) ).toVar();
                const theta = float( mul( 0.05, sub( 1.0, zoo ) ).mul( tm ) ).toVar();
                const coa = float( cos( theta ) ).toVar();
                const sia = float( sin( theta ) ).toVar();
                zoo.assign( pow( zoo, 8.0 ) );
                const xy = vec2( p.x.mul( coa ).sub( p.y.mul( sia ) ), p.x.mul( sia ).add( p.y.mul( coa ) ) ).toVar();
                const c = vec2( vec2( float( - 0.720 ), float( - 0.26 ) ).add( xy.mul( zoo ) ) ).toVar();
                const l = float( mandelbrot( c ) ).toVar();
                col.addAssign( select( l.lessThan( 0.5 ), vec3( 0.0, 0.0, 0.0 ), uContrast.add( uBrightness.mul( cos( mul( 6.28, uFrequency.mul( l.div( float( uCount ) ) ).add( uPhase ) ) ) ) ) ) );
    
            } );
    
        } );
    
        col.divAssign( AAsq );

        return vec4( col, 1.0 );
    
        //return vec4( vUv, 0, 1);
    } );

    material.fragmentNode = fragTSL();
    
    const options = {
        phase: 0x978672
    }

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

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

    scene.add( mesh );

  
}

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

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

//

function render() {
  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.