Add buildable C engine, shaders and music source
This commit is contained in:
parent
1d805b8896
commit
18721711c4
44 changed files with 23427 additions and 3 deletions
433
fx/eye.hlsl
Normal file
433
fx/eye.hlsl
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
// Shot 01 - COLD OPEN: CAT EYE MACRO. And ten more after it.
|
||||
//
|
||||
// The demo opens on an eye that fills the frame, close enough that you read
|
||||
// it as a landscape before you read it as an eye. No geometry: this is a
|
||||
// two dimensional field, which is the point - not every shot has to be a
|
||||
// space you fly through, and the cheapest shot in the demo is also the one
|
||||
// that has to earn the first ten seconds.
|
||||
//
|
||||
// It is also the scene the edit leans on hardest: ELEVEN appearances, more
|
||||
// than any other. That was fine as a plan and a disaster as a picture,
|
||||
// because the first version of this file read gTune for a pupil width, a
|
||||
// rotation and a glow, and drew the same photograph eleven times. Measured
|
||||
// pairwise as luminance thumbnails the eleven shots sat 12.7 apart - the
|
||||
// second most repetitive scene in the production.
|
||||
//
|
||||
// gTune.x pupil width (0 = a slit, 1 = fully round and blown)
|
||||
// gTune.y lid closure (0 = open, 1 = shut) - arms the blink
|
||||
// gTune.z iris rotation
|
||||
// gTune.w glow strength
|
||||
//
|
||||
// Those four now carry a fifth thing nobody asked them for: an IDENTITY.
|
||||
// The shot table hands eleven distinct (x, z, w) triples, so seven decorrelated
|
||||
// hashes of them give every appearance a stable fingerprint, and the fingerprint
|
||||
// decides the things a photographer decides - how tight the crop is, where the
|
||||
// eye sits in frame, how far the lids are apart, what shape the eye is, how
|
||||
// dense the veins are, and, most of all, WHAT THE CORNEA IS REFLECTING. The
|
||||
// multipliers below are not decoration: they were solved so that across the
|
||||
// eleven real triples every seed lands at least 0.056 from its nearest
|
||||
// neighbour, against a theoretical best of 0.1, with a worst pairwise
|
||||
// correlation of 0.02. That is what stops two appearances colliding.
|
||||
//
|
||||
// Nothing here is on a wall clock that outlives a shot. Every animation reads
|
||||
// gTime.x, which restarts at each cut, so a four bar hold plays a saccade
|
||||
// pattern, a dilation and a lid settle instead of being a still frame.
|
||||
|
||||
cbuffer Scene : register(b0)
|
||||
{
|
||||
float4 gTime; // x time, y beat, z shot fade, w aspect
|
||||
float4 gCam; // xyz eye, w tan(vfov/2)
|
||||
float4 gDir; // xyz look, w roll
|
||||
float4 gTune;
|
||||
/* What the music is doing at this exact sample. The three bands
|
||||
each drive a different SCALE of thing - low for big slow shapes,
|
||||
mid for mid-sized ones, high for fine texture - so the image ends
|
||||
up with the same frequency structure as the music instead of just
|
||||
reacting to it. */
|
||||
float4 gSync; // x low, y mid, z high, w master peak
|
||||
float4 gVoice; // x guitar, y solo, z organ, w hat
|
||||
};
|
||||
|
||||
struct VSOut
|
||||
{
|
||||
float4 pos : SV_Position;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
float hash12(float2 p)
|
||||
{
|
||||
float3 q = frac(float3(p.xyx) * 0.1031);
|
||||
q += dot(q, q.yzx + 33.33);
|
||||
return frac((q.x + q.y) * q.z);
|
||||
}
|
||||
|
||||
float hash11(float n)
|
||||
{
|
||||
n = frac(n * 0.1031);
|
||||
n *= n + 33.33;
|
||||
n *= n + n;
|
||||
return frac(n);
|
||||
}
|
||||
|
||||
float noise2(float2 p)
|
||||
{
|
||||
float2 i = floor(p), f = frac(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
return lerp(lerp(hash12(i), hash12(i + float2(1, 0)), f.x),
|
||||
lerp(hash12(i + float2(0, 1)), hash12(i + float2(1, 1)), f.x), f.y);
|
||||
}
|
||||
|
||||
// Value noise whose X axis is genuinely periodic, for anything wrapped around
|
||||
// a circle.
|
||||
//
|
||||
// The version above is not, and the comment further down used to claim it was
|
||||
// enough to use an integer number of cells around the turn. An integer count
|
||||
// does make the INTERPOLATION parameter meet itself at the atan2 +-pi line -
|
||||
// frac() lands on the same value from both sides - but hash12(i) and
|
||||
// hash12(i + period) are unrelated numbers, so the VALUE still jumps.
|
||||
//
|
||||
// Measured before this existed, by reimplementing hash12 and noise2 exactly
|
||||
// and evaluating across the seam: the iris fibre field jumped 0.18 to 0.20 of
|
||||
// its 0..1 range across a single pixel boundary, against 0.004 to 0.008 for a
|
||||
// typical neighbouring pixel. A 23x to 49x discontinuity - a hard radial tear
|
||||
// running from the pupil to the limbus, in all ten appearances of the eye.
|
||||
//
|
||||
// The fix is to wrap the lattice INDEX too, so the cell after the last one is
|
||||
// the first one again. period must be a whole number.
|
||||
float noise2p(float2 p, float period)
|
||||
{
|
||||
float2 i = floor(p), f = frac(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
|
||||
float x0 = i.x - period * floor(i.x / period);
|
||||
x0 = (x0 >= period) ? 0.0 : x0; // guard the rounding case
|
||||
float x1 = x0 + 1.0;
|
||||
x1 = (x1 >= period) ? 0.0 : x1;
|
||||
|
||||
return lerp(lerp(hash12(float2(x0, i.y)),
|
||||
hash12(float2(x1, i.y)), f.x),
|
||||
lerp(hash12(float2(x0, i.y + 1.0)),
|
||||
hash12(float2(x1, i.y + 1.0)), f.x), f.y);
|
||||
}
|
||||
|
||||
#define TAU 6.28318531
|
||||
#define ITAU 0.15915494
|
||||
|
||||
float4 main(VSOut i) : SV_Target
|
||||
{
|
||||
float2 p = float2(i.uv.x * 2.0 - 1.0, 1.0 - i.uv.y * 2.0);
|
||||
// y flipped: the fullscreen triangle puts uv.y = 0 at the TOP, so
|
||||
// without this the catch-light sat below centre and the lids
|
||||
// closed the wrong way round.
|
||||
p.x *= gTime.w;
|
||||
float t = gTime.x;
|
||||
|
||||
// ---- which of the eleven is this? ------------------------------------
|
||||
float k0 = frac(gTune.w * 10.00 + gTune.x * 14.20 + gTune.z * 2.60 + 0.143);
|
||||
float k1 = frac(gTune.w * 9.50 + gTune.x * 7.10 + gTune.z * 2.80 + 0.083);
|
||||
float k2 = frac(gTune.w * 12.50 + gTune.x * 13.10 + gTune.z * 6.10 + 0.587);
|
||||
float k3 = frac(gTune.w * 13.60 + gTune.x * 9.40 + gTune.z * 12.20 + 0.959);
|
||||
// three more, folded out of the first four rather than out of gTune, so
|
||||
// they stay independent of the framing they decorate.
|
||||
float k4 = frac(k0 * 3.6 + k2 * 3.2 + 0.415);
|
||||
float k5 = frac(k1 * 5.6 + k3 * 1.4 + 0.258);
|
||||
float k6 = frac(k0 * 3.5 + k3 * 2.6 + 0.346);
|
||||
|
||||
// ---- the crop --------------------------------------------------------
|
||||
// This is the single biggest lever in the file. At mag 0.74 the whole
|
||||
// eye sits in the frame with sclera and veins around it; at 1.52 we are
|
||||
// inside the iris and the limbus only clips the corners. Eleven shots
|
||||
// spread across that range stop being eleven of the same photograph.
|
||||
float mag = 0.72 + 0.80 * k0;
|
||||
float2 ctr = float2((k1 - 0.5) * 0.42, (k2 - 0.5) * 0.30);
|
||||
|
||||
// A slow drift, so the eye is alive without ever looking like it is
|
||||
// being animated at you. Rate and throw are per appearance now.
|
||||
ctr -= float2(sin(t * (0.19 + k4 * 0.17)) * (0.018 + k5 * 0.030),
|
||||
cos(t * (0.13 + k5 * 0.16)) * (0.014 + k4 * 0.026));
|
||||
|
||||
// ---- the gaze --------------------------------------------------------
|
||||
// A real eye never holds still: it darts and then sits. Step-and-hold,
|
||||
// fast on the way out and long on the hold, with the targets hashed off
|
||||
// this appearance's own seed so the pattern is reproducible per shot.
|
||||
// This is what makes a CAM_HOLD shot not a freeze frame.
|
||||
float sacR = 0.42 + k2 * 1.05; // darts per second
|
||||
float sacA = 0.015 + k3 * 0.085; // and how far each one goes
|
||||
float sp = t * sacR + k0 * 13.0;
|
||||
float si = floor(sp);
|
||||
float sf = smoothstep(0.0, 0.13, frac(sp));
|
||||
float sd = k1 * 57.0;
|
||||
float2 gA = float2(hash11(si * 2.13 + sd), hash11(si * 2.13 + sd + 19.7)) - 0.5;
|
||||
float2 gB = float2(hash11(si * 2.13 + 2.13 + sd),
|
||||
hash11(si * 2.13 + 2.13 + sd + 19.7)) - 0.5;
|
||||
float2 gaze = lerp(gA, gB, sf) * sacA;
|
||||
|
||||
// Two frames, and the difference between them is the whole trick: the
|
||||
// eyeball (sclera, veins, lids, the reflected world) does not move when
|
||||
// the eye looks somewhere - only the iris and pupil do.
|
||||
float2 eb = (p - ctr) / mag; // eyeball
|
||||
float2 e = eb - gaze; // iris
|
||||
float r = length(e);
|
||||
float rb = length(eb);
|
||||
float rs = length(p - ctr); // screen, for glow and vignette
|
||||
|
||||
// ---- iris ------------------------------------------------------------
|
||||
float irisEdge = 0.86;
|
||||
float spin = gTune.z + t * ((k3 - 0.45) * 0.16);
|
||||
float a01 = (atan2(e.y, e.x) + spin) * ITAU; // turns, not radians
|
||||
|
||||
// Radial fibres, two scales, so it holds up at this magnification. The
|
||||
// counts are INTEGERS in turns AND the noise is wrapped to them, which is
|
||||
// what actually closes the seam at the +-pi line - the integer count on
|
||||
// its own does not, see noise2p.
|
||||
float sect = floor(7.0 + k4 * 13.0); // 7..19 coarse spokes
|
||||
float fineN = floor(23.0 + k5 * 26.0); // 23..48 fine ones
|
||||
float strc = 3.6 + k6 * 5.4; // how far they smear radially
|
||||
float fib = noise2p(float2(a01 * sect, r * strc), sect) * 0.62
|
||||
+ noise2p(float2(a01 * fineN, r * strc * 2.3), fineN) * 0.38;
|
||||
|
||||
// The collarette: the ruff where the iris changes character, and where
|
||||
// the crypts live. Its radius moves per appearance, which redraws the
|
||||
// whole interior structure rather than recolouring it.
|
||||
float colR = 0.30 + k6 * 0.30;
|
||||
float cb = (r - colR) * 5.5;
|
||||
float band = exp(-cb * cb);
|
||||
float cryN = floor(5.0 + k5 * 9.0);
|
||||
float crypt = smoothstep(0.60, 0.88,
|
||||
noise2p(float2(a01 * cryN, r * 3.0 + 11.0), cryN)) * band;
|
||||
|
||||
float iris = smoothstep(irisEdge + 0.055, irisEdge - 0.02, r);
|
||||
|
||||
// A cat pupil is a vertical slit that opens into an ellipse.
|
||||
// The pupil breathes with the low band. 0.045 is a ceiling, not a
|
||||
// starting point: it is 2.8% of the iris, and the band's 420 ms release
|
||||
// against a 454 ms beat means it is always still falling when the next
|
||||
// kick lands. It physically cannot pulse.
|
||||
// On top of that it now DILATES OR CONTRACTS across the shot - direction
|
||||
// from the seed - so the pupil is a movement and not a setting.
|
||||
float dilDir = (k2 < 0.5) ? -1.0 : 1.0;
|
||||
float dil = saturate(gTune.x + dilDir * (0.05 + 0.20 * gTune.x)
|
||||
* smoothstep(0.15, 2.40, t));
|
||||
float pw = max(0.052 + dil * 0.62 + gSync.x * 0.045 + gVoice.y * 0.012, 0.020);
|
||||
float ph = 0.62 + k4 * 0.22; // how tall the slit is
|
||||
float lean = (k1 - 0.5) * 0.22; // and how far it leans
|
||||
float2 q = float2((e.x - e.y * lean) / pw, e.y / ph);
|
||||
// A perfect ellipse reads as vector art, so the rim borrows the iris
|
||||
// texture it is cut out of. Free: fib is already in hand.
|
||||
// Bounded by the iris mask: a tall slit on a wide seed would otherwise
|
||||
// run past the limbus and cut a hole in the sclera, which is an eye
|
||||
// with a slot milled through it.
|
||||
float pupil = smoothstep(1.04, 0.94, length(q) + (fib - 0.5) * 0.05) * iris;
|
||||
|
||||
float hueK = k2 * 0.55; // green cat to amber cat
|
||||
float3 dark = lerp(float3(0.10, 0.30, 0.16), float3(0.27, 0.21, 0.07), hueK);
|
||||
float3 lite = lerp(float3(0.55, 0.95, 0.40), float3(0.97, 0.82, 0.34), hueK);
|
||||
// Contrast of the fibres rides the mid band. base + k*s: the floor is
|
||||
// 1.20 and the music can only ever add to it.
|
||||
float3 gold = lerp(dark, lite, saturate(fib * (1.20 + gSync.y * 0.18)));
|
||||
gold = lerp(gold, lite * 1.15, pow(saturate(1.0 - r / irisEdge), 2.4) * 0.85);
|
||||
gold *= 1.0 - 0.55 * crypt; // the lacunae, punched dark
|
||||
|
||||
// Flecks. Four of them, at hashed positions in the iris, and only some
|
||||
// appearances get any at all - the reward for looking twice.
|
||||
float fleckAmt = saturate((k5 - 0.35) / 0.40);
|
||||
float fleck = 0.0;
|
||||
[unroll] for (int fi = 0; fi < 4; fi++)
|
||||
{
|
||||
float fh = hash11(float(fi) * 3.7 + k0 * 91.0);
|
||||
float fr = 0.26 + hash11(float(fi) * 5.1 + k2 * 77.0) * 0.50;
|
||||
float da = a01 - fh;
|
||||
da -= floor(da + 0.5); // shortest way round
|
||||
float2 fd = float2(da * TAU * max(r, 0.12), r - fr);
|
||||
fleck += exp(-dot(fd, fd) * 900.0);
|
||||
}
|
||||
gold += lite * fleck * fleckAmt * 0.55;
|
||||
|
||||
// The limbal ring: the dark edge that makes an iris read as an iris.
|
||||
float ringW = 0.13 + k6 * 0.17;
|
||||
gold *= 1.0 - (0.62 + 0.28 * k3) * smoothstep(irisEdge - ringW, irisEdge, r);
|
||||
|
||||
float3 col = gold * iris;
|
||||
col *= 1.0 - pupil; // punch the pupil out
|
||||
|
||||
// ---- the sclera ------------------------------------------------------
|
||||
// Only exists in the wider framings, and when it does it brings the veins
|
||||
// with it. A whole extra half of the picture that six of the eleven shots
|
||||
// have and the other five do not.
|
||||
float scl = smoothstep(irisEdge - 0.01, irisEdge + 0.07, rb);
|
||||
float3 sclera = float3(0.20, 0.215, 0.205) * (1.0 - 0.62 * smoothstep(0.95, 1.80, rb));
|
||||
[branch] if (mag < 1.30)
|
||||
{
|
||||
float ab = atan2(eb.y, eb.x) * ITAU;
|
||||
float vN = floor(13.0 + k5 * 17.0);
|
||||
float vN2 = floor(vN * 2.4); // integer, so it can wrap
|
||||
float vr = rb * 2.4 - t * 0.015;
|
||||
float vv = noise2p(float2(ab * vN, vr), vN) * 0.68
|
||||
+ noise2p(float2(ab * vN2, vr * 2.4 + 7.3), vN2) * 0.32;
|
||||
vv = saturate(1.0 - abs(vv * 2.0 - 1.0));
|
||||
vv *= vv; vv *= vv; vv *= vv; // ridged, ^8, no pow call
|
||||
float vmask = smoothstep(irisEdge + 0.02, irisEdge + 0.50, rb);
|
||||
sclera += float3(0.26, 0.09, 0.09) * vv * vmask
|
||||
* (0.18 + 0.70 * k5) * (1.0 + gSync.x * 0.25);
|
||||
}
|
||||
col += sclera * scl;
|
||||
|
||||
// ---- what the cornea is reflecting -----------------------------------
|
||||
// A cat's eye is a mirror before it is anything else, and the thing in
|
||||
// the mirror is the only element that can be a different OBJECT without
|
||||
// the shot stopping being an eye. Four of them, chosen by k1.
|
||||
float2 rc = eb + gaze * 0.30; // the world does not turn with the eye
|
||||
rc *= (1.4 + k3 * 1.5) / max(1.0 - 0.55 * dot(rc, rc), 0.30); // convex: the rim eats the horizon
|
||||
float ref = 0.0;
|
||||
int variant = min(3, (int)(k1 * 4.0));
|
||||
if (variant == 0)
|
||||
{
|
||||
// a lit window, four panes, drifting
|
||||
float2 wp = rc * float2(1.0, 1.30) - float2(sin(t * 0.09) * 0.20, 0.55);
|
||||
float pane = smoothstep(1.00, 0.74, max(abs(wp.x), abs(wp.y)));
|
||||
float mull = smoothstep(0.05, 0.11, abs(wp.x)) * smoothstep(0.05, 0.11, abs(wp.y));
|
||||
ref = pane * (0.30 + 0.70 * mull);
|
||||
}
|
||||
else if (variant == 1)
|
||||
{
|
||||
// a low sun over a banded horizon: the demo's own synthwave shot,
|
||||
// seen from the wrong side of the glass
|
||||
float2 sn = rc - float2(0.0, -0.30 + sin(t * 0.07) * 0.06);
|
||||
float sun = smoothstep(0.46, 0.20, length(sn * float2(1.0, 1.15)));
|
||||
float band = 0.5 + 0.5 * sin(sn.y * 26.0 - t * 0.7);
|
||||
float hz = smoothstep(0.06, 0.0, abs(rc.y + 0.64));
|
||||
ref = sun * (0.45 + 0.55 * band) + hz * 0.55;
|
||||
}
|
||||
else if (variant == 2)
|
||||
{
|
||||
// a tube. scanlines and a roll bar, because of course it is a tube
|
||||
float2 sn = rc * float2(0.92, 1.20);
|
||||
float scr = smoothstep(1.05, 0.86, max(abs(sn.x), abs(sn.y)));
|
||||
float lines = 0.55 + 0.45 * sin(sn.y * 52.0);
|
||||
float roll = smoothstep(0.30, 0.0, abs(frac(sn.y * 0.4 - t * 0.13) - 0.5));
|
||||
ref = scr * (lines * 0.75 + roll * 0.40);
|
||||
}
|
||||
else
|
||||
{
|
||||
// doorframes receding: the corridor, six deep, walking outward. The
|
||||
// ratio is exactly 2 so the sequence at frac()==1 is the sequence at
|
||||
// frac()==0 shifted by one frame, and the loop never pops.
|
||||
float s = 1.05 * exp2(frac(t * 0.11));
|
||||
[unroll] for (int ai = 0; ai < 6; ai++)
|
||||
{
|
||||
float dr = max(abs(rc.x) / s, abs(rc.y) / (s * 0.80));
|
||||
ref += smoothstep(0.075, 0.0, abs(dr - 1.0));
|
||||
s *= 0.5;
|
||||
}
|
||||
ref *= 0.55;
|
||||
}
|
||||
ref = saturate(ref);
|
||||
|
||||
// The pupil is not black: it is a hole with the world in it. Over the
|
||||
// iris the same reflection sits far fainter, because there is already
|
||||
// something under it.
|
||||
float corn = smoothstep(irisEdge + 0.16, irisEdge - 0.04, rb);
|
||||
col += pupil * float3(0.020, 0.028, 0.034);
|
||||
col += ref * corn * float3(0.72, 0.92, 0.85)
|
||||
* (pupil * (0.16 + 0.14 * k3) + (0.030 + 0.035 * k6));
|
||||
|
||||
// ---- the wet specular ------------------------------------------------
|
||||
// The catchlight is a reflection of the KEY LIGHT, so it barely follows
|
||||
// the gaze - which is exactly why it slides across the iris when the eye
|
||||
// darts. Three shapes, and its position moves per appearance.
|
||||
int cls = min(2, (int)(k2 * 3.0));
|
||||
float2 cp = float2(lerp(-0.40, 0.34, k1), lerp(0.14, 0.46, k3))
|
||||
+ gaze * 0.12 + float2(gSync.z * 0.010, 0.0);
|
||||
float2 cd = eb - cp;
|
||||
float csz = 0.13 + k6 * 0.17;
|
||||
float spec;
|
||||
if (cls == 0)
|
||||
{
|
||||
spec = smoothstep(csz * 2.1, 0.0, length(cd)); // one soft disc
|
||||
}
|
||||
else if (cls == 1)
|
||||
{
|
||||
spec = smoothstep(csz * 1.5, 0.0, length(cd)) // and a companion
|
||||
+ smoothstep(csz * 0.85, 0.0, length(cd - float2(0.21, -0.14))) * 0.70;
|
||||
}
|
||||
else
|
||||
{
|
||||
float2 wd = cd / csz; // a window, four panes
|
||||
float box = smoothstep(1.65, 1.15, max(abs(wd.x), abs(wd.y * 1.25)));
|
||||
float mull = smoothstep(0.10, 0.24, abs(wd.x)) * smoothstep(0.10, 0.24, abs(wd.y));
|
||||
spec = box * (0.35 + 0.65 * mull);
|
||||
}
|
||||
col += spec * float3(0.85, 0.95, 0.90) * (0.42 + 0.20 * k0) * (1.0 + gSync.z * 0.22);
|
||||
// the bounce off the wet lower lid
|
||||
col += smoothstep(0.28, 0.0, length(eb - float2(cp.x * 0.4, -0.54)))
|
||||
* float3(0.30, 0.42, 0.38) * 0.11;
|
||||
|
||||
// ---- glow ------------------------------------------------------------
|
||||
// The eye is its own light source. This is the reason the shot works in
|
||||
// a black frame. It falls off in SCREEN radius, not eyeball radius, or a
|
||||
// tight crop would flood the frame with it.
|
||||
float glow = exp(-rs * 1.9) * gTune.w * (1.0 + gVoice.z * 0.10);
|
||||
col += float3(0.28, 0.75, 0.34) * glow;
|
||||
col += float3(0.10, 0.30, 0.14) * exp(-rs * 0.7) * gTune.w * 0.35;
|
||||
|
||||
// ---- the third eyelid ------------------------------------------------
|
||||
// A cat has a nictitating membrane and it sweeps in from the inner
|
||||
// corner. Four of the eleven get one, once, early in the shot. It is a
|
||||
// film, not a shutter: it lifts the blacks under it and leaves a bright
|
||||
// leading edge, and it takes 0.9 s to cross, so nothing about it snaps.
|
||||
[branch] if (k6 > 0.62)
|
||||
{
|
||||
float sweep = sin(saturate((t - 0.55) / 0.90) * 3.14159265);
|
||||
float xe = -2.10 + sweep * 2.55;
|
||||
float memb = smoothstep(xe, xe - 0.14, p.x);
|
||||
float lead = smoothstep(0.10, 0.0, abs(p.x - xe + 0.07)) * sweep;
|
||||
col = lerp(col, col * 0.62 + float3(0.09, 0.13, 0.10) * (0.30 + 0.70 * sweep),
|
||||
memb * 0.45);
|
||||
col += float3(0.18, 0.26, 0.21) * lead * 0.35;
|
||||
}
|
||||
|
||||
// ---- the lid ---------------------------------------------------------
|
||||
// Two arcs closing on the eye. A blink is a shape, not a fade - and it
|
||||
// is a movement, so the shader plays it from the shot's own clock.
|
||||
// gTune.y only arms it; holding the lid shut for a whole shot would just
|
||||
// be a shot of black, which is what the first version of this was.
|
||||
//
|
||||
// The APERTURE is now per appearance, and it is the second biggest lever
|
||||
// after the crop: 0.68 is a half lidded stare and 1.37 is wide open, and
|
||||
// an eye is a different shape at each end of that. The curve and the
|
||||
// slant move with it, so some of the eleven are almonds and some are
|
||||
// rounds.
|
||||
float apert = (1.38 - 0.70 * k3) + sin(t * 0.7 + k0 * 6.0) * 0.015;
|
||||
float cvx = 0.16 + k6 * 0.30;
|
||||
float tilt = (k4 - 0.5) * 0.30;
|
||||
|
||||
float lid = 0.0;
|
||||
if (gTune.y > 0.5) {
|
||||
float u = saturate(t / 0.18); // 180 ms, shut then open
|
||||
lid = u < 0.38 ? (u / 0.38) : saturate(1.0 - (u - 0.38) / 0.62);
|
||||
}
|
||||
// and a settle, on every shot: the lids come the last few percent of the
|
||||
// way open over the first second, so frame one is never where we stay.
|
||||
lid = saturate(lid + (1.0 - smoothstep(0.0, 1.10, t)) * 0.10 * (0.40 + k5));
|
||||
|
||||
// The lid edge carries fur. Smoothed value noise along x, so the
|
||||
// silhouette is a different ragged line in every appearance.
|
||||
float2 d = p - ctr * 0.7;
|
||||
float lx = d.x * (30.0 + k5 * 34.0);
|
||||
float lf = frac(lx); lf = lf * lf * (3.0 - 2.0 * lf);
|
||||
float ls = 5.0 + k0 * 9.0;
|
||||
float fur = (lerp(hash12(float2(floor(lx), ls)),
|
||||
hash12(float2(floor(lx) + 1.0, ls)), lf) - 0.5) * 0.034;
|
||||
|
||||
float lo = -apert + lid * (apert + 0.30) + cvx * d.x * d.x + tilt * d.x + fur;
|
||||
float hi = apert - lid * (apert + 0.30) - cvx * d.x * d.x + tilt * d.x - fur;
|
||||
float open = smoothstep(lo, lo + 0.045, d.y) * smoothstep(hi, hi - 0.045, d.y);
|
||||
// the shadow the lid casts on the globe under it
|
||||
float inner = smoothstep(lo + 0.02, lo + 0.17, d.y) * smoothstep(hi - 0.02, hi - 0.17, d.y);
|
||||
col *= open * (0.30 + 0.70 * inner);
|
||||
|
||||
// Vignette down to nothing, so the eye floats in the dark.
|
||||
col *= 1.0 - 0.55 * smoothstep(0.72, 1.75, rs);
|
||||
|
||||
return float4(col * gTime.z, 1.0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue