Skip to content

Instantly share code, notes, and snippets.

@0b5vr
Created December 14, 2025 16:19
Show Gist options
  • Select an option

  • Save 0b5vr/a489c706b03e8f025c4b28cdd6970534 to your computer and use it in GitHub Desktop.

Select an option

Save 0b5vr/a489c706b03e8f025c4b28cdd6970534 to your computer and use it in GitHub Desktop.
20251130_hex grid tunnel
precision highp float;
#define lofi(i,j) (floor((i)/(j))*(j))
#define saturate(i) clamp(i,0.,1.)
// 200 is recommended, use 400 if your machine is a beef
const int ITER=400;
const float FOG_START=30.0;
const float FAR=40.0;
const vec3 FOG_COLOR=vec3(1,2,3);
const float PI=acos(-1.);
const float TAU=2.*PI;
const float SQRT3=sqrt(3.);
uniform vec2 resolution;
uniform float time;
uniform int frame;
uniform sampler2D backbuffer;
out vec4 outColor;
uvec3 seed;
uvec3 hash3u(uvec3 s){
s=s*1145141919u+1919810u;
s.x+=s.y*s.z;
s.y+=s.z*s.x;
s.z+=s.x*s.y;
s^=s>>16;
s.x+=s.y*s.z;
s.y+=s.z*s.x;
s.z+=s.x*s.y;
return s;
}
vec3 hash3f(vec3 s){
uvec3 r=hash3u(floatBitsToUint(s));
return vec3(r)/float(0xffffffffu);
}
vec3 random3(){
seed=hash3u(seed);
return vec3(seed)/float(0xffffffffu);
}
float random(){
return random3().x;
}
float easein(float x,float k){
x=saturate(x);
return (k+1.)*pow(x,k)-k*pow(x,k+1.);
}
float easeout(float x,float k){
return 1.-easein(1.-x,k);
}
vec2 cis(float t){
return vec2(cos(t),sin(t));
}
mat2 rotate2d(float t){
float c=cos(t),s=sin(t);
return mat2(c,s,-s,c);
}
mat3 orthbas(vec3 z){
z=normalize(z);
float isveryy=step(.999,abs(z.y));
vec3 up=vec3(0,1.-isveryy,isveryy);
vec3 x=normalize(cross(up,z));
return mat3(x,cross(z,x),z);
}
vec2 dohex(vec2 p){
mat2 MAT_SKEW=mat2(1,1./SQRT3,0,2./SQRT3);
mat2 INV_MAT_SKEW=mat2(1,-.5,0,SQRT3/2.);
vec2 pt=p*MAT_SKEW;
vec2 cell=floor(pt);
cell.y-=mod(cell.y+cell.x+2.,3.)-1.;
vec2 coord=pt-cell;
float istop=step(coord.x,coord.y);
cell+=vec2(1.-istop,istop);
return cell*INV_MAT_SKEW;
}
vec4 gridt(vec3 ro,vec3 rd){
vec3 cell=vec3(dohex(ro.xy+rd.xy*1E-2),0);
vec3 dice=hash3f(cell.xyy);
float zoffset=dice.x;
cell.z=lofi(ro.z+rd.z*1E-2-zoffset,1.)+.5+zoffset;
mat4 dir=mat4(
vec4(SQRT3*.5,.5,0,0),
vec4(0,1,0,0),
vec4(-SQRT3*.5,.5,0,0),
vec4(0,0,1,0)
);
vec4 rdd=vec4(rd,0)*dir;
vec4 src=-(vec4(ro-cell,0)*dir)/rdd;
vec4 dst=abs(vec2(SQRT3/2.,.5).xxxy/rdd);
vec4 bv=src+dst;
float b=min(min(bv.x,bv.y),min(bv.z,bv.w));
return vec4(cell,b);
}
vec4 isectmin(vec4 a,vec4 b){
return mix(a,b,step(b.w,a.w));
}
vec4 isectbox(vec3 ro,vec3 rd,vec3 s){
vec3 src=-ro/rd;
vec3 dst=abs(s/rd);
vec3 fv=src-dst;
vec3 bv=src+dst;
float f=max(max(fv.x,fv.y),fv.z);
float b=min(min(bv.x,bv.y),bv.z);
vec3 n=-sign(rd)*step(f,fv);
float isvalid=step(f,b)*step(0.,f);
return vec4(
n,
mix(FAR,f,isvalid)
);
}
vec4 isecthex(vec3 ro,vec3 rd,float r,float l){
mat4 dir=mat4(
vec4(SQRT3*.5,.5,0,0),
vec4(0,1,0,0),
vec4(-SQRT3*.5,.5,0,0),
vec4(0,0,1,0)
);
vec4 rdd=vec4(rd,0)*dir;
vec4 src=-(vec4(ro,0)*dir)/rdd;
vec4 dst=abs(vec2((SQRT3/2.)*r,l).xxxy/rdd);
vec4 bv=src+dst;
float b=min(min(bv.x,bv.y),min(bv.z,bv.w));
vec4 fv=src-dst;
float f=max(max(fv.x,fv.y),max(fv.z,fv.w));
vec4 mask=step(f,fv);
vec3 n=-(dir*mask).xyz*sign(dot(rdd,mask));
float isvalid=step(f,b)*step(0.,f);
return vec4(
n,
mix(FAR,f,isvalid)
);
}
vec3 sampleggx(vec3 n,float aa){
vec3 xi=random3();
float costheta=sqrt((1.-xi.y)/(1.+(aa-1.)*xi.y));
float sintheta=sqrt(1.-costheta*costheta);
return orthbas(n)*vec3(
sintheta*cis(TAU*xi.x),
costheta
);
}
void main() {
seed=uvec3(gl_FragCoord.xy,frame);
vec2 uv=gl_FragCoord.xy/resolution.xy;
vec2 p=uv*2.-1.;
p.x*=resolution.x/resolution.y;
vec3 co;
vec3 ro;
vec3 rd;
float rl;
vec3 col=vec3(0);
vec3 colrem=vec3(0);
float samples=0.;
for(int i=0;i<ITER;i++){
const float gridzmod=256.;
float mytime;
if(dot(colrem,vec3(1))<.4){
// reset
float warptime=time/10.-0.8; // 3.8 for the sake of direction
float warp1=easeout(6.*fract(warptime),6.);
float warp0=easeout(6.*fract(warptime)-.1,6.);
float warp=(warp1-warp0)/.1;
warp=warp/(warp+1.);
float prog=random();
colrem=mix(
vec3(1),
4.*(.5-.5*cos(TAU*saturate(2.*prog-vec3(0,.5,1.)))),
sqrt(warp)
);
mytime=time-.0166*prog;
float warpt=mix(warp0,warp1,prog)+floor(warptime);
mytime+=3.*warpt;
samples++;
// init camera
vec3 cx=vec3(1,0,0);
vec3 cy=vec3(0,1,0);
cx.yz*=rotate2d(1.2*warpt);
cy.yz*=rotate2d(1.2*warpt);
cx.zx*=rotate2d(-.11*mytime);
cy.zx*=rotate2d(-.11*mytime);
mat3 cb=mat3(cx,cy,cross(cx,cy));
co=cb*vec3(0,0,.6);
co.z-=mod(4.*mytime,gridzmod);
// init ray
ro=co;
vec2 pt=(p+2.*random3().xy/resolution.y)*rotate2d(.1*mytime+warpt);
rd=cb*normalize(vec3(pt,-1.8+warp*(-.4-.2*length(p)*length(p))));
rl=0.;
// dof
{
vec3 fp=ro+rd*5.;
vec3 dice=random3();
vec2 disc=sqrt(dice.x)*cis(TAU*dice.y);
ro+=0.01*vec3(disc,0)*cb;
rd=normalize(fp-ro);
}
}
// vars
vec4 isect=vec4(FAR);
float alpha=0.;
float f0=.1;
vec3 ecol=vec3(0);
// scroll
float scrdice=hash3f(dohex(ro.xy+rd.xy*1E-2).xyy).x;
float scr=24.*smoothstep(-.2,.2,sin(.1*mytime+TAU*scrdice));
ro.z-=scr;
// traverse
vec4 grid=gridt(ro,rd);
// roll dice
vec3 dice=hash3f(grid.xyy);
dice.z=floor(mod(grid.z,gridzmod)*dice.z*dice.z);
dice=hash3f(dice);
// isect emissive hexes
if(length(grid.xy)>1.&&dice.z<.1){
float showz=grid.z-co.z+mix(0.,40.,dice.x);
float size=easeout(showz/5.,5.)-easeout((showz-20.)/5.,3.);
vec4 isect2=isecthex(ro-grid.xyz,rd,size*.7,size*.2);
if(isect2.w<isect.w){
isect=isect2;
alpha=.001;
f0=.1;
ecol=4.*(.1+saturate(cos(PI*(fract(dice.z*100.)-vec3(.16,.5,.83)))));
}
}
// lines
if(length(grid.xy)>1.){
float mt=PI/3.*floor(mod(dice.x*18.,6.));
mat2 m=rotate2d(mt);
vec3 rpt=ro-grid.xyz;
rpt.xy*=m;
vec3 rdt=rd;
rdt.xy*=m;
vec4 isect2=isectmin(
isecthex(rpt-vec3(.72,.0,.0),rdt,.01,.49),
isecthex(rpt-vec3(-.72,.0,.0),rdt,.01,.49)
);
isect2.xy*=rotate2d(-mt);
if(isect2.w<isect.w){
isect=isect2;
alpha=1.;
f0=.0;
ecol=vec3(1.8,1.9,2);
}
}
// reflectors
if(length(grid.xy)>1.&&dice.z<.54){
float mt=PI/3.*floor(dice.x*3.);
mat2 m=rotate2d(mt);
vec3 rpt=ro-grid.xyz;
rpt.xy*=m;
vec3 rdt=rd;
rdt.xy*=m;
vec4 isect2=isectmin(
isectbox(rpt-vec3(0,0.8,0.),rdt,vec3(.5,0.02,.45)),
isectbox(rpt-vec3(0,-0.8,0.),rdt,vec3(.5,0.02,.45))
);
bool isface=abs(isect2.y)>.5;
isect2.xy*=rotate2d(-mt);
if(isect2.w<isect.w){
isect=isect2;
alpha=isface?exp(-3.-3.*dice.y):1.;
f0=isface?.5:.04;
ecol=vec3(0);
}
}
// revert the scroll
ro.z+=scr;
// if the ray misses, continue
if(isect.w==FAR){
ro+=grid.w*rd;
rl+=grid.w;
// if the ray goes way too far, grant fog instead
if(rl>=FAR){
col+=colrem*FOG_COLOR;
colrem*=0.;
}
continue;
}
// update ray
ro+=isect.w*rd;
rl+=isect.w;
// do the shading
vec3 n=isect.xyz;
// fog
float fog=smoothstep(FOG_START,FAR,rl);
col+=colrem*fog*FOG_COLOR;
colrem*=1.-fog;
// ggx
vec3 h=sampleggx(n,alpha*alpha);
float dotvh=dot(-rd,h);
rd=reflect(rd,h);
// fresnel
float f=mix(f0,1.,pow(saturate(1.-dotvh),5.));
// emissive
col+=colrem*(1.-f)*ecol;
// albedo
colrem*=f*step(0.,dot(rd,n)); // fresnel, visibility
}
// post processing
col/=samples; // sum -> average
col=pow(col/(col+1.),vec3(.4545)); // reinhard, oetf
// col*=1.-.2*length(p)*length(p); // vignette
col=smoothstep(vec3(0.0,-0.1,-0.3),vec3(1.0,1.0,1.1),col); // color grading
// temporal accumulation
vec4 prev=texture(backbuffer,uv);
outColor=mix(vec4(col,1),prev,.5*prev.w);
// debug
// outColor.xyz=cos(PI*(saturate(samples/10.)-vec3(.5,1.5,2.5)/3.)); // blue == enough samples, red == too few samples
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment