top of page
Welcome to the Stash
// Octane OSL Camera: VHS-ish lens warp (wobble + line jitter + mild barrel)
// Notes:
// - Avoids 'fract' by defining our own.
// - Avoids 'time' name collision by using 'animTime'.
// - Uses camera basis from globals P (pos), I (forward), N (up) per Octane docs.
// https://docs.otoy.com/osl/camera/(https://docs.otoy.com/osl/camera/)
float fractf(float x)
{
return x - floor(x);
}
float hash1(float n)
{
// Simple 1D hash -> 0..1
return fractf(sin(n) * 43758.5453123);
}
shader OslCameraVHS(
// If an input is named exactly "fov", Octane treats it as field of view. :contentReference[oaicite:0]{index=0}
float fov = 45.0 [[ float min = 1.0, float max = 179.0 ]],
// Renamed to avoid collision with Octane's internal 'time'
float animTime = 0.0 [[ float min = 0.0, float max = 10000.0 ]],
// Warp controls
float wobbleAmp = 0.010 [[ float min = 0.0, float max = 0.10 ]],
float wobbleFreq = 18.0 [[ float min = 0.0, float max = 200.0 ]],
float wobbleSpeed = 1.5 [[ float min = 0.0, float max = 20.0 ]],
float lineJitterAmp = 0.006 [[ float min = 0.0, float max = 0.10 ]],
float lineJitterSpeed = 6.0 [[ float min = 0.0, float max = 60.0 ]],
float lineCount = 240.0 [[ float min = 10.0, float max = 2000.0 ]],
// Lens distortion (barrel if negative, pincushion if positive)
float k1 = -0.08 [[ float min = -1.0, float max = 1.0 ]],
float k2 = 0.02 [[ float min = -1.0, float max = 1.0 ]],
// Optional vignette via initial throughput
float vignette = 0.25 [[ float min = 0.0, float max = 2.0 ]],
output point pos = 0,
output vector dir = 0,
output float tMax = 1.0/0.0
)
{
// Camera basis from Octane OSL camera globals. :contentReference[oaicite:1]{index=1}
pos = P;
vector up = N;
vector right = cross(I, up);
// Pixel aspect + resolution for correct film-plane scaling. :contentReference[oaicite:2]{index=2}
float pa = 1.0;
int res[2] = {1920, 1080};
getattribute("camera:pixelaspect", pa);
getattribute("camera:resolution", res);
// Film plane coords centered around 0.
float x = 2.0 * (u - 0.5);
float y = 2.0 * (v - 0.5);
// Apply aspect ratio (keep shapes circular on non-square frames)
float aspect = float(res[0]) / max(1.0, float(res[1]));
x *= aspect;
y *= pa;
// --- VHS-ish horizontal wobble (varies with y) ---
x += wobbleAmp * sin(y * wobbleFreq + animTime * wobbleSpeed);
// --- Per-scanline jitter (stepped by scanline index) ---
float line = floor(v * lineCount);
// Quantize time so jitter "steps" a bit like tape instability
float tStep = floor(animTime * lineJitterSpeed);
float j = hash1(line * 19.19 + tStep * 7.31) * 2.0 - 1.0; // -1..1
x += j * lineJitterAmp;
// --- Barrel distortion on film plane ---
float r2 = x*x + y*y;
float s = 1.0 + k1*r2 + k2*r2*r2;
x *= s;
y *= s;
// --- FOV mapping (simple pinhole) ---
float fovRad = radians(clamp(fov, 1.0, 179.0));
float f = 1.0 / tan(0.5 * fovRad);
// Ray direction. :contentReference[oaicite:3]{index=3}
dir = f * I + x * right + y * up;
// --- Vignette via initial throughput (optional) ---
if (vignette > 0.0)
{
float vig = exp(-vignette * r2);
setmessage("octane:throughput", color(vig, vig, vig));
}
}
// Octane OSL Camera: Panini Projection (architecture-friendly wide FOV)
// Struct-free version: uses vector (x,y) instead of point2.
//
// Panini inverse mapping adapted from Unity URP PaniniProjection shader math,
// rewritten to scalar form.
//
// Controls:
// - d: Panini strength (0 = rectilinear, ~0.6-0.9 good for architecture, 1 = strong)
// - paniniScale: manual crop/fit
// - fov: camera FOV (Octane treats param named exactly "fov" specially)
float safe_sqrt(float x) { return sqrt(max(x, 0.0)); }
// Inverse mapping for the special case d == 1
vector Panini_UnitDistance_xy(float vx, float vy)
{
// Returns (px, py, 0) in rectilinear projected plane
float view_dist = 2.0;
float view_dist_sq = 4.0;
float view_hyp = sqrt(vx*vx + view_dist_sq);
float cyl_hyp = view_hyp - (vx*vx) / view_hyp;
float cyl_hyp_frac = cyl_hyp / view_hyp;
float cyl_dist = view_dist * cyl_hyp_frac;
float cx = vx * cyl_hyp_frac;
float cy = vy * cyl_hyp_frac;
float denom = (cyl_dist - 1.0);
// Avoid divide-by-zero (shouldn't happen for valid inputs, but safe)
denom = (abs(denom) < 1e-8) ? ((denom < 0.0) ? -1e-8 : 1e-8) : denom;
return vector(cx / denom, cy / denom, 0.0);
}
// Generic inverse mapping for d != 1
vector Panini_Generic_xy(float vx, float vy, float d)
{
float view_dist = 1.0 + d;
float view_hyp_sq = vx*vx + view_dist*view_dist;
float isect_D = vx * d;
float isect_discr = view_hyp_sq - isect_D*isect_D;
float denom = (view_hyp_sq < 1e-8) ? 1e-8 : view_hyp_sq;
float cyl_dist_minus_d =
(-isect_D * vx + view_dist * safe_sqrt(isect_discr)) / denom;
float cyl_dist = cyl_dist_minus_d + d;
float cx = vx * (cyl_dist / view_dist);
float cy = vy * (cyl_dist / view_dist);
float denom2 = (cyl_dist - d);
denom2 = (abs(denom2) < 1e-8) ? ((denom2 < 0.0) ? -1e-8 : 1e-8) : denom2;
return vector(cx / denom2, cy / denom2, 0.0);
}
shader OslCameraPanini(
float fov = 100.0 [[ float min = 1.0, float max = 179.0 ]],
// Panini strength. 0 = normal pinhole camera, ~0.6-0.9 = nice architecture.
float d = 0.75 [[ float min = 0.0, float max = 2.0 ]],
// Manual fit/crop. Lower to "zoom out" and keep more edges.
float paniniScale = 1.0 [[ float min = 0.25, float max = 2.0 ]],
// Optional vignette via throughput (0 off)
float vignette = 0.0 [[ float min = 0.0, float max = 2.0 ]],
output point pos = 0,
output vector dir = 0,
output float tMax = 1.0/0.0
)
{
// Octane camera basis
pos = P;
vector up = N;
vector right = cross(I, up);
// Resolution + pixel aspect
float pa = 1.0;
int res[2] = {1920, 1080};
getattribute("camera:pixelaspect", pa);
getattribute("camera:resolution", res);
float aspect = float(res[0]) / max(1.0, float(res[1]));
// Convert fov to view-plane extents (pinhole at z=1)
float fovRad = radians(clamp(fov, 1.0, 179.0));
float viewExtY = tan(0.5 * fovRad);
float viewExtX = aspect * viewExtY;
// Film plane coords in "Panini view space"
float vx = (2.0 * (u - 0.5)) * viewExtX * paniniScale;
float vy = (2.0 * (v - 0.5)) * viewExtY * pa * paniniScale;
// Inverse Panini mapping: Panini-space (vx,vy) -> rectilinear projected (px,py)
vector proj;
if (d <= 1e-8)
{
// d=0 -> regular rectilinear
proj = vector(vx, vy, 0.0);
}
else if (abs(d - 1.0) < 1e-6)
{
proj = Panini_UnitDistance_xy(vx, vy);
}
else
{
proj = Panini_Generic_xy(vx, vy, d);
}
// Build ray direction from rectilinear projection plane
dir = I + proj.x * right + proj.y * up;
// Optional vignette via throughput (uses normalized radius-ish measure)
if (vignette > 0.0)
{
float nx = proj.x / (viewExtX + 1e-8);
float ny = proj.y / (viewExtY + 1e-8);
float r2 = nx*nx + ny*ny;
float vig = exp(-vignette * r2);
setmessage("octane:throughput", color(vig, vig, vig));
}
}
// Octane OSL Camera: Datamosh-ish glitch (macroblocks + tearing bands + frame stepping)
// + Camera-driven modulation (position + orientation influence)
// + Macroblock smear (soft blending between blocks)
// + Focus-driven amplitude (approx)
// + Random block transforms (flip/mirror/rotate)
// + Focus-driven smear (uses same depth factor k)
// + DOF (thin lens) implemented in OSL camera
//
// Struct-free, avoids fract() and smooth() (Octane OSL compatibility)
float fractf(float x) { return x - floor(x); }
float clampf(float x, float a, float b)
{
return min(max(x, a), b);
}
// Smoothstep replacement: returns 0..1 as x goes from edge0->edge1
float smoothstepf(float edge0, float edge1, float x)
{
float t = (x - edge0) / (edge1 - edge0);
t = clampf(t, 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
float hash1(float n)
{
return fractf(sin(n) * 43758.5453123);
}
float hash2(float a, float b)
{
return hash1(a * 17.0 + b * 131.0 + 0.123);
}
float lerpf(float a, float b, float t) { return a + (b - a) * t; }
// Apply one of 8 dihedral transforms (rotate/flip) to a 2D vector (x,y)
// mode 0..7
void xform2d(int mode, float x, float y, output float ox, output float oy)
{
int m = mode % 8;
float sx = x;
float sy = y;
if (m >= 4)
sx = -sx; // mirror across Y axis
int r = m % 4;
if (r == 0) { ox = sx; oy = sy; }
else if (r == 1) { ox = -sy; oy = sx; }
else if (r == 2) { ox = -sx; oy = -sy; }
else { ox = sy; oy = -sx; }
}
shader OslCameraDatamosh(
// Defaults match your screenshot
float fov = 24.5 [[ float min = 1.0, float max = 179.0 ]],
// Animate this (seconds or frames — your choice)
float animTime = 82192.0 [[ float min = 0.0, float max = 100000.0 ]],
// Frame stepping (0 = off). Higher = more "hold frames".
float stepRate = 63.1 [[ float min = 0.0, float max = 240.0 ]],
// Macroblock grid size (blocks across / down)
float blocksX = 184.3 [[ float min = 4.0, float max = 512.0 ]],
float blocksY = 196.3 [[ float min = 4.0, float max = 512.0 ]],
// Macroblock displacement strength (in film-plane units)
float blockAmpX = 0.0423 [[ float min = 0.0, float max = 0.25 ]],
float blockAmpY = 0.0423 [[ float min = 0.0, float max = 0.25 ]],
// How fast the block pattern evolves
float blockSpeed = 0.0 [[ float min = 0.0, float max = 60.0 ]],
// Tearing bands
float tearCount = 7.25 [[ float min = 0.0, float max = 30.0 ]],
float tearWidth = 0.2054 [[ float min = 0.001, float max = 0.5 ]],
float tearAmpX = 0.0193 [[ float min = 0.0, float max = 0.5 ]],
float tearSpeed = 0.0 [[ float min = 0.0, float max = 10.0 ]],
// Chance a band "pops" harder
float popChance = 0.0 [[ float min = 0.0, float max = 1.0 ]],
float popMult = 1.0 [[ float min = 1.0, float max = 10.0 ]],
// ----------------------------
// Camera-driven controls
// ----------------------------
float camDrive = 1.0 [[ float min = 0.0, float max = 1.0 ]],
float camPosScale = 0.25 [[ float min = 0.0, float max = 5.0 ]],
float camRotScale = 50.0 [[ float min = 0.0, float max = 500.0 ]],
int camAffectBlocks = 1 [[ string widget = "checkBox", string label="Camera -> Blocks", int connectable = 0 ]],
int camAffectTears = 1 [[ string widget = "checkBox", string label="Camera -> Tears", int connectable = 0 ]],
// ----------------------------
// Smear / blur blocks together (manual)
// 0 = hard macroblocks, 1 = smooth blended blocks
// ----------------------------
float blockSmear = 0.0 [[ float min = 0.0, float max = 1.0 ]],
// ----------------------------
// Focus-driven amplitude (approx)
// ----------------------------
int useFocusDrive = 0 [[ string widget = "checkBox", string label="Enable Focus Drive", int connectable = 0 ]],
point focusPos = point(0.0, 0.0, 0.0) [[ string label="Focus Position (world)" ]],
float focusBandMin = 0.0 [[ float min = 0.0, float max = 10000.0 ]],
float focusBandMax = 5.0 [[ float min = 0.0, float max = 10000.0 ]],
float driveBlockAmpX_min = 0.01 [[ float min = 0.0, float max = 0.25 ]],
float driveBlockAmpX_max = 0.08 [[ float min = 0.0, float max = 0.25 ]],
float driveBlockAmpY_min = 0.01 [[ float min = 0.0, float max = 0.25 ]],
float driveBlockAmpY_max = 0.08 [[ float min = 0.0, float max = 0.25 ]],
// ----------------------------
// Focus-driven smear
// ----------------------------
int useFocusSmear = 0 [[ string widget = "checkBox", string label="Enable Focus Smear", int connectable = 0 ]],
float focusSmearNear = 0.0 [[ float min = 0.0, float max = 1.0 ]],
float focusSmearFar = 1.0 [[ float min = 0.0, float max = 1.0 ]],
float focusSmearAmount = 1.0 [[ float min = 0.0, float max = 1.0 ]],
// ----------------------------
// Random flip/mirror/rotate of block displacement (time-seeded)
// ----------------------------
int useBlockXform = 1 [[ string widget = "checkBox", string label="Enable Block Xform", int connectable = 0 ]],
float xformRate = 2.0 [[ float min = 0.0, float max = 120.0 ]],
float xformAmount = 0.5 [[ float min = 0.0, float max = 1.0 ]],
float xformSeed = 0.0 [[ float min = -10000.0, float max = 10000.0 ]],
int splitXY = 1 [[ string widget = "checkBox", string label="Split X/Y Xform", int connectable = 0 ]],
// ----------------------------
// DOF (thin lens)
// Note: Implemented inside the OSL camera by offsetting pos on a lens disk
// and re-aiming rays to a focus plane at focusDist.
// ----------------------------
int useDOF = 0 [[ string widget="checkBox", string label="Enable DOF", int connectable=0 ]],
float focusDist = 5.0 [[ float min=0.001, float max=100000.0 ]], // world units
float aperture = 0.03 [[ float min=0.0, float max=10.0 ]], // world units (bigger = blurrier)
int bladeCount = 0 [[ int min=0, int max=12 ]], // 0=disk, 3+=polygon bokeh
float dofSeed = 0.0 [[ float min=-10000.0, float max=10000.0 ]],
float dofJitter = 1.0 [[ float min=0.0, float max=10.0 ]],
// Optional vignette via throughput
float vignette = 0.0 [[ float min = 0.0, float max = 2.0 ]],
output point pos = 0,
output vector dir = 0,
output float tMax = 1.0/0.0
)
{
pos = P;
vector up = N;
vector right = cross(I, up);
// Resolution + pixel aspect
float pa = 1.0;
int res[2] = {1920, 1080};
getattribute("camera:pixelaspect", pa);
getattribute("camera:resolution", res);
float aspect = float(res[0]) / max(1.0, float(res[1]));
// Camera-driven seed
float camSeed =
dot(P, vector(0.113, 0.271, 0.179)) * camPosScale +
dot(I, vector(0.631, 0.223, 0.714)) * camRotScale +
dot(N, vector(0.317, 0.911, 0.129)) * camRotScale;
camSeed *= camDrive;
// Optional time stepping (frame-hold feel)
float t = animTime;
if (stepRate > 0.0)
t = floor(animTime * stepRate) / stepRate;
float tBlocks = t + (camAffectBlocks != 0 ? camSeed : 0.0);
float tTears = t + (camAffectTears != 0 ? camSeed : 0.0);
// Film plane coords centered around 0
float x = 2.0 * (u - 0.5);
float y = 2.0 * (v - 0.5);
x *= aspect;
y *= pa;
// Base pinhole direction (used for focus-depth estimate)
float fovRad_base = radians(clamp(fov, 1.0, 179.0));
float f_base = 1.0 / tan(0.5 * fovRad_base);
vector dir_base = f_base * I + x * right + y * up;
vector dirn = normalize(dir_base);
// Focus depth factor k (0 near focus, 1 far)
float k = 0.0;
if (useFocusDrive != 0 || useFocusSmear != 0)
{
float focusDistApprox = distance(P, focusPos);
float denom = max(1e-6, dot(dirn, normalize(I)));
float tPlane = focusDistApprox / denom;
float df = abs(tPlane - focusDistApprox);
k = smoothstepf(focusBandMin, max(focusBandMin + 1e-6, focusBandMax), df);
}
// Focus-driven amplitude (optional)
float ampX = blockAmpX;
float ampY = blockAmpY;
if (useFocusDrive != 0)
{
ampX = lerpf(driveBlockAmpX_min, driveBlockAmpX_max, k);
ampY = lerpf(driveBlockAmpY_min, driveBlockAmpY_max, k);
}
// Focus-driven smear (optional), blended with manual blockSmear
float smear = blockSmear;
if (useFocusSmear != 0)
{
float smearFocus = lerpf(focusSmearNear, focusSmearFar, k);
smear = lerpf(blockSmear, smearFocus, focusSmearAmount);
}
// ----------------------------
// Macroblock displacement (with smear + optional transforms)
// ----------------------------
float uScaled = u * blocksX;
float vScaled = v * blocksY;
float bu = floor(uScaled);
float bv = floor(vScaled);
float fu = uScaled - bu;
float fv = vScaled - bv;
float bt = floor(tBlocks * blockSpeed);
// Hard block offsets
float rx00 = hash2(bu + bt * 7.0, bv + bt * 13.0) * 2.0 - 1.0;
float ry00 = hash2(bu + bt * 11.0, bv + bt * 5.0 ) * 2.0 - 1.0;
float rx = rx00;
float ry = ry00;
// Smear = bilinear blend between neighbor blocks
if (smear > 0.0)
{
float bu1 = bu + 1.0;
float bv1 = bv + 1.0;
float rx10 = hash2(bu1 + bt * 7.0, bv + bt * 13.0) * 2.0 - 1.0;
float ry10 = hash2(bu1 + bt * 11.0, bv + bt * 5.0 ) * 2.0 - 1.0;
float rx01 = hash2(bu + bt * 7.0, bv1 + bt * 13.0) * 2.0 - 1.0;
float ry01 = hash2(bu + bt * 11.0, bv1 + bt * 5.0 ) * 2.0 - 1.0;
float rx11 = hash2(bu1 + bt * 7.0, bv1 + bt * 13.0) * 2.0 - 1.0;
float ry11 = hash2(bu1 + bt * 11.0, bv1 + bt * 5.0 ) * 2.0 - 1.0;
float su = smoothstepf(0.0, 1.0, fu);
float sv = smoothstepf(0.0, 1.0, fv);
float rx0 = lerpf(rx00, rx10, su);
float rx1 = lerpf(rx01, rx11, su);
float rxB = lerpf(rx0, rx1, sv);
float ry0 = lerpf(ry00, ry10, su);
float ry1 = lerpf(ry01, ry11, su);
float ryB = lerpf(ry0, ry1, sv);
rx = lerpf(rx00, rxB, smear);
ry = lerpf(ry00, ryB, smear);
}
// Random flip/mirror/rotate of displacement
if (useBlockXform != 0 && xformAmount > 0.0)
{
float xt = (xformRate > 0.0) ? floor(tBlocks * xformRate) : 0.0;
float rMode = hash2(bu + 37.0 + xt * 11.0 + xformSeed, bv + 91.0 + xt * 17.0);
int mode = int(floor(rMode * 8.0));
float tx, ty;
xform2d(mode, rx, ry, tx, ty);
if (splitXY != 0)
{
float rMode2 = hash2(bu + 113.0 + xt * 19.0 + xformSeed * 1.37, bv + 7.0 + xt * 23.0);
int mode2 = int(floor(rMode2 * 8.0));
float tx2, ty2;
xform2d(mode2, rx, ry, tx2, ty2);
ty = ty2; // intentionally mismatch
}
rx = lerpf(rx, tx, xformAmount);
ry = lerpf(ry, ty, xformAmount);
}
// Apply displacements
x += rx * ampX;
y += ry * ampY;
// ----------------------------
// Horizontal tearing bands
// ----------------------------
float tearX = 0.0;
int tc = int(tearCount);
for (int i = 0; i < tc; ++i)
{
float fi = float(i);
float center = fractf(fi * 0.37 + tTears * tearSpeed);
float dv = abs(v - center);
dv = min(dv, 1.0 - dv);
float m = 1.0 - smoothstepf(0.0, tearWidth, dv);
float bandRand = hash1(fi * 91.7 + floor(tTears * 3.0) * 13.0);
float dirSign = (bandRand < 0.5) ? -1.0 : 1.0;
float pop = (hash1(fi * 33.3 + floor(tTears * 5.0) * 101.0) < popChance) ? popMult : 1.0;
tearX += m * dirSign * tearAmpX * pop;
}
x += tearX;
// ----------------------------
// Build ray direction (pinhole)
// ----------------------------
float fovRad = radians(clamp(fov, 1.0, 179.0));
float f = 1.0 / tan(0.5 * fovRad);
dir = f * I + x * right + y * up;
// ----------------------------
// DOF (thin lens)
// ----------------------------
if (useDOF != 0 && aperture > 0.0 && focusDist > 0.0)
{
// Stable-ish random pair from u/v + time + seed
float base = (u * 1733.0 + v * 9277.0) + dofSeed + animTime * dofJitter;
float r1 = hash1(base + 11.3);
float r2 = hash1(base + 57.9);
// Concentric disk sampling
float sx = 2.0 * r1 - 1.0;
float sy = 2.0 * r2 - 1.0;
float a, b;
if (sx == 0.0 && sy == 0.0) { a = 0.0; b = 0.0; }
else
{
float r, theta;
if (abs(sx) > abs(sy)) { r = sx; theta = (3.14159265/4.0) * (sy/sx); }
else { r = sy; theta = (3.14159265/2.0) - (3.14159265/4.0) * (sx/sy); }
a = r * cos(theta);
b = r * sin(theta);
}
// Optional polygonal bokeh (blades)
if (bladeCount >= 3)
{
float ang = atan2(b, a);
float rad = sqrt(a*a + b*b);
float kBlades = float(bladeCount);
float seg = 6.2831853 / kBlades;
float edge = cos(floor((ang + seg*0.5)/seg)*seg - ang);
edge = max(edge, 1e-6);
rad *= edge;
a = rad * cos(ang);
b = rad * sin(ang);
}
// Lens offset in world space
vector lensOffset = (a * right + b * up) * aperture;
// Focus point along current ray, intersecting a plane perpendicular to I at focusDist
vector dirN = normalize(dir);
float denom = max(1e-6, dot(dirN, normalize(I)));
float tFocus = focusDist / denom;
point focusPoint = pos + dirN * tFocus;
// Offset origin on lens and re-aim to focus point
pos = pos + lensOffset;
dir = focusPoint - pos;
}
// Optional vignette
if (vignette > 0.0)
{
float r2 = x*x + y*y;
float vig = exp(-vignette * r2);
setmessage("octane:throughput", color(vig, vig, vig));
}
}
bottom of page



