// Created by inigo quilez - iq/2020 // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. // Everybody has to implement an hexagonal grid. This it mine. // It does raycasting on it, efficiently (just a few muls per step) and robustly // (works in integers). Each cell is visited only once and in the right order. // Based on https://www.shadertoy.com/view/WtSBWK Check castRay() in line 92. // That, plus the fact the ambient occlusion is analytical means this shader should // run smoothly even on a crappy phone. It does on mine! #version 450 layout(set = 1, binding = 0) uniform sampler2D iChannel0; layout(set = 1, binding = 1) uniform sampler2D iChannel1; layout(set = 3, binding = 0) uniform UniformBlock { float time; vec2 resolution; } Uniforms; //layout(location = 0) in vec2 fragCoord; layout(location = 0) out vec4 fragColor; // make this bigger if you have a storng PC #define AA 2 // ----------------------------------------- // mod3 - not as trivial as you first though // ----------------------------------------- int mod3( int n ) { return (n<0) ? 2-((2-n)%3) : n%3; // Some methods of computing mod3: // PC-WebGL Native-OpenGL Android WebGL // // -------- ------------- ------- // 1. return (n<0) ? 2-((2-n)%3) : n%3; // Ok Ok Ok // 2. return int((uint(n)+0x80000001U)%3u); // Ok Ok Broken // 3. n %= 3; return (n<0)?n+3:n; // Ok Broken Ok // 4. n %= 3; n+=((n>>31)&3); return n; // Ok Broken Ok // 5. return ((n%3)+3)%3; // Ok Broken Ok // 6. return int[](1,2,0,1,2)[n%3+2]; // Ok Broken Ok } // -------------------------------------- // hash by Hugo Elias) // -------------------------------------- int hash( int n ) { n=(n<<13)^n; return n*(n*n*15731+789221)+1376312589; } // -------------------------------------- // basic hexagon grid functions // -------------------------------------- ivec2 hexagonID( vec2 p ) { const float k3 = 1.732050807; vec2 q = vec2( p.x, p.y*k3*0.5 + p.x*0.5 ); ivec2 pi = ivec2(floor(q)); vec2 pf = fract(q); int v = mod3(pi.x+pi.y); int ca = (v<1)?0:1; int cb = (v<2)?0:1; ivec2 ma = (pf.x>pf.y)?ivec2(0,1):ivec2(1,0); ivec2 id = pi + ca - cb*ma; return ivec2( id.x, id.y - (id.x+id.y)/3 ); } vec2 hexagonCenFromID( in ivec2 id ) { const float k3 = 1.732050807; return vec2(float(id.x),float(id.y)*k3); } // --------------------------------------------------------------------- // the height function. yes, i know reading from a video texture is cool // --------------------------------------------------------------------- const float kMaxH = 6.0; float map( vec2 p, in float time ) { p *= 0.5; float f = 0.5+0.5*sin(0.53*p.x+0.5*time+1.0*sin(p.y*0.24))* sin(0.13*p.y+time); f*= 0.75+0.25*sin(1.7*p.x+1.32*time)*sin(1.3*p.y+time*2.1); return kMaxH*(0.005+0.995*f); } // -------------------------------------------------- // raycast. this function is the point of this shader // -------------------------------------------------- vec4 castRay( in vec3 ro, in vec3 rd, in float time, out ivec2 outPrismID, out int outFaceID ) { ivec2 hid = hexagonID(ro.xz); vec4 res = vec4( -1.0, 0.0, 0.0, 0.0 ); const float k3 = 0.866025; const vec2 n1 = vec2( 1.0,0.0); const vec2 n2 = vec2( 0.5,k3); const vec2 n3 = vec2(-0.5,k3); float d1 = 1.0/dot(rd.xz,n1); float d2 = 1.0/dot(rd.xz,n2); float d3 = 1.0/dot(rd.xz,n3); float d4 = 1.0/rd.y; float s1 = (d1<0.0)?-1.0:1.0; float s2 = (d2<0.0)?-1.0:1.0; float s3 = (d3<0.0)?-1.0:1.0; float s4 = (d4<0.0)?-1.0:1.0; ivec2 i1 = ivec2( 2,0); if(d1<0.0) i1=-i1; ivec2 i2 = ivec2( 1,1); if(d2<0.0) i2=-i2; ivec2 i3 = ivec2(-1,1); if(d3<0.0) i3=-i3; // traverse hexagon grid (in 2D) bool found = false; vec2 t1, t2, t3, t4; for( int i=0; i<100; i++ ) { // fetch height for this hexagon vec2 ce = hexagonCenFromID( hid ); float he = 0.5*map(ce, time); // compute ray-hexaprism intersection vec3 oc = ro - vec3(ce.x,he,ce.y); t1 = (vec2(-s1,s1)-dot(oc.xz,n1))*d1; t2 = (vec2(-s2,s2)-dot(oc.xz,n2))*d2; t3 = (vec2(-s3,s3)-dot(oc.xz,n3))*d3; t4 = (vec2(-s4,s4)*he-oc.y)*d4; float tN = max(max(t1.x,t2.x),max(t3.x,t4.x)); float tF = min(min(t1.y,t2.y),min(t3.y,t4.y)); if( tN0.0 ) { found = true; break; } // move to next hexagon if( t1.yres.x ) {res=vec4(t2.x,s2*vec3(n2.x,0,n2.y)); outFaceID=(d2<0.0)?-2: 2;} if( t3.x>res.x ) {res=vec4(t3.x,s3*vec3(n3.x,0,n3.y)); outFaceID=(d3<0.0)?-3: 3;} if( t4.x>res.x ) {res=vec4(t4.x,s4*vec3( 0.0,1,0)); outFaceID=(d4<0.0)? 4:-4;} outPrismID = hid; } return res; } // ------------------------------------------------------------------------- // same as above, but simpler sinec we don't need the normal and primtive id // -------------------------------------------------------------------------- float castShadowRay( in vec3 ro, in vec3 rd, in float time ) { float res = 1.0; ivec2 hid = hexagonID(ro.xz); const float k3 = 0.866025; const vec2 n1 = vec2( 1.0,0.0); const vec2 n2 = vec2( 0.5,k3); const vec2 n3 = vec2(-0.5,k3); float d1 = 1.0/dot(rd.xz,n1); float d2 = 1.0/dot(rd.xz,n2); float d3 = 1.0/dot(rd.xz,n3); float d4 = 1.0/rd.y; float s1 = (d1<0.0)?-1.0:1.0; float s2 = (d2<0.0)?-1.0:1.0; float s3 = (d3<0.0)?-1.0:1.0; float s4 = (d4<0.0)?-1.0:1.0; ivec2 i1 = ivec2( 2,0); if(d1<0.0) i1=-i1; ivec2 i2 = ivec2( 1,1); if(d2<0.0) i2=-i2; ivec2 i3 = ivec2(-1,1); if(d3<0.0) i3=-i3; vec2 c1 = (vec2(-s1,s1)-dot(ro.xz,n1))*d1; vec2 c2 = (vec2(-s2,s2)-dot(ro.xz,n2))*d2; vec2 c3 = (vec2(-s3,s3)-dot(ro.xz,n3))*d3; // traverse regular grid (2D) for( int i=0; i<8; i++ ) { vec2 ce = hexagonCenFromID( hid ); float he = 0.5*map(ce, time); vec2 t1 = c1 + dot(ce,n1)*d1; vec2 t2 = c2 + dot(ce,n2)*d2; vec2 t3 = c3 + dot(ce,n3)*d3; vec2 t4 = (vec2(1.0-s4,1.0+s4)*he-ro.y)*d4; float tN = max(max(t1.x,t2.x),max(t3.x,t4.x)); float tF = min(min(t1.y,t2.y),min(t3.y,t4.y)); if( tN < tF && tF > 0.0) { res = 0.0; break; } if( t1.y0.0 ) { // data at intersection point vec3 pos = ro + rd*t; vec3 nor = -tnor.yzw; vec2 ce = hexagonCenFromID(prismID); float he = map(ce,time); int id = prismID.x*131 + prismID.y*57; // uvs vec2 uv = (faceID==4) ? (pos.xz-ce)*0.15 : vec2(atan(pos.x-ce.x,pos.z-ce.y)/3.14156, (pos.y-he)/4.0 ); uv += ce; // material color vec3 mate = vec3(1.0); id = hash(id); mate *= 0.1+0.9*float((id>>13)&3)/3.0; id = hash(id); mate = ( ((id>>8)&15)==0 ) ? vec3(0.7,0.0,0.0) : mate; vec3 tex = vec3(0.15,0.09,0.07)+0.75*pow(texture(iChannel0,uv.yx).xyz,vec3(1.0,0.95,0.9)); mate *= tex; // lighting float occ = calcOcclusion( pos, nor, time, prismID, faceID ); // diffuse col = mate*pow(vec3(occ),vec3(0.95,1.05,1.1)); // specular float ks = tex.x*2.0; vec3 ref = reflect(rd,nor); col *= 0.85; float fre = clamp(1.0+dot(nor,rd),0.0,1.0); col += vec3(1.1)*ks* smoothstep(0.0,0.15,ref.y)* (0.04 + 0.96*pow(fre,5.0))* castShadowRay( pos+nor*0.001, ref, time ); // fog col = mix(col,vec3(1.0), 1.0-exp2(-0.00005*t*t) ); } return col; } //----------------------------------------------- // main = animate + render + color grade //----------------------------------------------- void main() { vec2 fragCoord = gl_FragCoord.xy; fragCoord.y = Uniforms.resolution.y - gl_FragCoord.y; // init random seed ivec2 q = ivec2(fragCoord); // sample pixel and time vec3 tot = vec3(0.0); for( int m=0; m1 float d = 0.5+0.5*sin(fragCoord.x*147.0)*sin(fragCoord.y*131.0); float time = Uniforms.time - 0.5*(1.0/24.0)*(float(m*AA+n)+d)/float(AA*AA); #else float time = Uniforms.time; #endif // camera float cr = -0.1; float an = 3.0*time; vec3 ro = vec3(0.1,13.0,1.0-an); vec3 ta = vec3(0.0,12.0,0.0-an); // build camera matrix vec3 ww = normalize( ta - ro); vec3 uu = normalize(cross( ww,vec3(sin(cr),cos(cr),0.0) )); vec3 vv = normalize(cross(uu,ww)); // distort p *= 0.9+0.1*(p.x*p.x*0.4 + p.y*p.y); // buid ray vec3 rd = normalize( p.x*uu + p.y*vv + 2.0*ww ); // dof #if AA>1 vec3 fp = ro + rd*17.0; vec2 ra = texelFetch(iChannel1,(q+ivec2(13*m,31*n))&1023,0).xy; ro.xy += 0.3*sqrt(ra.x)*vec2(cos(6.2831*ra.y),sin(6.2831*ra.y)); rd = normalize( fp - ro ); #endif // render vec3 col = render( ro, rd, time ); // accumulate for AA tot += col; } tot /= float(AA*AA); // hdr->ldr tonemap tot = tot*1.6/(1.0+tot); tot = tot*tot*(3.0-2.0*tot); // gamma tot = pow( clamp(tot,0.0,1.0), vec3(0.45) ); // color grade vec2 p = fragCoord/Uniforms.resolution.xy; tot.xyz += (p.xyy-0.5)*0.1; // vignetting tot *= 0.5 + 0.5*pow( 16.0*p.x*p.y*(1.0-p.x)*(1.0-p.y), 0.1 ); // output fragColor = vec4( tot, 1.0 ); }