#### <sup>[Ollin](../../index.md) → [Documentation](../index.md) → [Shaders](index.md) → `Shader library`</sup>

---

## The shader library

Ollin splices its shader library into every [user-supplied shader](shaders.md) at compile time, so you can call these helpers from inside `shade(uv, info)` with no `#include`. They are the same helpers Ollin's own shaders use, so there is one source of truth. Each one is written from the published technique and credited in the [Techniques list](../../attribution.md#techniques).

[Compute kernels](compute.md) get the same library in full, so a helper you learn here works the same way in a kernel. The `using:` option is a fragment-`Shader` option only, because a kernel always sees everything.

By default the whole library is available. Unused helpers are removed as dead code, so on the GPU the choice costs nothing either way. Restrict the library with the `using:` option to cut compile time:

```swift
let s = Shader(source, using: [.noise, .sdf])   // only these sections splice
```

| Module | What it covers | Always on? |
| --- | --- | --- |
| (base) | color conversion, luminance, 2D rotation | yes |
| `.color` | cosine palette, OKLab / OKLCH, thin-film interference | no |
| `.hash` | integer-free pseudo-random hashes, disc sampling | no |
| `.noise` | value / FBM / gradient / simplex / Worley / curl noise, ridged / turbulence / warped fbm (depends on `.hash`) | no |
| `.sdf` | smooth-min and the 2D signed-distance catalog | no |
| `.domain` | repeat / mirror / polar-fold space operators | no |
| `.complex` | complex arithmetic on a `float2` (`cmul`, `cdiv`, `cexp`, `clog`, `cpow`, the trigonometric set), the `complexPlane` framing, and `domainColor` (depends on `.color`) | no |

### Contents

- [Color (base)](#color-base)
- [Palettes & perceptual color](#palettes-perceptual-color)
- [Hashing](#hashing)
- [Noise](#noise)
- [Signed-distance functions](#signed-distance-functions)
- [Domain operators](#domain-operators)
- [Complex numbers](#complex-numbers)
- [Visual-chain operations](#visual-chain-operations)
- [Chaotic systems (compute only)](#chaotic-systems-compute-only)

---

## Color (base)

These are always available. The render pipeline composites in linear light, so they convert between the sRGB values you author and the linear values that blend correctly.

| Function | Description |
| --- | --- |
| `float3 srgbToLinear(float3 c)` | sRGB → linear RGB. |
| `float3 linearToSrgb(float3 c)` | linear RGB → sRGB (clamped to 0…1). |
| `float perceptualCoverage(float c)` | remap anti-aliasing coverage so a thin dark mark reads evenly dark in linear light (for hand-rolled AA). |
| `unipolar(v)` | a signed `-1…1` value read as a `0…1` amount (`v * 0.5 + 0.5`), for `float` through `float4`. Apply it to `sin` or `cos` before the result drives a mix, a brightness, or a size. |
| `bipolar(v)` | the inverse, which maps a `0…1` fraction onto `-1…1`, for `float` through `float4`. |

A `shade` function returns straight sRGB and Ollin handles the linear conversion, so you only need these functions for your own color math.

## Palettes & perceptual color

`using: .color`. OKLab math assumes non-negative linear RGB.

| Function | Description |
| --- | --- |
| `float luma(float3 c)` | Rec. 709 luminance of a linear color. |
| `float3 palette(float t, float3 a, float3 b, float3 c, float3 d)` | cosine gradient palette, `a + b·cos(2π(c·t + d))`. It turns one scalar into a range of procedural color. |
| `float3 linearToOklab(float3 c)` | linear RGB → OKLab (perceptual lightness/a/b). |
| `float3 oklabToLinear(float3 lab)` | OKLab → linear RGB. |
| `float3 oklabToOklch(float3 lab)` | OKLab → OKLCH (lightness, chroma, hue). |
| `float3 oklchToOklab(float3 lch)` | OKLCH → OKLab. |
| `float3 thinFilm(float cosTheta, float3 baseF0, float filmIor, float thicknessNm)` | the color a clear film of that thickness (in nanometers) makes over a surface reflecting `baseF0`, seen at that view cosine. Light reflects off both faces of the film and the two waves meet out of step, so some colors add and others cancel. You see this on a soap bubble, an oil slick, and anodized metal. |
| `float3 thinFilmF0(float3 filmReflectance, float cosTheta)` | that reflectance turned back into the straight-on value that would produce it, for a term that takes an F0 rather than a single ray. |

To mix in OKLab or OKLCH, interpolate there and then convert back. That gives you even lightness and clean hue sweeps, which linear-RGB mixing cannot.

`thinFilm` returns a reflectance, not a finished color, so multiply it by whatever the surface is reflecting. The color moves with `cosTheta`, which is what produces the effect. If your sketch wants bands across a shape, feed `cosTheta` a coordinate rather than a constant. The [3D material](../3d/3d.md#thin-film) is the same model wired into the physically based finish.

## Hashing

`using: .hash`. These give pseudo-random values from a coordinate, in `[0, 1)`, with no texture lookup. The name is `hashNM`, where `N` is the number of output channels and `M` is the number of seed components.

| Function | Description |
| --- | --- |
| `float hash11(float p)` | one channel from a `float`. |
| `float hash12(float2 p)` | one channel from a `float2`. |
| `float hash13(float3 p3)` | one channel from a `float3`. |
| `float2 hash22(float2 p)` | two channels from a `float2`. |
| `float3 hash33(float3 p3)` | three channels from a `float3`. |
| `float2 discSample(float2 seed)` | a point in the unit disc, uniform over its *area*. The radius comes from a square root, so samples do not bunch at the center, which is the scatter energy-conserving bokeh needs. |
| `float3 ballSample(float3 seed)` | a point in the unit ball, uniform over its *volume*. The radius comes from a cube root and the direction is uniform over the sphere, which is the three-dimensional scatter a depth-of-field lens applies to a sample in camera space. |

## Noise

`using: .noise`, which pulls in `.hash`. Value noise reads in about `[0, 1]`, and gradient noise in about `[-1, 1]`.

| Function | Description |
| --- | --- |
| `float valueNoise(float2 p)` | smoothed interpolation of per-cell hashes. |
| `float valueNoise(float3 p)` | the 3D form (trilinear). Animate it by sliding `z`. |
| `float fbm(float2 p)` | four-octave fractal sum of `valueNoise`. |
| `float fbm(float3 p)` | the 3D form, over the 3D `valueNoise`. Animate it by sliding `z`. Mirrors the CPU `fbm(x, y, z)`. |
| `float gradientNoise(float2 p)` | Perlin-style gradient noise (smoother, signed). |
| `float simplexNoise(float2 p)` | simplex-lattice gradient noise in about `[-1, 1]`, with a rounder, more even grain and no axis-aligned bias. Mirrors the CPU `simplexNoise`. |
| `float simplexNoise(float3 p)` | the 3D form. Animate it by sliding `z`. |
| `float worley(float2 p[, float jitter])` | cellular noise, the distance to the nearest hashed feature point, in about `[0, 1]`, with dark cell cores and bright walls. `jitter` runs the cells from grid (0) to organic (1, the default). Mirrors the CPU `worley`. |
| `float2 worley2(float2 p[, float jitter])` | the nearest *and* second-nearest distances. Their difference is zero on the borders between cells, so threshold it for cracks and veins. |
| `float worley(float3 p[, float jitter])`, `float2 worley2(float3 p[, float jitter])` | the 3D forms. Slide `z` and the cells bubble and reform. |
| `float gaborNoise(float2 p, float wavelength, float bandwidth[, float angle, float spread[, float impulses, float phase, uint seed]])` | Gabor noise in `[0, 1]`: sparse Gabor convolution, a field with one principal `wavelength` (in the units of `p`, pixels on a layer), a band `bandwidth` wide around it as a fraction, and one direction (`angle`, with `spread` 0) or every direction (`spread` π, the short form). `impulses` is the quality (32 in the short forms), `phase` slides the waves (periodic over 2π). Filtered for a one-pixel footprint. Mirrors the CPU `gaborNoise` and the `.gaborNoise` generator. |
| `float ridgedFbm(float2 p)` | four octaves folded into creases, with detail gathering on the ridge lines. This is the basis for mountainous terrain, in `[0, 1]`. Mirrors the CPU `ridgedFbm`. |
| `float turbulence(float2 p)` | four octaves of folded (absolute-value) noise, which gives billows with creased seams, in `[0, 1]`. This is the basis for clouds and marble. Mirrors the CPU `turbulence`. |
| `float warpedFbm(float2 p, float warp)` | domain-warped fbm, where the field displaces its own coordinates twice over. A `warp` of 0 is exactly `fbm(p)`, and 1 is the classic strength. Mirrors the CPU `warpedFbm`. The `DomainWarp` example takes the recipe apart. |
| `float2 curlNoise(float2 p)` | the divergence-free curl of a value-noise potential, a flow field whose streams swirl and never converge into sinks. |
| `float3 curlNoise(float3 p)` | the same in space: the curl of three offset copies of the 3D value noise, a flow that swirls in every direction and never gathers. Mirrors the CPU `curlNoise(x, y, z)`. |
| `float chladni(float2 p, float m, float n)` | the Chladni standing-wave field of a square plate over plate coordinates `0…1`, in `[-1, 1]`. Sand gathers on the zero set. An `(a, b)` overload mixes the two mirrored modes unevenly. Mirrors the CPU `chladni`. See [Chladni figures](../generators/chladni.md). |

## Signed-distance functions

`using: .sdf`. Each function returns the signed distance to a shape's outline in local units, negative inside and positive outside. The open shapes `sdSegment`, `sdArc`, and `sdBezier` return an unsigned distance instead. Combine the shapes with `smin` and the [domain operators](#domain-operators). To turn a distance into an anti-aliased edge, use `fwidth` (`smoothstep(fw, -fw, d)`). These are the canonical 2D distance functions. A few of them take precomputed `(sin, cos)` angle vectors, which is the form the originals use.

| Function | Shape |
| --- | --- |
| `float smin(float a, float b, float k)` | smooth minimum of two distances over radius `k`, the melt/blend operator. As `k` approaches 0 it becomes a hard `min`. |
| `float sdEllipse(float2 p, float2 ab)` | ellipse with radii `ab` (exact circle when `ab.x == ab.y`). |
| `float sdRoundBox(float2 p, float2 b, float r)` | box of half-size `b`, corners rounded by `r`. |
| `float sdOrientedBox(float2 p, float2 a, float2 b, float th)` | box spanning endpoints `a`→`b` with thickness `th`. |
| `float sdSegment(float2 p, float2 a, float2 b)` | line segment `a`→`b` (unsigned). |
| `float sdPie(float2 p, float2 sc, float r)` | pie wedge of radius `r`, where `sc` is the `(sin, cos)` of the half-angle. |
| `float sdArc(float2 p, float2 sc, float ra, float rb)` | arc of radius `ra` and thickness `rb`, where `sc` is the `(sin, cos)` of the half-aperture. |
| `float sdTriangleIsosceles(float2 p, float2 q)` | isosceles triangle, `q` = `(half-width, height)`. |
| `float sdStar(float2 p, float r, float2 acs, float2 ecs, float an)` | regular star of radius `r` (precomputed-angle form). |
| `float sdRhombus(float2 p, float2 b)` | rhombus with half-diagonals `b`. |
| `float sdCross(float2 p, float2 b, float r)` | plus/cross of arm extents `b`, rounded by `r`. |
| `float sdVesica(float2 p, float r, float d)` | vesica lens, arc radius `r`, half-separation `d`. |
| `float sdOrientedVesica(float2 p, float2 a, float2 b, float w)` | vesica spanning `a`→`b` of width `w`. |
| `float sdMoon(float2 p, float d, float ra, float rb)` | crescent, which is disk `ra` minus disk `rb` offset by `d`. |
| `float sdTrapezoid(float2 p, float r1, float r2, float he)` | trapezoid, base radii `r1`/`r2`, half-height `he`. |
| `float sdParallelogram(float2 p, float wi, float he, float sk)` | parallelogram, half-width `wi`, half-height `he`, skew `sk`. |
| `float sdEgg(float2 p, float ra, float rb)` | egg, radii `ra`/`rb`. |
| `float sdHeart(float2 p)` | unit heart. |
| `float sdCutDisk(float2 p, float r, float h)` | disk of radius `r` cut by a chord at height `h`. |
| `float sdUnevenCapsule(float2 p, float r1, float r2, float h)` | capsule with end radii `r1`/`r2` over length `h`. |
| `float sdHorseshoe(float2 p, float2 c, float r, float2 w)` | horseshoe, where `c` is the `(cos, sin)` of the opening, `r` the radius, and `w` the thickness. |
| `float sdParabolaSegment(float2 pos, float wi, float he)` | parabola segment, half-width `wi`, height `he`. |
| `float sdRoundedX(float2 p, float w, float r)` | rounded X, arm length `w`, rounding `r`. |
| `float sdBlobbyCross(float2 pos, float he)` | blobby four-lobe cross of size `he`. |
| `float sdTunnel(float2 p, float2 wh)` | tunnel (flat-bottomed arch), half-size `wh`. |
| `float sdStairs(float2 p, float2 wh, float n)` | staircase of `n` steps of size `wh`. |
| `float sdCoolS(float2 p)` | the "cool S". |
| `float sdTriangle(float2 p, float2 a, float2 b, float2 c)` | triangle through points `a`, `b`, `c`. |
| `float sdBezier(float2 pos, float2 A, float2 B, float2 C, thread float &outT)` | quadratic Bézier `A`→`B`→`C` (unsigned). `outT` returns the nearest curve parameter. |

## Domain operators

`using: .domain`. These transform the point a field is evaluated at. The tiling operators change the point in place and return the cell or side index. A shape evaluated at the transformed point then tiles or reflects across space, so you never evaluate it once per copy. Rotation works differently, because it takes a point and returns another one.

| Function | Description |
| --- | --- |
| `float2 rotate2D(float2 p, float a)` | rotate a 2D point by `a` radians. |
| `float pmod(thread float &p, float s)` | repeat one axis with period `s`, in centered cells. Returns the cell index. |
| `float2 pmod2(thread float2 &p, float2 s)` | repeat both axes with per-axis period `s`. Returns the cell. |
| `float mirror(thread float &p, float d)` | mirror across the plane at distance `d` from the origin. Returns the original side (`±1`). |
| `float pmodPolar(thread float2 &p, float n)` | fold space into `n` wedges around the origin, a radial repeat. Returns the wedge index. |

This example draws a ring of one shape:

```metal
float4 shade(float2 uv, ShaderInfo info) {
    float2 p = (uv - 0.5) * 8.0;
    pmodPolar(p, 8.0);              // 8-fold radial fold
    p.x -= 2.5;                     // push the cell out to a radius
    float d = sdHeart(p * 1.5) / 1.5;
    float e = fwidth(d);
    float cov = smoothstep(e, -e, d);
    return float4(palette(0.6, float3(0.5), float3(0.5),
                          float3(1.0), float3(0.0, 0.1, 0.2)) * cov, 1.0);
}
```

---

## Complex numbers

`using: .complex` (pulls in `.color` for the wheel). A `float2` read as a complex number, `x` the real part and `y` the imaginary part. A point of the plane is then a number, and the functions below are what a plain `*` cannot do: multiplying multiplies the lengths and *adds the angles*. The names follow C's own complex library, with `cmul` and `cdiv` for the two operators C never had to name. Every multi-valued function takes the principal branch, the argument in `(-pi, pi]`, so the seam along the negative real axis is real and shown. Each mirrors the CPU [`Complex`](../helpers/complex.md) value of the same meaning. A curve worked out in `draw()` lands on the pixels a shader paints.

| Function | Description |
| --- | --- |
| `float2 cmul(float2 a, float2 b)` | the product. |
| `float2 cdiv(float2 a, float2 b)` | the quotient. |
| `float2 conj(float2 z)` | the mirror image across the real axis. |
| `float2 cinv(float2 z)` | `1 / z`. |
| `float cabs(float2 z)` | the modulus, `\|z\|` (the same as `length`). |
| `float carg(float2 z)` | the argument, in radians. |
| `float2 cpolar(float r, float angle)` | the number at distance `r`, turned `angle` radians: `cpolar(1.0, a)` turns whatever it multiplies by `a`. |
| `float2 cexp(float2 z)` | `e^z`. |
| `float2 clog(float2 z)` | the natural logarithm, `(log\|z\|, arg z)`. |
| `float2 cpow(float2 z, float n)` | `z` to a real power; a whole `n` winds cleanly, a fraction shows the seam. |
| `float2 cpow(float2 z, float2 w)` | `z` to a complex power, `exp(w·log z)`. |
| `float2 csqrt(float2 z)` | the principal square root. |
| `float2 csin(float2 z)`, `ccos`, `ctan` | the trigonometric functions. |
| `float2 csinh(float2 z)`, `ccosh`, `ctanh` | the hyperbolic functions. |
| `float2 complexPlane(float2 uv, float2 resolution, float2 center, float span)` | the plane under a layer's `uv`: `center` in the middle, `span` units across the shorter side, the imaginary axis **up** (a `uv` runs down the canvas, so it is negated once here). Every built-in generator that paints the plane frames it the same way, with `span = 3 / zoom`, and [`ComplexPlane`](../helpers/complex.md#canvas) is the same framing as a value on the CPU. |
| `float3 domainColor(float2 f, int shading, float strength)` | a value painted the way a [domain coloring](../drawing/effects.md#generate) paints it: the direction picks a hue off a perceptual wheel (OKLCH at one lightness, so no hue reads brighter than another), and `shading` adds what a hue cannot say. `0` is the plain phase portrait; `1` rules the size, a band from dark to light between one doubling of `\|f\|` and the next; `2` rules the direction as well, twelve sectors to the turn, so away from the zeros and poles the two rulings cross in the little squares that make "conformal" visible. `strength` (`0…1`) is how dark the rulings go. Returns straight sRGB, ready to return from `shade`. |
| `float3 domainColor(float2 f)` | the plain phase portrait. |

The transcendentals clamp their exponent, so a far-out pixel saturates instead of returning an infinity that `atan2` cannot take a direction from.

```metal
float4 shade(float2 uv, ShaderInfo info) {
    float2 z = complexPlane(uv, info.resolution, float2(0.0), 3.0);
    float2 p = cpolar(0.8, info.time), q = cpolar(0.8, info.time + 2.3);
    float2 f = cdiv(z - p, z - q);              // a zero at p, a pole at q
    return float4(domainColor(f, 2, 0.7), 1.0);
}
```

## Visual-chain operations

`using: .visual`, which pulls in `.hash` and `.noise`. These are the per-pixel sources, coordinate warps, HSV color adjustments, and two-input blends behind [`Visual` chains](visuals.md). You can call them from a hand-written shader too. Colors are straight sRGB. An operation that would distort on a non-square canvas takes an `aspect` (width / height) and corrects for it.

| Function | Description |
| --- | --- |
| `float4 ollin_vis_osc(float2 st, float f, float speed, float shift, float time, float aspect)` | sine bands, `f` waves across, drifting. `shift` fringes the channels. |
| `float4 ollin_vis_noise(float2 st, float scale, float speed, float time, float aspect)` | evolving value-noise field, signed (`-1…1`). |
| `float4 ollin_vis_voronoi(float2 st, float scale, float speed, float blending, float time, float aspect)` | animated cells, hash-gray each, darkened toward borders. |
| `float4 ollin_vis_shape(float2 st, float sides, float radius, float smoothing, float aspect)` | soft-edged regular polygon, centered, vertex up. The alpha channel carries the shape. |
| `float4 ollin_vis_gradient(float2 st, float speed, float time)` | red is x, green is y, and blue varies with time. |
| `float2 ollin_vis_rotate(float2 st, float2 center, float angle, float aspect)` | rotate the sampling coordinate, aspect-true. |
| `float2 ollin_vis_scale(float2 st, float2 center, float amount, float2 axis)` | zoom about `center` (per-axis `axis` multipliers). |
| `float2 ollin_vis_pixelate(float2 st, float2 cells)` | snap to a cell grid, sampling cell centers. |
| `float2 ollin_vis_repeat(float2 st, float2 reps, float2 offset)` | tile, with a per-row/column stagger. |
| `float2 ollin_vis_kaleid(float2 st, float2 center, float sides, float radiusShift, float aspect)` | fold into mirrored wedges. `radiusShift` warps the fold. |
| `float2 ollin_vis_scroll(float2 st, float2 offset, float2 speed, float time)` | translate, drifting, wrapping. |
| `float4 ollin_vis_brightness/contrast/saturate/invert(float4 c, float amount)` | the basic adjustments. |
| `float4 ollin_vis_posterize(float4 c, float bins, float gamma)` | quantized levels in a gamma-lifted space. |
| `float4 ollin_vis_threshold(float4 c, float t, float tol)` | black/white split about a luminance. |
| `float4 ollin_vis_luma(float4 c, float t, float tol)` | luminance keying, where the dark side becomes transparent. |
| `float4 ollin_vis_hueShift(float4 c, float amount)` | rotate the hue (fraction of the wheel). |
| `float4 ollin_vis_colorCycle(float4 c, float amount)` | wrap-around HSV crawl. |
| `float4 ollin_vis_tint(float4 c, float4 tint)` | multiply by a color. |
| `float4 ollin_vis_channel(float4 c, int sel, float scale, float offset)` | broadcast channel `sel` (0 r, 1 g, 2 b, 3 a, 4 luma) as gray. |
| `float3 ollin_vis_rgb2hsv(float3 c)` / `ollin_vis_hsv2rgb` | the hexcone conversions. |
| `float4 ollin_vis_over(float4 a, float4 b)` | alpha-over compositing. |
| `float4 ollin_vis_blend(float4 a, float4 b, int mode, float amount)` | blend by selector (0 over, 1 add, 2 subtract, 3 multiply, 4 screen, 5 lightest, 6 darkest), faded by `amount`. |
| `float4 ollin_vis_difference(float4 a, float4 b)` | absolute per-channel difference. |
| `float4 ollin_vis_mask(float4 a, float4 b)` | keep `a` where `b` is bright and opaque. |

## Spatial-hash neighbor search (compute only)

These are always available in a **compute kernel**. They take bound buffers, so they are not part of the `using:` fragment-shader subset. They are the primitives behind [`SpatialHash`](compute.md#spatialhash) and the [artificial-life sims](../simulation/artificiallife.md), and `OllinSpatialGrid` is the shared grid struct.

| Function | Description |
| --- | --- |
| `int2 ollin_grid_coord(float2 pos, OllinSpatialGrid g)` | wrapped integer cell coordinate of a world position (positive modulo, edges join). |
| `uint ollin_grid_cell(float2 pos, OllinSpatialGrid g)` | flat (row-major) cell index of a position, the counting-sort bin. |
| `float2 ollin_torus_delta(float2 from, float2 to, float2 worldSize)` | shortest displacement on the torus (minimum image), for wrap-correct distances. |
| `OLLIN_FOR_NEIGHBORS(pos, grid, sorted, start, count, j)` … `OLLIN_END_NEIGHBORS` | iterate the neighbors of `pos`, the 3×3 wrapped cell block. `j` is each neighbor's particle index. |

## Chaotic systems (compute only)

These are always available in a **compute kernel**. Like the neighbor search, they sit outside the `using:` fragment-shader subset. They are the velocity fields behind [`AttractorFlow`](../drawing/attractors.md#flow) and the classic iterated maps, written from their published equations. A kernel of your own can use them to run a chaotic system directly.

A **flow** returns the derivative at a phase-space point, and you advance it with `OLLIN_RK4_STEP`. A **map** returns the next point directly and needs no integration.

| Function | Description |
| --- | --- |
| `float3 ollin_lorenz(float3 p, float sigma, float rho, float beta)` | the original two-lobed butterfly. |
| `float3 ollin_rossler(float3 p, float a, float b, float c)` | a single folded band. |
| `float3 ollin_aizawa(float3 p, float a, float b, float c, float d, float e, float f)` | a spiralling sphere-and-spindle. |
| `float3 ollin_thomas(float3 p, float b)` | a looping, axis-symmetric lattice walk. |
| `float3 ollin_halvorsen(float3 p, float a)` | three intertwined scrolls. |
| `float3 ollin_dadras(float3 p, float a, float b, float c, float d, float e)` | a four-winged twist. |
| `float3 ollin_chen(float3 p, float alpha, float beta, float delta)` | a tightly wound double scroll. |
| `float3 ollin_four_wing(float3 p, float a, float b, float c)` | four lobes meeting at the center. |
| `float2 ollin_clifford(float2 p, float a, float b, float c, float d)` | Clifford's map, fine trigonometric threads within roughly ±2. |
| `float2 ollin_de_jong(float2 p, float a, float b, float c, float d)` | the Peter de Jong map, from the same family. |
| `float2 ollin_henon(float2 p, float a, float b)` | the Hénon map, a thin folded curve. |
| `OLLIN_RK4_STEP(state, h, derivative)` | advance a `float3` one fixed step of fourth-order Runge-Kutta. |

`derivative` is an expression in the sample point `_p`, which is how a system's constants reach it. Metal has no function pointers here, so this is a macro, like the neighbor iteration:

```metal
OLLIN_RK4_STEP(state, 0.01, ollin_lorenz(_p, 10.0, 28.0, 8.0 / 3.0));
```

Keep the step near the one the system was published with. A step much larger than the system's own scale integrates a different system, so take several small steps rather than one big one.

---

### See also

- [Shaders](shaders.md): the `Shader` type, the `shade(uv, info)` contract, and running a shader as a generator/filter/combine
- [Visuals](visuals.md): the fluent `Visual` chains these operations render
- [SDF combinators](../drawing/combinators.md): compose signed-distance fields into merged shapes without writing shader code
- [Color](../drawing/color.md): the CPU-side `Color`, `Palette`, and OKLab family
- [Strange attractors](../drawing/attractors.md): the CPU orbits and the `AttractorFlow` these fields drive
