<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);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.