fork a shader
Write or paste an ISF shader (GLSL ES with a /*{ ... }*/ header). It compiles + renders in yer browser. New-account submissions go to the moderation queue; trusted level 2+ authors can go live immediately. Everything is CC0. Got a file first? preview it locally →
A model marks this as witnessed_generation (honest AI provenance). Leave blank if you hand-authored it.
/*{ "DESCRIPTION": "Opal Slipstream: Cloudsurf with wind-carried, flight-reactive smoke. Translucent vapor and twisting pearl folds follow previous emitter poses, fan through banks and change their apparent thickness and opal highlights as the camera moves.", "CATEGORIES": [ "3D", "illustration" ], "INPUTS": [ { "NAME": "azimuth", "LABEL": "Camera azimuth", "TYPE": "float", "DEFAULT": 50.36, "MIN": 35, "MAX": 115 }, { "NAME": "elevation", "LABEL": "Camera elevation", "TYPE": "float", "DEFAULT": 12.012, "MIN": 8, "MAX": 42 }, { "NAME": "flight", "LABEL": "Flight motion", "TYPE": "float", "DEFAULT": 0.912, "MIN": 0, "MAX": 1.5 }, { "NAME": "smoke", "LABEL": "Smoke ribbon", "TYPE": "float", "DEFAULT": 1.5, "MIN": 0, "MAX": 1.5 }, { "NAME": "curl", "LABEL": "Smoke curls", "TYPE": "float", "DEFAULT": 1.6, "MIN": 0, "MAX": 1.6 }, { "NAME": "opalescence", "LABEL": "Smoke opalescence", "TYPE": "float", "DEFAULT": 1, "MIN": 0, "MAX": 1 }, { "NAME": "skin", "LABEL": "Mint to jade", "TYPE": "float", "DEFAULT": 0.534, "MIN": 0, "MAX": 1 }, { "NAME": "sky", "LABEL": "Sky warmth", "TYPE": "float", "DEFAULT": 1, "MIN": 0, "MAX": 1 }, { "NAME": "papergrain", "LABEL": "Paper grain", "TYPE": "float", "DEFAULT": 0.514, "MIN": 0, "MAX": 1 }, { "NAME": "softness", "LABEL": "Light softness", "TYPE": "float", "DEFAULT": 0.13111546903730154, "MIN": 0, "MAX": 1 }, { "NAME": "journey", "LABEL": "Camera journey", "TYPE": "float", "DEFAULT": 0.627542615913452, "MIN": 0, "MAX": 1 }, { "NAME": "clouds", "LABEL": "Cloud sea", "TYPE": "float", "DEFAULT": 0.7219870608089806, "MIN": 0, "MAX": 1 } ] }*/ #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #endif mat2 turn(float a) { float c=cos(a),s=sin(a); return mat2(c,-s,s,c); } float sat(float x) { return clamp(x,0.0,1.0); } float blend(float a,float b,float k) { float h=sat(0.5+0.5*(b-a)/k); return mix(b,a,h)-k*h*(1.0-h); } float ell(vec3 p,vec3 r) { float a=length(p/r), b=length(p/(r*r)); return a*(a-1.0)/max(b,0.0001); } float rod(vec3 p,vec3 a,vec3 b,float r0,float r1) { vec3 v=b-a; float h=sat(dot(p-a,v)/dot(v,v)); return length(p-a-v*h)-mix(r0,r1,h); } // Continuous cubic sweep, not a chain of capsules. Minimize distance to // variable-radius spheres on the curve with a sampled bracket and safeguarded // Newton steps. Unbracketed Newton jumps across thin tips and creates speckles. // Hull rejection keeps irrelevant curves out of the ray/normal/shadow budget. float bezierTube(vec3 p,vec3 a,vec3 b,vec3 c,vec3 d,float r0,float r1,float ceiling) { vec3 lo=min(min(a,b),min(c,d)),hi=max(max(a,b),max(c,d)); float lower=length(max(max(lo-p,p-hi),0.0))-max(r0,r1); if(lower>ceiling+0.15) return lower; vec3 k1=3.0*(b-a),k2=3.0*(a-2.0*b+c),k3=d-a+3.0*(b-c); float dr=r0-r1; float best=100.0,t=0.0; for(int s=0;s<=16;s++) { float u=float(s)/16.0; float f=length(a+u*(k1+u*(k2+u*k3))-p)-r1-dr*(1.0-u)*(1.0-u); if(f<best) { best=f; t=u; } } float left=max(0.0,t-0.0625),right=min(1.0,t+0.0625); for(int j=0;j<8;j++) { vec3 v=a+t*(k1+t*(k2+t*k3))-p; vec3 v1=k1+t*(2.0*k2+3.0*t*k3),v2=2.0*k2+6.0*t*k3; float len=max(length(v),0.00001),r=r1+dr*(1.0-t)*(1.0-t); best=min(best,len-r); float slope=dot(v,v1)/len; float g=slope+2.0*dr*(1.0-t); float h=(dot(v1,v1)+dot(v,v2)-slope*slope)/len-2.0*dr; if(g>0.0) right=t; else left=t; float next=t-g/max(h,0.00001); if(h<=0.00001 || next<=left || next>=right) next=0.5*(left+right); t=next; } return best; } vec2 pick(vec2 a,vec2 b) { return a.x<b.x?a:b; } const float RUN=26.0; float ease(float a,float b,float x) { float u=sat((x-a)/(b-a)); return u*u*u*(10.0+u*(-15.0+6.0*u)); } float beat(float x,float a,float b,float c,float d) { return ease(a,b,x)*(1.0-ease(c,d,x)); } // The course is expressed in world X so the clouds and craft share the same // corridor without solving a closest path or integrating hidden camera state. vec2 course(float x) { float s=mod(x/3.5,RUN); return journey*vec2(-2.10*beat(s,4.0,9.0,11.0,18.0) +1.45*beat(s,14.0,20.0,21.0,26.0), 8.0*beat(s,6.0,12.0,15.0,23.0)); } vec3 positionAt(float t) { float x=3.5*t-journey*6.0*(sin(t*6.2831853/RUN+0.3)-sin(0.3)); vec2 yz=course(x); return vec3(x,yz.x,yz.y); } // Analytic velocity/acceleration avoid subtracting large world positions to // recover a tiny turn rate (which can otherwise cause long-run bank jitter). vec2 easeDerivatives(float a,float b,float x) { float u=sat((x-a)/(b-a)),v=1.0-u; return vec2(30.0*u*u*v*v/(b-a),60.0*u*(2.0*u*u-3.0*u+1.0)/((b-a)*(b-a))); } vec2 beatDerivatives(float x,float a,float b,float c,float d) { return easeDerivatives(a,b,x)-easeDerivatives(c,d,x); } void motionAt(float t,out vec3 v,out vec3 acc) { float k=6.2831853/RUN,theta=k*t+0.3; float x=positionAt(t).x,phase=mod(x/3.5,RUN); float speed=3.5-journey*6.0*k*cos(theta),push=journey*6.0*k*k*sin(theta); vec2 y=journey*(-2.10*beatDerivatives(phase,4.0,9.0,11.0,18.0) +1.45*beatDerivatives(phase,14.0,20.0,21.0,26.0))/vec2(3.5,12.25); vec2 z=journey*8.0*beatDerivatives(phase,6.0,12.0,15.0,23.0)/vec2(3.5,12.25); v=vec3(speed,y.x*speed,z.x*speed); acc=vec3(push,y.y*speed*speed+y.x*push,z.y*speed*speed+z.x*push); } vec3 velocityAt(float t) { vec3 v,a; motionAt(t,v,a); return v; } mat3 frameAt(float t,out float bank,out float pitch) { vec3 v,acc; motionAt(t,v,acc); vec3 f=normalize(v),side=normalize(cross(f,vec3(0,1,0))),up=cross(side,f); bank=clamp(atan(dot(acc,side)*0.70),-0.48,0.48); pitch=atan(v.y,length(v.xz)); return mat3(f,up*cos(bank)+side*sin(bank),side*cos(bank)-up*sin(bank)); } vec3 localVector(vec3 p,mat3 frame) { return vec3(dot(p,frame[0]),dot(p,frame[1]),dot(p,frame[2])); } float gSway,gNod,gBank,gFlutter,gTime; mat3 gFrame; vec3 restPoint(vec3 p) { p.z-=gSway*smoothstep(0.72,1.70,p.y); p.x-=gNod*smoothstep(1.0,1.85,p.y); return p; } // A single continuous animal. Feet sit on the ridge; neck and tail are not // independently rotating ornaments. Small details have their own materials. vec2 animal(vec3 p) { p=restPoint(p); float d=ell(p-vec3(-0.30,0.64,0.0),vec3(0.65,0.36,0.30)); d=blend(d,bezierTube(p,vec3(0.10,0.65,0.0),vec3(0.78,0.86,0.0), vec3(0.13,1.70,0.0),vec3(0.91,1.855,0.0),0.25,0.104,d),0.13); d=blend(d,ell(p-vec3(0.99,1.87,0.0),vec3(0.29,0.155,0.155)),0.10); vec3 leg=p; leg.z=abs(leg.z); d=blend(d,bezierTube(leg,vec3(-0.65,0.59,0.21),vec3(-0.85,0.44,0.22), vec3(-0.73,0.26,0.25),vec3(-0.66,0.16,0.23),0.145,0.105,d),0.08); d=blend(d,bezierTube(leg,vec3(0.07,0.57,0.21),vec3(-0.04,0.44,0.22), vec3(0.21,0.29,0.25),vec3(0.11,0.16,0.23),0.13,0.092,d),0.08); float wag=0.11*sin(TIME*0.85*flight)+0.35*gBank; d=blend(d,bezierTube(p,vec3(-0.76,0.65,0.0),vec3(-1.30,0.43,wag*0.5), vec3(-1.30,1.28,wag),vec3(-2.08,0.99+0.035*sin(TIME*flight),wag+0.025),0.20,0.010,d),0.12); // Flat foot contact with the actual creased upper sheet. The low tracking // shot must not reveal the old rounded toe tips piercing the paper underside. float run=(2.18-p.x)/3.73; float deck=0.04-0.07025*run+0.3625*abs(p.z); if(abs(p.z)>0.58*run) { float slope=(0.06+(p.z>0.0?gFlutter:-0.4*gFlutter))/0.52; deck=0.04+(0.14-0.58*slope)*run+slope*abs(p.z); } d=max(d,deck+0.002-p.y); vec2 result=vec2(d,1.0); // Half-lidded eyes, small nostrils, and a relaxed mouth line on both sides. vec3 q=p; q.z=abs(q.z); result=pick(result,vec2(ell(q-vec3(1.015,1.945,0.139),vec3(0.060,0.037,0.024)),3.0)); result=pick(result,vec2(ell(q-vec3(1.027,1.944,0.160),vec3(0.030,0.017,0.014)),4.0)); result=pick(result,vec2(ell(q-vec3(1.214,1.912,0.10),vec3(0.018,0.012,0.012)),4.0)); result=pick(result,vec2(rod(p,vec3(1.215,1.826,0.022),vec3(1.69,1.784,0.022),0.035,0.027),5.0)); result=pick(result,vec2(rod(p,vec3(1.66,1.786,0.022),vec3(1.705,1.782,0.022),0.028,0.027),6.0)); return result; } void face(vec3 ro,vec3 rd,vec3 a,vec3 b,vec3 c,float id,inout float t,inout vec3 n,inout float m) { vec3 e=b-a,f=c-a,h=cross(rd,f); float det=dot(e,h); if(abs(det)<0.00001) return; vec3 s=ro-a; float u=dot(s,h)/det; vec3 q=cross(s,e); float v=dot(rd,q)/det; float z=dot(f,q)/det; if(u>=0.0 && v>=0.0 && u+v<=1.0 && z>0.0 && z<t) { t=z; n=normalize(cross(e,f)); if(dot(n,rd)>0.0) n=-n; m=id; } } void paper(vec3 ro,vec3 rd,inout float t,inout vec3 n,inout float m) { vec3 nose=vec3(2.18,0.04,0.0); // One connected folded sheet. A common straight trailing cut replaces the // old staggered triangles; two creased wing panels taper into the same nose. // A narrow V body hangs below the wing roots, instead of an upright dorsal fin. vec3 root=vec3(-1.55,0.035,0.18); vec3 fold=vec3(-1.55,0.18,0.58); vec3 tip=vec3(-1.55,0.24+gFlutter,1.10); vec3 keel=vec3(-1.55,-0.31,0.0); face(ro,rd,nose,fold,tip,10.0,t,n,m); face(ro,rd,nose,root,fold,11.0,t,n,m); face(ro,rd,nose,keel,root,14.0,t,n,m); root.z=-root.z; fold.z=-fold.z; tip.z=-tip.z; tip.y-=1.4*gFlutter; face(ro,rd,nose,tip,fold,13.0,t,n,m); face(ro,rd,nose,fold,root,12.0,t,n,m); face(ro,rd,nose,root,keel,15.0,t,n,m); } vec2 bounds(vec3 ro,vec3 rd) { vec3 inv=1.0/(rd+vec3(0.000001)); vec3 a=(vec3(-2.20,0.03,-0.49)-ro)*inv; vec3 b=(vec3(1.80,2.15,0.49)-ro)*inv; vec3 lo=min(a,b),hi=max(a,b); return vec2(max(max(lo.x,lo.y),lo.z),min(min(hi.x,hi.y),hi.z)); } // Smooth model-space pulp and fibres. Frequencies above the approximate pixel // footprint fade out rather than becoming screen noise as the plane banks. float paperHash(vec2 p) { vec3 h=fract(vec3(p.xyx)*0.1031); h+=dot(h,h.yzx+33.33); return fract((h.x+h.y)*h.z); } float paperNoise(vec2 p) { vec2 i=floor(p),f=fract(p); f=f*f*(3.0-2.0*f); return mix(mix(paperHash(i),paperHash(i+vec2(1,0)),f.x), mix(paperHash(i+vec2(0,1)),paperHash(i+vec2(1,1)),f.x),f.y); } vec3 paperSurface(vec3 p,float footprint) { vec2 sheet=vec2(p.x,p.z+0.35*p.y); float pulp=(paperNoise(sheet*4.5)-0.5)*(1.0-smoothstep(0.35,0.85,footprint*4.5)); float fibre=(paperNoise(sheet*vec2(7.0,27.0))-0.5)*(1.0-smoothstep(0.25,0.8,footprint*27.0)); vec3 base=vec3(0.91,0.89,0.83)*(1.0+papergrain*(0.10*pulp+0.09*fibre)); float run=sat((2.18-p.x)/3.73); float w=max(0.014,footprint*0.7); float ridge=(abs(p.z)-0.58*run)/w; float root=(abs(p.z)-0.18*run)/w; // Integrated narrow creases: wider but fainter when subpixel. Not black ink. base*=1.0-(0.085*exp(-ridge*ridge)+0.06*exp(-root*root))*0.014/w; return base; } float shadow(vec3 p,vec3 l) { vec2 span=bounds(p,l); if(span.x>span.y || span.y<0.025) return 1.0; float t=max(0.012,span.x),s=1.0; for(int i=0;i<28;i++) { float h=animal(p+l*t).x; s=min(s,mix(14.0,4.0,softness)*max(h,0.0)/t); t+=clamp(h,0.018,0.20); if(h<0.001 || t>min(2.7,span.y)) break; } return 0.26+0.74*sat(s); } vec3 normal(vec3 p) { vec2 e=vec2(1.0,-1.0)*0.001; return normalize(e.xyy*animal(p+e.xyy).x+e.yyx*animal(p+e.yyx).x +e.yxy*animal(p+e.yxy).x+e.xxx*animal(p+e.xxx).x); } // Reconstruct a short-lived parcel from its emission pose. World wind, lift // and bounded inertial drift replace a sine wave translated with the aircraft. // No camera coordinates enter the trajectory: changing views reveals real depth. void smokeSample(float u,float lifetime,out vec3 center,out vec3 spread,out float age,out float pulse) { age=lifetime*u; float emitted=TIME-age,past=gTime-age*flight/0.65; float bank,pitch; mat3 pose=frameAt(past,bank,pitch); vec3 v,acc; motionAt(past,v,acc); float pace=flight/0.65; v*=pace; acc=clamp(acc*pace*pace,vec3(-5.0),vec3(5.0)); vec3 ember=vec3(1.71-0.12*pitch*smoothstep(1.0,1.85,1.788), 1.788,0.022-0.14*bank); center=positionAt(past)+pose*ember; vec3 forward=normalize(v+vec3(0.85,0.0,0.10)); vec3 side=normalize(cross(forward,vec3(0,1,0))),lift=cross(side,forward); // Newly emitted smoke inherits velocity, then loses it to the surrounding air. center+=v*(0.22*(1.0-exp(-age/0.22))); vec3 wind=vec3(-0.85,0.0,0.26*sin(emitted*0.71+center.x*0.10)); wind+=vec3(0.12*sin(emitted*0.47),0.08*sin(emitted*0.9+1.0), 0.20*sin(emitted*0.43+center.z*0.23)); center+=wind*age-acc*(0.085*age*age); center.y+=0.80*age+0.28*(1.0-exp(-age*4.0)); float opening=smoothstep(0.025,0.24,u)*pow(u,0.70); float theta=13.0*age-1.8*emitted+0.95*sin(emitted*0.57+age*2.1); float radius=(0.08+0.38*curl)*opening*(0.78+0.22*sin(emitted*1.3+age*3.2)); radius*=1.0+0.60*abs(bank); center+=radius*(side*cos(theta)+lift*0.72*sin(theta)); center+=side*(0.11*curl*opening*sin(19.0*age+emitted*0.9)); float twist=theta*0.57+emitted*0.34+bank*0.9; spread=side*cos(twist)+lift*sin(twist); pulse=0.78+0.16*sin(emitted*2.3)+0.06*sin(emitted*5.1+0.7); } // Analytic ray integral through an oriented Gaussian parcel. Segment-length // weights remove the old subdivision-dependent bright knots; an ellipse turns // from a diffuse veil to a fine edge as the camera and the wake move past it. vec2 smokeParcel(vec3 ro,vec3 rd,vec3 p,vec3 tangent,vec3 across,vec3 normal, vec3 widths,float segmentLength,float hit) { vec3 q=p-ro; vec3 o=vec3(dot(q,tangent),dot(q,across),dot(q,normal))/widths; vec3 v=vec3(dot(rd,tangent),dot(rd,across),dot(rd,normal))/widths; float aa=max(dot(v,v),0.00001),depth=dot(o,v)/aa; vec3 nearest=o-v*depth; float radial=dot(nearest,nearest),rayWidth=inversesqrt(aa); // Smooth truncation approximates the finite ray integral at opaque surfaces. float visible=smoothstep(-rayWidth,rayWidth,depth) *smoothstep(-rayWidth,rayWidth,hit-depth); float mass=segmentLength/max(widths.x,0.001); return vec2(exp(-radial)*rayWidth*mass*visible,depth); } float cloudHash(vec3 p) { p=fract(p*0.1031); p+=dot(p,p.yzx+31.32); return fract((p.x+p.y)*p.z); } float cloudNoise(vec3 p) { vec3 i=floor(p),f=fract(p); f=f*f*(3.0-2.0*f); return mix(mix(mix(cloudHash(i),cloudHash(i+vec3(1,0,0)),f.x), mix(cloudHash(i+vec3(0,1,0)),cloudHash(i+vec3(1,1,0)),f.x),f.y), mix(mix(cloudHash(i+vec3(0,0,1)),cloudHash(i+vec3(1,0,1)),f.x), mix(cloudHash(i+vec3(0,1,1)),cloudHash(i+vec3(1,1,1)),f.x),f.y),f.z); } float cloudDensity(vec3 p) { vec2 cell=floor(p.xz/7.0),f=fract(p.xz/7.0)-0.5; float d=p.y+5.7; for(int j=-1;j<=1;j++) for(int i=-1;i<=1;i++) { vec2 offset=vec2(float(i),float(j)); float seed=paperHash(cell+offset); vec2 jitter=vec2(paperHash(cell+offset+4.7),paperHash(cell+offset+15.3))-0.5; vec3 q=vec3((f.x-offset.x-jitter.x*0.4)*7.0,p.y+4.7-seed*0.3, (f.y-offset.y-jitter.y*0.4)*7.0); vec3 radius=vec3(2.55+seed,1.6+seed*1.0,2.6+seed*0.9); d=blend(d,(length(q/radius)-1.0)*radius.y,0.65); } // Two shoulders motivate the bank. A clear, world-space channel follows the // same course as the aircraft; the camera never relies on a moving fog eraser. float x=mod(p.x+5.0,91.0)-5.0; vec3 a=(vec3(x,p.y,p.z)-vec3(35.0,-3.8,0.5))/vec3(6.0,5.3,5.6); vec3 b=(vec3(x,p.y,p.z)-vec3(60.0,-4.8,11.5))/vec3(5.5,4.0,4.7); d=blend(d,(length(a)-1.0)*4.5+0.75*(0.5-cloudNoise(p*0.55)),0.85); d=blend(d,(length(b)-1.0)*4.0,0.85); d+=(0.5-cloudNoise(p*1.05))*0.65+(0.5-cloudNoise(p*2.3))*0.18; vec2 yz=course(p.x); float corridor=length(vec2(p.z-yz.y,(p.y-yz.x-0.65)*0.86)); return (1.0-smoothstep(-0.60,0.45,d))*smoothstep(-8.0,-7.0,p.y) *smoothstep(1.30,2.65,corridor); } vec3 skyColor(vec3 rd) { vec3 sun=normalize(vec3(-0.54,0.22,-0.84)); float horizon=pow(1.0-max(rd.y,0.0),5.0); vec3 zenith=mix(vec3(0.16,0.43,0.65),vec3(0.13,0.29,0.48),sky); vec3 haze=mix(vec3(0.77,0.88,0.91),vec3(0.98,0.72,0.49),sky); vec3 bg=mix(zenith,haze,horizon); float sunDot=max(dot(rd,sun),0.0); bg+=vec3(0.48,0.28,0.095)*pow(sunDot,48.0); return mix(bg,vec3(1.0,0.96,0.77),smoothstep(0.9983,0.9988,sunDot)); } vec2 cloudSpan(vec3 ro,vec3 rd,float hit) { float vy=abs(rd.y)<0.00001?0.00001:rd.y; float a=(-8.0-ro.y)/vy,b=(2.8-ro.y)/vy; return vec2(max(0.0,min(a,b)),min(min(hit,95.0),max(a,b))); } vec3 cloudComposite(vec3 ro,vec3 rd,vec3 background,float hit) { if(clouds<0.001) return background; vec2 span=cloudSpan(ro,rd,hit); float t=span.x,trans=1.0; vec3 sum=vec3(0); vec3 sun=normalize(vec3(-0.54,0.22,-0.84)); vec3 haze=mix(vec3(0.77,0.88,0.91),vec3(0.98,0.72,0.49),sky); float sunDot=max(dot(rd,sun),0.0); // Near samples are dense and deterministic. End exactly at opaque geometry: // a cloud shoulder can pass IN FRONT of a wing without a flat screen overlay. for(int i=0;i<160;i++) { if(t>=span.y || trans<0.018) break; float ds=min(max(0.14,t*0.028),span.y-t); vec3 p=ro+rd*(t+0.5*ds); float d=cloudDensity(p); if(d>0.005) { float optical=1.1*cloudDensity(p+sun*0.65)+0.9*cloudDensity(p+sun*1.8); float light=0.20+0.80*exp(-optical); vec3 cc=mix(vec3(0.40,0.47,0.59),vec3(1.03,0.90,0.74),light); cc+=vec3(0.25,0.17,0.09)*pow(sunDot,16.0)*light; cc=mix(cc,haze,smoothstep(14.0,70.0,t)*0.90); float a=1.0-exp(-d*ds*0.85*clouds); sum+=trans*a*cc; trans*=1.0-a; } t+=ds; } return sum+trans*background; } float smokeTransmission(vec3 ro,vec3 rd,float depth) { vec2 span=cloudSpan(ro,rd,depth); if(span.y<=span.x || clouds<0.001) return 1.0; float ds=(span.y-span.x)/8.0,tau=0.0; for(int i=0;i<8;i++) tau+=cloudDensity(ro+rd*(span.x+(float(i)+0.5)*ds))*ds; return exp(-0.85*clouds*tau); } void main() { vec2 uv=(gl_FragCoord.xy-0.5*RENDERSIZE)/min(RENDERSIZE.x,RENDERSIZE.y)*2.0; float tm=TIME*flight/0.65; gTime=tm; float bank,pitch; mat3 pose=frameAt(tm,bank,pitch); gFrame=pose; gBank=bank; gSway=-0.14*bank; gNod=-0.12*pitch; vec3 origin=positionAt(tm); float phase=mod(origin.x/3.5,RUN); float dive=beat(phase,4.0,8.0,9.0,13.0); float carve=beat(phase,8.0,11.0,14.0,18.0); float reveal=beat(phase,18.0,22.0,23.0,26.0); gFlutter=journey*(0.010+0.018*dive)*sin(tm*8.4); vec3 lag=velocityAt(tm-0.65); float az=azimuth*0.017453293+0.60*atan(lag.z,lag.x) +journey*(0.23*carve-0.13*dive+0.16*reveal); float el=clamp(elevation+journey*(-5.0+14.0*dive-6.0*carve+11.0*reveal),5.0,50.0)*0.017453293; vec3 low=mix(vec3(0.77,0.89,0.90),vec3(0.96,0.82,0.66),sky); vec3 target=origin+vec3(0.0,1.02,0.0)+pose[0]*(0.10*dive); float distance=7.10+journey*(-0.40*carve+1.5*reveal+0.35*cos(tm*6.2831853/RUN)); vec3 worldRo=target+distance*vec3(cos(az)*cos(el),sin(el),sin(az)*cos(el)) -velocityAt(tm)*(0.10*journey*dive); vec3 fw=normalize(target-worldRo),rt=normalize(cross(fw,vec3(0,1,0))),up=cross(rt,fw); vec2 lens=turn(bank*0.22)*uv; float focal=2.43+journey*(-0.15*dive+0.10*carve); vec3 worldRd=normalize(rt*lens.x+up*lens.y+fw*focal); vec3 col=skyColor(worldRd); vec3 ro=localVector(worldRo-origin,pose),rd=localVector(worldRd,pose); float hit=100.0,material=0.0; vec3 n=vec3(0,1,0); paper(ro,rd,hit,n,material); vec2 span=bounds(ro,rd); float t=max(span.x,0.0),end=min(span.y,hit); for(int i=0;i<116;i++) { if(t>end) break; vec2 d=animal(ro+rd*t); if(d.x<0.0015) { hit=t; material=d.y; n=normal(ro+rd*t); break; } t+=max(d.x*0.78,0.0008); } if(material>0.5) { vec3 p=ro+rd*hit; vec3 light=localVector(normalize(vec3(-0.65,1.3,1.8)),pose); vec3 base=mix(vec3(0.38,0.73,0.48),vec3(0.13,0.47,0.39),skin); if(material<2.0) { vec3 q=restPoint(p); float belly=(1.0-smoothstep(0.48,0.66,q.y))*smoothstep(-0.93,-0.45,q.x)*(1.0-smoothstep(0.12,0.28,q.x)); base=mix(base,vec3(0.72,0.80,0.50),belly*0.7); float freckles=sin(q.x*31.0)*sin(q.y*28.0)*sin(q.z*25.0); base*=1.0-0.07*smoothstep(0.65,0.9,freckles); // A mouth marking lives on the skin rather than floating beside it. float mouth=abs(q.y-1.822)+max(1.00-q.x,0.0)+max(q.x-1.235,0.0); base=mix(base,vec3(0.055,0.15,0.10),0.7*(1.0-smoothstep(0.002,0.009,mouth))); } else if(material<3.5) base=vec3(0.92,0.94,0.74); else if(material<4.5) base=vec3(0.025,0.08,0.065); else if(material<5.5) base=vec3(0.96,0.90,0.71); else if(material<7.0) base=vec3(0.34,0.095,0.025); else { float footprint=2.0*hit/(focal*min(RENDERSIZE.x,RENDERSIZE.y)*max(0.12,abs(dot(n,rd)))); base=paperSurface(p,footprint); if(material>10.5 && material<12.5) base*=0.975; if(material>13.5) base*=vec3(0.88,0.92,0.96); } // Share the two expensive distance evaluations across material branches. // Close paper contact stays dark while the directional penumbra opens out. float nearAO=material>7.0?0.075:0.12,farAO=material>7.0?0.22:0.30; float ao=1.0-(material>7.0?0.30:0.27)*sat((nearAO-animal(p+n*nearAO).x)/nearAO); ao-=(material>7.0?0.12:0.15)*sat((farAO-animal(p+n*farAO).x)/farAO); float wrap=0.16*softness; float dif=sat((dot(n,light)+wrap)/(1.0+wrap)),sh=shadow(p+n*0.009,light); vec3 ambient=mix(vec3(0.27,0.28,0.27),vec3(0.44,0.53,0.61),0.5+0.5*n.y); // A little diffuse skin fill keeps the jade buoyant under this softer key. // Warm reflected paper light under the belly, rather than a grey cavity. if(material<2.0) { ambient+=vec3(0.035,0.070,0.035); ambient+=vec3(0.13,0.10,0.055)*(1.0-smoothstep(0.25,0.95,p.y))*sat(0.4-n.y); } float opening=1.0-journey*0.20*carve+journey*0.12*reveal; col=base*(ambient*ao+vec3(0.75,0.68,0.55)*dif*sh*opening); float spec=pow(max(dot(n,normalize(light-rd)),0.0),mix(30.0,13.0,softness)); if(material<2.0) col+=vec3(0.075,0.080,0.067)*mix(1.0,0.52,softness)*spec*sh; if(material>7.0) col+=base*vec3(0.07,0.055,0.035)*pow(max(dot(-n,light),0.0),2.0); if(material>5.5 && material<7.0) col+=vec3(0.40,0.10,0.015)*(0.85+0.15*sin(TIME*2.2)); col+=vec3(0.24,0.16,0.065)*pow(1.0-max(dot(n,-rd),0.0),3.0)*sat(n.y+0.35); col=mix(col,low,0.025); } col=cloudComposite(worldRo,worldRd,col,hit); // Diffuse vapor and two twisting opal folds. Geometry lives in world space; // view-dependent thickness and pearl color supply the camera response. if(smoke>0.001) { float speed=length(velocityAt(tm))*flight/0.65; float lifetime=1.50/(1.0+0.13*speed); float strain=smoothstep(2.0,10.0,speed); float vapor=0.0,depthSum=0.0; vec3 pearl=vec3(0); vec3 previous,previousSpread; float previousAge,previousPulse; smokeSample(0.0,lifetime,previous,previousSpread,previousAge,previousPulse); for(int i=1;i<=64;i++) { float u=float(i)/64.0,age,pulse; vec3 next,spread; smokeSample(u,lifetime,next,spread,age,pulse); vec3 segment=next-previous; float len=max(length(segment),0.00001); vec3 tangent=segment/len,across=spread+previousSpread; across-=tangent*dot(across,tangent); if(dot(across,across)<0.0001) { vec3 ref=abs(tangent.y)<0.9?vec3(0,1,0):vec3(0,0,1); across=cross(tangent,ref); } across=normalize(across); vec3 norm=normalize(cross(tangent,across)); vec3 p=0.5*(previous+next); float grow=smoothstep(0.0,0.25,u); float width=(0.026+0.25*pow(u,0.75))*(1.0-0.24*strain); float thin=mix(width*0.90,width*0.26,grow); float along=max(0.065,len*0.85); vec2 mist=smokeParcel(worldRo,worldRd,p,tangent,across,norm, vec3(max(along,width*0.7),width*1.45,width*0.95),len,hit); // Older folds peel away from the diffuse core, rather than drawing two // perfectly parallel ropes. Each parcel's phase follows emission time. float emitted=TIME-age; float shed=smoothstep(0.10,0.85,u)*(0.08+0.28*curl); vec3 split=across*width*(0.65+0.45*sin(12.0*age+emitted)) +norm*shed*0.80; vec2 silk=smokeParcel(worldRo,worldRd,p+split, tangent,across,norm,vec3(along,width*0.65,thin),len,hit); vec2 lace=smokeParcel(worldRo,worldRd,p-split*0.65+norm*shed*0.6*sin(9.0*age+emitted*0.6), tangent,across,norm,vec3(along,width*0.46,thin*0.65),len,hit); float fade=(1.0-smoothstep(0.58,1.0,u))*exp(-u*0.45)*pulse; float dm=mist.x*0.85*fade,ds=silk.x*3.1*fade,dl=lace.x*2.6*fade; float facing=abs(dot(norm,-worldRd)); float pearlShift=0.5+0.5*sin(6.0*u-emitted*0.85+2.6*facing); vec3 opal=mix(vec3(0.68,0.89,0.97),vec3(0.94,0.76,0.95),pearlShift); opal=mix(vec3(0.92,0.94,0.96),opal,opalescence); vec3 glow=mix(opal,vec3(0.98,0.94,0.86),0.22*pow(1.0-facing,2.0)); float lighting=0.78+0.22*abs(dot(norm,normalize(vec3(-0.54,0.22,-0.84)))); vec3 haze=mix(vec3(0.79,0.83,0.87),opal*0.88,opalescence)*lighting; float density=dm+ds+dl; vapor+=density; depthSum+=dm*mist.y+ds*silk.y+dl*lace.y; pearl+=dm*haze+ds*glow+dl*mix(opal,vec3(0.76,0.80,0.99),0.25*opalescence); previous=next; previousSpread=spread; } if(vapor>0.00001) { float visibility=smokeTransmission(worldRo,worldRd,max(0.0,depthSum/vapor)); col=mix(col,pearl/vapor,0.82*(1.0-exp(-vapor*smoke*1.65/0.82))*visibility); } } // Output is display-referred; avoid an extra gamma lift that bleaches the paper. gl_FragColor=vec4(clamp(col,0.0,1.0),1.0); }
forking #uj2ujb2l — lineage will be recorded.
renders in YOUR browser. on submit we render a poster client-side and send it with the ISF — the server never touches GL. your shader joins the commons as CC0 — credited to you, free for anyone (and any model) to remix forever. that's the whole point. it's permanent: once it's public, CC0 has no take-backs. by submitting you attest you have the right to release it as CC0.