const SHADER = `precision highp float; uniform highp float iTime; uniform highp vec2 iResolution; uniform highp vec3 iMouse; // z = 0.0 if up, 1.0 if down (.xy will retain the last mouse position when was down) //uniform sampler2D iChannel0; precision highp float; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // Copyright (c) Timo Saarinen 2021 // You can use this Work in a Good and Cool Spirit. // Just don't copy-paste the whole thing, that's lame! // Greetings go to IQ and other Shadertoy inspirators. // Turbo loader, volumetric clouds, weird star, demoish effects. // One goal was to not use textures at all for 3D, but to do everything // procedurally. Tested with custom logo 2D textures, but works without // them. And the usual disclaimer: this was done in a hurry. // Lots of room for optimization et al. const float PI = 3.141592; // close enough float saturate(float x) { return clamp(x, 0.0, 1.0); } // 3D noise function (IQ) float noise3(vec3 p) { //p += vec3(iTime*0.07); // TEST vec3 ip=floor(p); p-=ip; vec3 s=vec3(7,157,113); vec4 h=vec4(0.,s.yz,s.y+s.z)+dot(ip,s); p=p*p*(3.-2.*p); h=mix(fract(sin(h)*43758.5),fract(sin(h+s.x)*43758.5),p.x); h.xy=mix(h.xz,h.yw,p.y); return mix(h.x,h.y,p.z); } // TODO: faster, better, noise! vec3 noise(in vec3 q) { return vec3(noise3(q)); //return vec3(noise3(q*0.7), noise3(q*0.997), noise3(q*0.77)); //return vec3(noise3(q.zxy*3.3 + q.xyz*0.997 + q.yxz*7.77)); } float iRaySphere(in vec3 ro, in vec3 rd, in vec3 sph, in float rad) { vec3 oc = ro - sph; float b = dot(oc, rd); float c = dot(oc, oc) - rad*rad; float t = b*b - c; return (t > 0.0) ? -b - sqrt(t) : t; } mat4 lookat(in vec3 o, in vec3 dir, in vec3 worldup) { vec3 right = normalize(cross(dir, worldup)); vec3 up = normalize(cross(right, dir)); mat4 m = mat4(vec4(right, 0.0), vec4(up, 0.0), vec4(dir, 0.0), vec4(o, 1.0)); return m; } // Star background // https://www.shadertoy.com/view/XsyGWV vec3 stars(in vec3 p) { vec3 c = vec3(0.); float res = iResolution.x*0.8; for (float i=0.;i<3.;i++) { vec3 q = fract(p*(.15*res))-0.5; vec3 id = floor(p*(.15*res)); vec2 rn = noise(id).xy; float c2 = 1.-smoothstep(0.,.6,length(q)); c2 *= step(rn.x,.0005+i*i*0.001); c += c2*(mix(vec3(1.0,0.49,0.1),vec3(0.75,0.9,1.),rn.y)*0.25+0.75); p *= 1.4; } return c*c*.7; } //------------------------------------------------------------------------ // Raymarching clouds+ (based on: https://www.shadertoy.com/view/XslGRr) //------------------------------------------------------------------------ const vec3 sundir = normalize( vec3(-1.0,0.0,-1.9) ); const vec3 sunpos2 = sundir; const float sunr = 0.3; const int kDiv = 1; // make bigger for more steps, higher quality float cloudnoise(in vec3 x) { return noise(x).x * 1.0; } float cloudDensity(in vec3 p, int oct) { vec3 q = p - vec3(0.0,0.1,1.0)*iTime; float g = 0.5 + 0.5*cloudnoise( q*0.3 ); float f; f = 0.50000*cloudnoise( q ); q = q*2.02; f += 0.25000*cloudnoise( q ); q = q*2.23; f += 0.12500*cloudnoise( q ); q = q*2.41; f += 0.06250*cloudnoise( q ); q = q*2.62; f += 0.03125*cloudnoise( q ); f = mix( f*0.1-0.75, f, g*g ) + 0.1; return 1.5*f - 0.5 - p.y; } vec3 background(in vec3 ro, in vec3 rd, in ivec2 px, in vec3 suncol, in float sundot, in float beat) { vec3 add = vec3(0.0); vec3 sunpos = ro + sunpos2; float sunt = iRaySphere(ro, rd, sunpos, sunr + 0.005*beat); vec3 halocol = suncol; float halo = pow(sundot, 32.0*5.0*sunr + 4.0*5.0*sunr*-beat); if( sunt > 0.0 ) { // add "sun" vec3 p2 = (ro + sunt*rd) - sunpos; vec3 hitn = normalize(p2); add.xyz += saturate(dot(vec3(0.0, 0.0, 1.0),hitn)) * vec3(0.2, 0.0, 0.5); vec3 n = normalize( vec3( hitn.x, 0.0, hitn.z ) ); float u = acos( dot( vec3(1.0, 0.0, 0.0), n ) )/PI; float v = acos( dot( vec3(0.0, 1.0, 0.0), hitn ) )/PI; float sunnoise = noise(vec3(u*777.0,v*997.0, 0.1*iTime)).x; vec3 sun0 = suncol * 0.5; vec3 sun1 = suncol * 1.5; add.xyz += mix(sun0, sun1, saturate(sin(50.0*u*v + iTime) + cos(0.07*v + 0.7*iTime))); add.xyz += 1.0*halo*halocol; // keep some "halo" inside } else { // sun halo + stars add.xyz += 10.0*stars(rd); add.xyz += 10.0*halo*halocol; } return add; } // TODO: the idea was to get some mountaintops peaking from clouds.. vec4 hitTerrain(in vec3 ro, in vec3 rd, float tmin, float tmax) { return vec4(0.0, 1.0, 0.0, 99999999.0); } #define NUM_STEPS 32 vec3 raymarch(in vec3 ro, in vec3 rd, in ivec2 px, float beat) { float tmin = 0.1; // just fixed bounds for now float tmax = 10.0; float sundot = clamp( dot(sundir,rd), 0.0, 1.0 ); vec3 suncol = vec3(1.0, 0.95, 0.840) * (1.0+saturate(0.5*-beat)); vec3 horizcol = vec3(0.32, 0.0, 1.0); vec3 glarecol = vec3(1.0, 1.0, 1.0); vec3 dircol = 0.6 * vec3(1.0, 0.8, 1.0); // terrain vec4 terra = hitTerrain(ro, rd, tmin, 100.0*tmax); // background (sun, stars) vec3 bgcol = background(ro, rd, px, suncol, sundot, beat); // raymarch loop vec4 sum = vec4(0.0); vec3 add = vec3(0.0); float dt = tmax / float(NUM_STEPS); float t = tmin + noise(vec3(px, iTime)).x * dt; // jitter the raymarch start position for(int i=0; i < NUM_STEPS; ++i) { vec3 pos = ro + t*rd; int oct = 5 - int(log2(1.0 + 0.5*t)); float density = cloudDensity(pos, oct); if( density > 0.01 ) { vec3 clouddencol = vec3(5.0, 0.3, 0.3); float shadowFeeler = cloudDensity(pos + 0.3*sundir, oct); float diffuse = clamp((density - shadowFeeler)/0.3, 0.0, 1.0); vec3 cloudcol = 0.5*suncol + vec3(0.0, 0.0, 1.25*abs(rd.y)); vec3 lin = suncol*diffuse + cloudcol; vec4 col = vec4(lin * mix(suncol, clouddencol, density), density); col.w = min(col.w*8.0*dt, 1.0); col.rgb *= col.a; sum += col*(1.0-sum.a); } t += dt; if( t > tmax || sum.a > 0.99 ) break; // far clip or full opacity? //if( t > terra.w ) { // TODO: hit the ground? } } // FIX: shows as overlay.. well, kinda works vec3 horiz = (pow(1.0-abs(rd.y + 0.00), 8.0)) * horizcol; horiz += (pow(1.0-abs(rd.y + 0.00), 32.0)) * vec3(5.0, 5.0, 0.0) * beat; return clamp( sum.xyz, 0.0, 1.0 ) + add + (1.0-sum.w)*bgcol + horiz*t/tmax; } //------------------------------------------------------------------------- vec3 render(in vec3 ro, in vec3 rd, in ivec2 px, float beat) { vec3 col = raymarch(ro, rd, px, beat); return col.xyz; // TODO: tone mapping } //------------------------------------------------------------------------- mat3 setCamera(in vec3 ro, in vec3 ta, float cr) // by IQ { vec3 cw = normalize(ta-ro); vec3 cp = vec3(sin(cr), cos(cr),0.0); vec3 cu = normalize( cross(cw,cp) ); vec3 cv = normalize( cross(cu,cw) ); return mat3( cu, cv, cw ); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { float beat = pow(0.75*(cos(iTime) * cos(iTime*0.97)) + 0.25*cos(iTime*7.77), 2.0); vec2 m = vec2(0.0, -0.15) + iMouse.xy; // NOTE: no / iResolution.xy; // camera by IQ (was nice) vec3 ro = 4.0*normalize(vec3(sin(3.0*m.x), 0.8*m.y, cos(3.0*m.x))) - vec3(0.0,0.1,0.0); vec3 ta = vec3(0.0, -1.5, 0.0); mat3 ca = setCamera( ro, ta, 0.07*cos(0.25*iTime) ); // render the 3D scene vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; vec3 rd = ca * normalize( vec3(p.xy,1.5)); vec3 c = render( ro, rd, ivec2(fragCoord-0.5), beat ); // vignetting float vigdist = length(p); float vignette = mix(1.5, 0.0, pow(0.25*vigdist, 0.5)); float grain = 1.0; // TODO: film grain? c *= vignette * grain; //c.rgb = vec3(pow(c.g, 0.25) * 0.5); // TEST: B&W! //c = 1.5*pow(c, vec3(0.5)); // TEST c = exp(c) * 0.1; // TEST fragColor = vec4(c, 1.0); } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< void main(void) { mainImage(gl_FragColor, gl_FragCoord.xy); //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // DEBUG } `;