Mandelbrot set

Mandelbrot set

탈출 시간을 매끄럽게 이어 칠한 만델브로 집합.

TypeScript로 쓰고 TypeShade가 컴파일한 Mandelbrot set 셰이더. 빌드할 때 렌더링함.
// ═══ typeshade example — Mandelbrot set (smooth escape-time) ═══
//
// The fractal every shader site ports first: iterate z ← z² + c where c is the
// pixel, colour by the SMOOTH iteration count (the fractional remainder from
// log₂ log₂ |z|² kills the discrete banding), and breathe the zoom into the
// seahorse valley. Sibling to julia.ts (fixed plane, orbiting c) — here c is
// the plane and the camera moves. f32 precision bounds the useful zoom depth,
// which is exactly the limit the fp64 emulation (`fp64Lower`, and the
// relative-to-centre trick the fp64-* examples use) exists to beat.
// WGSL (WebGPU) + GLSL ES 3.00 (WebGL2).
import {
fn,
module,
u32,
f32,
vec2,
vec3,
vec4,
sin,
cos,
dot,
exp,
log2,
max,
step,
Loop,
If,
Break,
Var,
Let,
f32T,
vec4fT,
} from '../src/index.js';
import { VsOut, vs, fullscreenUniforms, screenCoords } from './_fullscreen.js';
import type { ShaderExample } from './_shared.js';
const U = fullscreenUniforms({ zoom: f32T, mouse: vec4fT });
// Iridescent cosine palette: 0.5 + 0.5·cos(2π(t + phase)).
const palette = fn('palette', { t: f32T }, ({ t }) => {
const ph = vec3(0.0, 0.33, 0.67);
return vec3(0.5).add(cos(t.add(ph).mul(6.283)).mul(0.5));
});
const fs = fn(
'fs',
{ vo: VsOut },
({ vo }) => {
const res = U.field.resolution;
const p = screenCoords(vo.uv, res);
// breathing zoom into the seahorse valley (−0.7453 + 0.1127i)
const s = Let(
exp(U.field.zoom.add(sin(U.field.time.mul(0.2)).mul(0.75).add(0.75)).neg()).mul(2.4),
);
// pointer pans the view: the pointer maps into the same isotropic space as
// p and offsets the centre, scaled by the current zoom. mu.w = 0 (never
// touched) keeps the canonical seahorse-valley framing.
const mu = U.field.mouse;
const pan = Let(
screenCoords(vec2(mu.x.div(res.x), mu.y.div(res.y)), res)
.mul(s)
.mul(mu.w),
);
const c = vec2(p.x.mul(s).sub(0.7453).add(pan.x), p.y.mul(s).add(0.1127).add(pan.y));
const z = Var(vec2(0, 0));
const it = Var(f32(0));
Loop(
u32(0),
(i) => i.lt(u32(120)),
() => {
If(dot(z, z).gt(16), () => {
Break();
}); // escaped
z.assign(vec2(z.x.mul(z.x).sub(z.y.mul(z.y)).add(c.x), z.x.mul(z.y).mul(2).add(c.y)));
it.assign(it.add(1));
},
);
// smooth iteration count — subtract the fractional escape overshoot
const m = Let(dot(z, z));
const sn = it.sub(log2(max(log2(max(m, 1.0001)), 0.0001))).add(1);
// interior (never escaped) stays black
const inside = step(119.5, it);
const col = palette({ t: sn.mul(0.035).add(U.field.time.mul(0.02)) }).mul(f32(1).sub(inside));
return vec4(col, 1);
},
{ stage: 'fragment', retAttr: '@location(0)' },
);
const mandelbrotModule = module({
structs: [U.struct, VsOut.decl],
bindings: [U.binding],
funcs: [vs, fs],
});
export const mandelbrot: ShaderExample = {
id: 'mandelbrot',
title: 'Mandelbrot set',
blurb:
'The Mandelbrot set with smooth escape-time colouring — log₂ log₂ |z|² removes the iteration banding — breathing in and out of the seahorse valley. Move the pointer to pan the view; the zoom slider sets how deep the breath goes (f32 bounds the floor).',
category: 'generic',
file: 'mandelbrot.ts',
module: mandelbrotModule,
renderable: true,
controls: {
time: { kind: 'time' },
resolution: { kind: 'resolution' },
zoom: { kind: 'slider', label: 'Zoom', min: 0, max: 4, step: 0.1, value: 1.5 },
mouse: { kind: 'mouse' },
},
};
struct Uniforms {
time: f32,
resolution: vec2<f32>,
zoom: f32,
mouse: vec4<f32>,
}
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn palette(t: f32) -> vec3<f32> {
return (vec3<f32>(0.5) + (cos(((t + vec3<f32>(0.0, 0.33, 0.67)) * 6.283)) * 0.5));
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0);
let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0);
return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));
}
@fragment
fn fs(vo: VsOut) -> @location(0) vec4<f32> {
let _v0 = (exp((-(U.zoom + ((sin((U.time * 0.2)) * 0.75) + 0.75)))) * 2.4);
let _cse0 = vec2<f32>((U.mouse.x / U.resolution.x), (U.mouse.y / U.resolution.y));
let _cse4 = (U.resolution.x / U.resolution.y);
let _v1 = ((vec2<f32>((((_cse0.x * 2.0) - 1.0) * _cse4), ((_cse0.y * 2.0) - 1.0)) * _v0) * U.mouse.w);
var _v2: vec2<f32> = vec2<f32>(0.0, 0.0);
var _v3: f32 = 0.0;
let _cse3 = vec2<f32>((((vo.uv.x * 2.0) - 1.0) * _cse4), ((vo.uv.y * 2.0) - 1.0));
let _cse1 = _cse3.x;
let _cse2 = _cse3.y;
for (var _v4: u32 = 0u; (_v4 < 120u); _v4 = (_v4 + 1u)) {
if ((dot(_v2, _v2) > 16.0)) {
break;
}
let _lc0 = vec2<f32>((((_cse1 * _v0) - 0.7453) + _v1.x), (((_cse2 * _v0) + 0.1127) + _v1.y));
_v2 = vec2<f32>((((_v2.x * _v2.x) - (_v2.y * _v2.y)) + _lc0.x), (((_v2.x * _v2.y) * 2.0) + _lc0.y));
_v3 = (_v3 + 1.0);
}
let _v5 = dot(_v2, _v2);
return vec4<f32>((palette(((((_v3 - log2(max(log2(max(_v5, 1.0001)), 0.0001))) + 1.0) * 0.035) + (U.time * 0.02))) * (1.0 - step(119.5, _v3))), 1.0);
}
#version 300 es
precision highp float;
precision highp int;
out vec2 uv;
void main() {
uint vi = uint(gl_VertexID);
float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0);
float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0);
gl_Position = vec4(_cse0, _cse1, 0.0, 1.0);
uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));
}
#version 300 es
precision highp float;
precision highp int;
layout(std140) uniform Uniforms {
float time;
vec2 resolution;
float zoom;
vec4 mouse;
} U;
vec3 palette(float t) {
return (vec3(0.5) + (cos(((t + vec3(0.0, 0.33, 0.67)) * 6.283)) * 0.5));
}
in vec2 uv;
layout(location = 0) out vec4 _ret;
void main() {
float _v0 = (exp((-(U.zoom + ((sin((U.time * 0.2)) * 0.75) + 0.75)))) * 2.4);
vec2 _cse0 = vec2((U.mouse.x / U.resolution.x), (U.mouse.y / U.resolution.y));
float _cse4 = (U.resolution.x / U.resolution.y);
vec2 _v1 = ((vec2((((_cse0.x * 2.0) - 1.0) * _cse4), ((_cse0.y * 2.0) - 1.0)) * _v0) * U.mouse.w);
vec2 _v2 = vec2(0.0, 0.0);
float _v3 = 0.0;
vec2 _cse3 = vec2((((uv.x * 2.0) - 1.0) * _cse4), ((uv.y * 2.0) - 1.0));
float _cse1 = _cse3.x;
float _cse2 = _cse3.y;
for (uint _v4 = 0u; (_v4 < 120u); _v4 = (_v4 + 1u)) {
if ((dot(_v2, _v2) > 16.0)) {
break;
}
vec2 _lc0 = vec2((((_cse1 * _v0) - 0.7453) + _v1.x), (((_cse2 * _v0) + 0.1127) + _v1.y));
_v2 = vec2((((_v2.x * _v2.x) - (_v2.y * _v2.y)) + _lc0.x), (((_v2.x * _v2.y) * 2.0) + _lc0.y));
_v3 = (_v3 + 1.0);
}
float _v5 = dot(_v2, _v2);
_ret = vec4((palette(((((_v3 - log2(max(log2(max(_v5, 1.0001)), 0.0001))) + 1.0) * 0.035) + (U.time * 0.02))) * (1.0 - step(119.5, _v3))), 1.0);
}

Mandelbrot set. 빌드할 때 그린 화면입니다.

WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).

이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.

GitHub의 파일

이 페이지 편집 문제 보고