Glass of Orange Juice (raymarched)
GLSL shader by scry · created 2026-07-14 · 24s loop · 2 passes
A photorealistic glass of orange juice, raymarched from scratch: dielectric glass with Fresnel reflection + refraction, Beer-Lambert absorption and subsurface scattering through the juice, a meniscus surface, contact shadow and a warm caustic on the table. Studio softbox + key light.
Tags: 3d, Raymarching
This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.
Shader source (GLSL)
Common
#define pi acos(-1.)
#define deg pi/180. //1 degree
#define time iTime*2.*pi/10. //sin(time) loops 10 seconds
#define R iResolution.xy //shorthand
#define ar R.x/R.y //aspect ratio
#define M iMouse //shorthand
#define xm (M.xy/R) //normalized mouse
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5) //aspect ratio correction
vec3 cs = vec3(1.,2.,3.);
mat2 r2d(float a) {
return mat2(cos(a),sin(a),-sin(a),cos(a));
}
Buffer A (iChannel0)
// Glass of Orange Juice — magazine product shot
// backlit scattering juice, rounded tumbler, condensation, straw, marble,
// mirrored counter, ACES tonemap, vignette + grain.
//
// Raymarched & signed by Claude (Fable 5, Anthropic) in ShaderKit, July 2026.
// Art direction by Scry. The pixel signature bottom-right is drawn by the
// shader itself — see sig() at the end of mainImage.
#define PI 3.14159265
const float Rout = 0.82;
const float Rin = 0.72;
const float GH = 2.30; // glass height
const float BT = 0.34; // thick glass base
const float JLVL = 1.86; // juice level
const vec3 LDIR = vec3(-0.5145, 0.6860, 0.5145); // key, upper-left-front
const vec3 BDIR = vec3(0.4190, 0.1400, -0.8970); // backlight, angled off-axis
// straw: bottom rests at inner wall, leans across the rim
const vec3 SB = vec3(-0.52, 0.36, 0.30);
const vec3 SD = vec3(0.4670, 0.8470, -0.2580); // normalized lean direction
const float SL = 2.8; // length
const float SR = 0.055; // outer radius
float h21(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }
float h31(vec3 p){
p = fract(p*0.3183 + 0.1); p *= 17.0;
return fract(p.x*p.y*p.z*(p.x+p.y+p.z));
}
// ---------- geometry ----------
float sdRCyl(vec3 p, float ra, float rb, float h){ // rounded cylinder (iq)
vec2 d = vec2(length(p.xz) - ra + rb, abs(p.y) - h + rb);
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - rb;
}
float sdGlass(vec3 p){
vec3 q = p - vec3(0.0, GH*0.5, 0.0);
float outer = sdRCyl(q, Rout, 0.06, GH*0.5);
vec3 c = p - vec3(0.0, (GH + BT)*0.5 + 0.30, 0.0);
float cavity = sdRCyl(c, Rin, 0.10, (GH - BT)*0.5 + 0.30);
// smooth subtraction -> rounded lip
float k = 0.03;
float hmix = clamp(0.5 - 0.5*(outer + cavity)/k, 0.0, 1.0);
return mix(outer, -cavity, hmix) + k*hmix*(1.0 - hmix);
}
float sdJuice(vec3 p){
float rr = length(p.xz);
float side = rr - (Rin - 0.004);
float bottom = BT - p.y;
float topY = JLVL + 0.05*smoothstep(Rin-0.16, Rin-0.01, rr); // meniscus
return max(max(side, p.y - topY), bottom);
}
float sdStraw(vec3 p){
vec3 q = p - SB;
float s = dot(q, SD);
float r = length(q - SD*s);
float d = max(r - SR, max(-s, s - SL));
float cav = max((SL - 0.20) - s, r - 0.042); // hollow open end
return max(d, -cav);
}
vec3 nGlass(vec3 p){
vec2 e = vec2(0.0015, 0.0);
return normalize(vec3(
sdGlass(p+e.xyy)-sdGlass(p-e.xyy),
sdGlass(p+e.yxy)-sdGlass(p-e.yxy),
sdGlass(p+e.yyx)-sdGlass(p-e.yyx)));
}
vec3 nJuice(vec3 p){
vec2 e = vec2(0.002, 0.0);
return normalize(vec3(
sdJuice(p+e.xyy)-sdJuice(p-e.xyy),
sdJuice(p+e.yxy)-sdJuice(p-e.yxy),
sdJuice(p+e.yyx)-sdJuice(p-e.yyx)));
}
// ---------- condensation ----------
float dropField(vec2 q){
vec2 g = vec2(q.x*(67.0/(2.0*PI)), q.y*13.0); // 67 cells: wraps seamlessly
vec2 id = floor(g), f = fract(g);
float d = 0.0;
for(int i=-1;i<=1;i++)
for(int j=-1;j<=1;j++){
vec2 o = vec2(float(i), float(j));
float rz = h21(id+o+11.7);
if(rz < 0.86) continue; // rare, chunky drops
vec2 c = o + vec2(h21(id+o), h21(id+o+7.3)) - f;
float sz = 0.15 + 0.26*h21(id+o+3.1);
d += sz*exp(-dot(c,c)/(sz*sz*0.42));
}
return d;
}
// ---------- scene ----------
vec3 backdrop(vec3 rd){
float up = smoothstep(-0.05, 0.85, rd.y);
vec3 c = mix(vec3(0.40,0.35,0.30), vec3(0.155,0.145,0.15), up); // airy kitchen sweep
c = mix(c, vec3(0.035,0.032,0.034), smoothstep(0.20, 1.60, abs(rd.x))*0.55);
c += vec3(1.0,0.88,0.70)*exp(-rd.y*rd.y*22.0)*0.30; // horizon glow band
float b = max(dot(rd, BDIR), 0.0);
c += vec3(1.00,0.88,0.66)*smoothstep(0.35, 0.96, b)*0.85; // wide soft backlight
float k = max(dot(rd, LDIR), 0.0);
c += vec3(1.0,0.96,0.88)*smoothstep(0.78, 0.99, k)*0.9; // key panel
return c;
}
vec3 juiceBody(float lit){ return mix(vec3(0.86,0.16,0.005), vec3(1.0,0.52,0.06), lit); }
float vnoise(vec2 p){
vec2 i = floor(p), f = fract(p);
f = f*f*(3.0 - 2.0*f);
return mix(mix(h21(i), h21(i+vec2(1,0)), f.x),
mix(h21(i+vec2(0,1)), h21(i+vec2(1,1)), f.x), f.y);
}
float fbm(vec2 p){
float s = 0.0, a = 0.5;
for(int i=0;i<4;i++){ s += a*vnoise(p); p = p*2.03 + 17.7; a *= 0.5; }
return s;
}
vec3 tableCol(vec3 p, vec3 rd, float tv){
float rr = length(p.xz);
float blur = exp(-max(tv - 7.5, 0.0)*0.22); // fake DOF on far counter
// polished marble
vec2 uv2 = p.xz*0.85;
float n = fbm(uv2);
float vein = pow(1.0 - abs(sin(uv2.x*0.8 + uv2.y*0.55 + n*6.5)), 7.0);
float vein2 = pow(1.0 - abs(sin(uv2.y*1.7 - uv2.x*0.30 + n*4.0)), 9.0);
vec3 base = vec3(0.86, 0.845, 0.83);
base -= vec3(0.34,0.33,0.32)*(vein + 0.6*vein2)*blur;
base -= 0.05*fbm(uv2*3.7)*blur;
// true mirrored glass on the polished stone (analytic ray vs cylinder)
vec3 rdir = reflect(rd, vec3(0.0,1.0,0.0));
vec2 dxz = rdir.xz;
float a2 = dot(dxz, dxz);
if(a2 > 1e-5){
float b2 = 2.0*dot(p.xz, dxz);
float c2 = dot(p.xz, p.xz) - Rout*Rout;
float disc = b2*b2 - 4.0*a2*c2;
if(disc > 0.0 && c2 > 0.0){
float th = (-b2 - sqrt(disc))/(2.0*a2);
if(th > 0.0){
float hy = p.y + rdir.y*th;
if(hy > 0.0 && hy < GH){
float g = smoothstep(BT, JLVL, hy);
vec3 rc = hy < JLVL ? juiceBody(0.8)*(0.45 + 0.55*g) : vec3(0.95,0.93,0.90);
base = mix(base, rc, 0.38*exp(-th*0.35));
}
}
}
}
// contact shadow + tight occlusion ring at the base
base *= 1.0 - 0.55*smoothstep(Rout+0.9, Rout+0.02, rr);
base *= 1.0 - 0.45*smoothstep(Rout+0.16, Rout+0.01, rr);
// warm caustic
vec2 cpos = normalize(vec2(LDIR.x, LDIR.z))*(-1.15);
float cd = length(p.xz - cpos);
float caust = smoothstep(0.75, 0.05, cd)*smoothstep(Rin*0.35, Rin*0.95, rr);
base += vec3(1.0,0.55,0.12)*caust*0.9;
base += vec3(1.0,0.78,0.35)*smoothstep(0.35,0.0,cd)*0.55;
return base;
}
vec3 envir(vec3 ro, vec3 rd){
if(rd.y < -0.002){
float t = -ro.y/rd.y;
if(t > 0.0 && t < 60.0){
vec3 tc = tableCol(ro + rd*t, rd, t);
return mix(tc, backdrop(rd), smoothstep(4.0, 24.0, t)); // fuse into sweep
}
}
return backdrop(rd);
}
vec3 strawShade(vec3 p, vec3 rd){
vec2 e = vec2(0.002, 0.0);
vec3 n = normalize(vec3(
sdStraw(p+e.xyy)-sdStraw(p-e.xyy),
sdStraw(p+e.yxy)-sdStraw(p-e.yxy),
sdStraw(p+e.yyx)-sdStraw(p-e.yyx)));
float s = dot(p - SB, SD);
float stripe = step(0.5, fract(s*3.2)); // candy rings
vec3 alb = mix(vec3(0.93,0.94,0.95), vec3(0.82,0.10,0.09), stripe);
float dif = clamp(dot(n, LDIR), 0.0, 1.0)*0.85 + 0.35;
vec3 col = alb*dif;
vec3 hlf = normalize(LDIR - rd);
col += vec3(1.0)*pow(max(dot(n, hlf), 0.0), 60.0)*0.8; // plastic gloss
col += backdrop(reflect(rd, n))*0.08;
return col;
}
vec2 mapP(vec3 p){
float g = sdGlass(p);
vec2 r = vec2(g, 1.0);
if(p.y < r.x) r = vec2(p.y, 2.0);
float st = sdStraw(p);
if(st < r.x) r = vec2(st, 3.0);
return r;
}
vec2 marchP(vec3 ro, vec3 rd){
float t = 0.0;
for(int i=0;i<130;i++){
vec2 h = mapP(ro + rd*t);
if(h.x < 0.0012) return vec2(t, h.y);
t += h.x;
if(t > 40.0) break;
}
return vec2(-1.0, 0.0);
}
// ---------- juice transmission ----------
vec3 transmit(vec3 pEnter, vec3 rdIn, vec3 nO){
vec3 rd = refract(rdIn, nO, 1.0/1.50);
if(dot(rd,rd) < 1e-4) rd = reflect(rdIn, nO);
vec3 p = pEnter + rd*0.012;
float jl = 0.0, st = 0.028;
bool hitS = false; vec3 sp = vec3(0.0), sn = vec3(0.0);
bool hitStraw = false;
float prev = sdJuice(p);
for(int i=0;i<170;i++){
p += rd*st;
if(sdStraw(p) < 0.012){ // straw inside glass/juice
vec3 a = p - rd*st, b = p; // bisect for a clean edge
for(int k=0;k<5;k++){ vec3 m = 0.5*(a+b); if(sdStraw(m) < 0.012) b = m; else a = m; }
p = b; hitStraw = true; break;
}
float dj = sdJuice(p);
if(dj < 0.0) jl += st;
if(!hitS && prev > 0.0 && dj <= 0.0){ hitS = true; sp = p; sn = nJuice(p); }
prev = dj;
if(length(p.xz) > Rout + 0.05 || p.y > GH + 0.08 || p.y < -0.05) break;
}
vec3 bg;
if(hitStraw){
bg = strawShade(p, rd);
} else {
vec3 nOut = nGlass(p);
vec3 rout = refract(rd, -nOut, 1.50);
if(dot(rout,rout) < 1e-4) rout = rd;
bg = envir(p, rout);
}
vec3 absr = exp(-jl*vec3(0.50, 3.0, 11.0)); // Beer-Lambert, deep orange
float dens = 1.0 - exp(-jl*6.0);
float grad = smoothstep(BT, JLVL, pEnter.y);
float lit = 0.5 + 0.5*dot(LDIR, rd);
float back = pow(max(dot(rd, -BDIR), 0.0), 2.0); // backlight punch-through (capped)
vec3 sss = juiceBody(lit)*dens*(0.55 + 0.5*grad);
sss += vec3(1.0,0.50,0.08)*back*dens*(0.20 + 0.38*grad);
float pulp = 0.97 + 0.05*h31(floor(pEnter*46.0));
sss *= pulp;
vec3 col = bg*absr*(1.0 - dens*0.90) + sss*1.25;
if(hitS && sn.y > 0.6){ // TOP juice surface only
float g2 = pow(max(dot(reflect(rd, sn), LDIR), 0.0), 90.0);
col += g2*vec3(1.0,0.93,0.75)*0.9;
float rr = length(sp.xz);
float ring = smoothstep(Rin-0.16, Rin-0.04, rr); // bubble ring at edge
if(h31(floor(sp*95.0)) > 0.90) col += vec3(1.0,0.9,0.7)*ring*0.8;
}
return col;
}
// ---------- main ----------
void mainImage(out vec4 fragColor, in vec2 fragCoord){
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
// telephoto, near juice level, barely drifting
float ang = iTime*(2.0*PI/24.0); // slow full orbit, 24 s loop
vec3 ta = vec3(0.0, 1.42, 0.0);
vec3 ro = ta + vec3(sin(ang)*8.0, 1.55, cos(ang)*8.0);
vec3 fw = normalize(ta - ro);
vec3 rt = normalize(cross(vec3(0,1,0), fw));
vec3 up = cross(fw, rt);
vec3 rd = normalize(fw*1.75 + uv.x*rt + uv.y*up);
vec3 col;
vec2 h = marchP(ro, rd);
if(h.y < 0.5){
col = envir(ro, rd);
} else if(h.y < 1.5){ // glass
vec3 p = ro + rd*h.x;
vec3 n = nGlass(p);
float rr = length(p.xz);
// condensation bump (outer wall, below juice line)
float dmask = smoothstep(0.45, 0.70, p.y)*smoothstep(JLVL-0.10, JLVL-0.55, p.y)
* step(Rin + 0.02, rr);
float drop = 0.0;
if(dmask > 0.01){
vec2 q = vec2(atan(p.z, p.x), p.y);
drop = dropField(q);
vec2 e2 = vec2(0.012, 0.0);
vec2 gr = vec2(dropField(q+e2.xy) - dropField(q-e2.xy),
dropField(q+e2.yx) - dropField(q-e2.yx))/(2.0*e2.x);
vec3 t1 = normalize(vec3(-p.z, 0.0, p.x));
n = normalize(n - (gr.x*t1 + gr.y*vec3(0,1,0))*0.18*dmask);
}
float fre = pow(1.0 - max(dot(-rd,n), 0.0), 5.0);
float f = 0.05 + 0.95*fre;
vec3 refl = envir(p, reflect(rd, n));
vec3 tr = transmit(p, rd, n);
col = mix(tr, refl, f);
// speculars: key + backlight kicker
vec3 h1 = normalize(LDIR - rd);
float nh1 = max(dot(n, h1), 0.0);
col += vec3(1.0,0.98,0.92)*pow(nh1, 180.0)*2.2;
col += vec3(1.0,0.97,0.90)*pow(nh1, 36.0)*0.28;
vec3 h2 = normalize(-BDIR - rd);
col += vec3(1.0,0.92,0.78)*pow(max(dot(n,h2),0.0), 120.0)*1.6;
// rim light + droplet sparkle + meniscus line
col += pow(1.0 - max(dot(-rd,n),0.0), 4.0)*vec3(1.0,0.98,0.93)*0.5;
col += drop*dmask*vec3(1.0,0.97,0.9)*0.10;
col += vec3(1.0,0.85,0.55)*smoothstep(0.05,0.0,abs(p.y-JLVL-0.02))*0.35;
} else if(h.y < 2.5){
vec3 p = ro + rd*h.x;
col = mix(tableCol(p, rd, h.x), backdrop(rd), smoothstep(4.0, 24.0, h.x)); // match envir fuse
} else {
col = strawShade(ro + rd*h.x, rd); // straw above the rim
}
// --- film finish ---
col *= 0.88;
col = clamp((col*(2.51*col + 0.03))/(col*(2.43*col + 0.59) + 0.14), 0.0, 1.0); // ACES
col = pow(col, vec3(0.4545));
float vig = 1.0 - 0.38*pow(length(uv*vec2(0.80, 1.10)), 2.4);
col *= vig;
col += (h21(fragCoord.xy + fract(iTime)*7.13) - 0.5)*0.012; // grain
// — signature: "CLAUDE" in 5x5 pixel type, bottom-right, like ink on the print —
float sgs = 0.0085; // cell size in uv units
vec2 suv = (uv - vec2(0.5*iResolution.x/iResolution.y - 44.0*sgs, -0.5 + 13.0*sgs))/sgs;
if(suv.x >= 0.0 && suv.y >= 0.0 && suv.y < 5.0 && suv.x < 36.0){
int li = int(suv.x)/6;
float lx = mod(suv.x, 6.0);
if(lx < 5.0 && li < 6){
int code = li==0 ? 14713902 : li==1 ? 1082415 : li==2 ? 15269425 :
li==3 ? 18400814 : li==4 ? 16303663 : 32554047; // C L A U D E
int idx = int(lx) + int(suv.y)*5;
if(((code >> idx) & 1) == 1) col = mix(col, vec3(0.42,0.38,0.36), 0.55);
}
}
fragColor = vec4(col, 1.0);
}
Image
Not used
More shaders by scry
- Project_2026-08-15_18-22-06
- Project_2026-08-14_16-45-00
- Project_2026-07-24_15-10-23
- Project_2026-07-15_03-18-26
- Project_2026-07-13_00-53-50
- Project_2026-07-10_16-39-54
- Popcorn ceiling
- Project_2026-06-20_15-32-19
- Project_2026-06-25_12-22-43
- Visual Enhancer
- Project_2026-06-24_11-11-39
- Project_2026-06-23_19-17-29
Browse all public shaders · All shaders by scry · ShaderKit home
Unread comments
×Projects with new comments. Click to open and read.