The shader library
Ollin splices its shader library into every user-supplied shader 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.
Compute kernels 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:
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)
- Palettes & perceptual color
- Hashing
- Noise
- Signed-distance functions
- Domain operators
- Complex numbers
- Visual-chain operations
- 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 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. |
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. 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:
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 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, `\ |
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\ |
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 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 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 `\ |
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.
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. 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 and the artificial-life sims, 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 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:
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: the
Shadertype, theshade(uv, info)contract, and running a shader as a generator/filter/combine - Visuals: the fluent
Visualchains these operations render - SDF combinators: compose signed-distance fields into merged shapes without writing shader code
- Color: the CPU-side
Color,Palette, and OKLab family - Strange attractors: the CPU orbits and the
AttractorFlowthese fields drive