# Julia Sets: Turn the Dial

*Task 5 of 5 · [Escape-Time Fractals](https://gpu.rocks/learn/escape-time-fractals-0de4764c.md) · GPU.js Learn*

Here's the payoff. Take the exact loop you've built and **flip the
roles**: in a Julia set, z starts *at the pixel* and `c` is one
fixed complex number shared by every thread. Each choice of c is a different fractal —
`c = 0` gives a plain disk, `−0.7269 + 0.1889i` a galaxy of spirals —
and the Mandelbrot set turns out to be the map of which c values give connected Julias.

Because c arrives as **kernel arguments**, changing it costs one function
call — no recompiling. That's what makes those mesmerizing morphing-Julia animations:
nudge c, redraw, repeat.

## Figures

- **same loop, roles swapped — mandelbrot varies c, julia varies z₀**

## Goal

**Goal:** a graphical Julia kernel over the fixed view −1.6…1.6: seed
`z` from the pixel, add the arguments `cRe, cIm` each step, and keep
task 4's smooth shading (interior black).

## Requirements

- Seed z from the pixel: `zr = xMin + x·step`, `zi = yMin + y·step` (constants are wired up)
- Inside the loop, add `cRe` and `cIm` — not the pixel coordinates
- Escaped: shade with `t = smooth / 100` through the same three-cosine palette as the last task; interior: black
- Call the kernel with a c of your choice and `render()` it

## Hint 1 — what actually changes

Two lines. Mandelbrot: z starts at 0 and c is the pixel. Julia: z starts at the
pixel and c is the argument pair. The loop body, the guard, the shading — all identical.

## Hint 2 — the exact edits

Seed with

```js
let zr = this.constants.xMin + x * this.constants.step;
```

(and likewise `zi` from y), then inside the loop use
`… + cRe` and `… + cIm` instead of `px` / `py`.

## Same idea elsewhere

A per-launch value broadcast to every thread is what other APIs call a
*uniform*: a WGSL uniform buffer, a Metal constant buffer, a plain CUDA kernel
parameter. Animating one uniform per frame — exactly your c — is how every shader-toy
Julia morph is driven.

## Starter code

```js
// Same loop, roles flipped: the pixel is z₀, and c is a knob you turn.
const gpu = new GPU({ mode });

const julia = gpu.createKernel(function (cRe, cIm) {
  const x = this.thread.x;
  const y = this.thread.y;
  const px = this.constants.xMin + x * this.constants.step;
  const py = this.constants.yMin + y * this.constants.step;
  // TODO: this is still the Mandelbrot arrangement — z from 0, pixel as c.
  // Flip it: seed z from (px, py), and add cRe / cIm inside the loop.
  let zr = 0;
  let zi = 0;
  let count = 0;
  for (let i = 0; i < 100; i++) {
    if (zr * zr + zi * zi < 4) {
      const zrNext = zr * zr - zi * zi + px;
      zi = 2 * zr * zi + py;
      zr = zrNext;
      count = count + 1;
    }
  }
  if (count < 100) {
    const smooth = count + 1 - Math.log2(0.5 * Math.log2(zr * zr + zi * zi));
    const t = smooth / 100;
    const a = 6.28318 * t;
    this.color(0.5 + 0.5 * Math.cos(a), 0.5 + 0.5 * Math.cos(a + 2.0944), 0.5 + 0.5 * Math.cos(a + 4.18879), 1);
  } else {
    this.color(0, 0, 0, 1);
  }
}, {
  output: [128, 128],
  graphical: true,
  constants: { xMin: -1.6, yMin: -1.6, step: 0.025 },
});

// A real dial. slider() returns the value this run is using and puts a control
// under the console; moving it re-runs the whole program, so the set redraws as
// you drag. Defaults are the classic dendrite c = -0.7269 + 0.1889i.
const cRe = slider('c real', { min: -1, max: 0.4, value: -0.7269, step: 0.001 });
const cIm = slider('c imag', { min: -0.8, max: 0.8, value: 0.1889, step: 0.001 });

await julia(cRe, cIm);
render(julia.canvas);
```

---

Interactive version: https://gpu.rocks/learn/escape-time-fractals-0de4764c/5

[Previous task](https://gpu.rocks/learn/escape-time-fractals-0de4764c/4.md)
