diff --git a/examples/shadertoy_blink.py b/examples/shadertoy_blink.py index b71119f9..dac1cf5a 100644 --- a/examples/shadertoy_blink.py +++ b/examples/shadertoy_blink.py @@ -26,7 +26,7 @@ fn shader_main(frag_coord: vec2) -> vec4 { - let uv = (frag_coord-i_resolution*0.5)/i_resolution.y; + let uv = (frag_coord-i_resolution.xy*0.5)/i_resolution.y; let col = render(uv); diff --git a/examples/shadertoy_circuits.py b/examples/shadertoy_circuits.py index 4dfd60dd..3a383371 100644 --- a/examples/shadertoy_circuits.py +++ b/examples/shadertoy_circuits.py @@ -42,7 +42,7 @@ } fn shader_main(frag_coord: vec2) -> vec4 { - var uv = frag_coord / i_resolution - 0.5; + var uv = frag_coord / i_resolution.xy - 0.5; uv.x*=i_resolution.x/i_resolution.y; var aa = 6.0; diff --git a/examples/shadertoy_flyby.py b/examples/shadertoy_flyby.py index 29e01e9e..41fd39b5 100644 --- a/examples/shadertoy_flyby.py +++ b/examples/shadertoy_flyby.py @@ -162,7 +162,7 @@ } fn shader_main(frag_coord : vec2) -> vec4 { - var uv = frag_coord / i_resolution; + var uv = frag_coord / i_resolution.xy; uv = uv - 0.5; uv /= vec2(i_resolution.y / i_resolution.x, 1.0); diff --git a/examples/shadertoy_glsl_flame.py b/examples/shadertoy_glsl_flame.py new file mode 100644 index 00000000..9e02a9c4 --- /dev/null +++ b/examples/shadertoy_glsl_flame.py @@ -0,0 +1,81 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/MdX3zr + +// Created by anatole duprat - XT95/2013 +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +float noise(vec3 p) //Thx to Las^Mercury +{ + vec3 i = floor(p); + vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.); + vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5; + a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x); + a.xy = mix(a.xz, a.yw, f.y); + return mix(a.x, a.y, f.z); +} + +float sphere(vec3 p, vec4 spr) +{ + return length(spr.xyz-p) - spr.w; +} + +float flame(vec3 p) +{ + float d = sphere(p*vec3(1.,.5,1.), vec4(.0,-1.,.0,1.)); + return d + (noise(p+vec3(.0,iTime*2.,.0)) + noise(p*3.)*.5)*.25*(p.y) ; +} + +float scene(vec3 p) +{ + return min(100.-length(p) , abs(flame(p)) ); +} + +vec4 raymarch(vec3 org, vec3 dir) +{ + float d = 0.0, glow = 0.0, eps = 0.02; + vec3 p = org; + bool glowed = false; + + for(int i=0; i<64; i++) + { + d = scene(p) + eps; + p += d * dir; + if( d>eps ) + { + if(flame(p) < .0) + glowed=true; + if(glowed) + glow = float(i)/64.; + } + } + return vec4(p,glow); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 v = -1.0 + 2.0 * fragCoord.xy / iResolution.xy; + v.x *= iResolution.x/iResolution.y; + + vec3 org = vec3(0., -2., 4.); + vec3 dir = normalize(vec3(v.x*1.6, -v.y, -1.5)); + + vec4 p = raymarch(org, dir); + float glow = p.w; + + vec4 col = mix(vec4(1.,.5,.1,1.), vec4(0.1,.5,1.,1.), p.y*.02+.4); + + fragColor = mix(vec4(0.), col, pow(glow*2.,4.)); + //fragColor = mix(vec4(1.), mix(vec4(1.,.5,.1,1.),vec4(0.1,.5,1.,1.),p.y*.02+.4), pow(glow*2.,4.)); + +} + + + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_fuji.py b/examples/shadertoy_glsl_fuji.py new file mode 100644 index 00000000..8cf3c629 --- /dev/null +++ b/examples/shadertoy_glsl_fuji.py @@ -0,0 +1,169 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/Wt33Wf + +float sun(vec2 uv, float battery) +{ + float val = smoothstep(0.3, 0.29, length(uv)); + float bloom = smoothstep(0.7, 0.0, length(uv)); + float cut = 3.0 * sin((uv.y + iTime * 0.2 * (battery + 0.02)) * 100.0) + + clamp(uv.y * 14.0 + 1.0, -6.0, 6.0); + cut = clamp(cut, 0.0, 1.0); + return clamp(val * cut, 0.0, 1.0) + bloom * 0.6; +} + +float grid(vec2 uv, float battery) +{ + vec2 size = vec2(uv.y, uv.y * uv.y * 0.2) * 0.01; + uv += vec2(0.0, iTime * 4.0 * (battery + 0.05)); + uv = abs(fract(uv) - 0.5); + vec2 lines = smoothstep(size, vec2(0.0), uv); + lines += smoothstep(size * 5.0, vec2(0.0), uv) * 0.4 * battery; + return clamp(lines.x + lines.y, 0.0, 3.0); +} + +float dot2(in vec2 v ) { return dot(v,v); } + +float sdTrapezoid( in vec2 p, in float r1, float r2, float he ) +{ + vec2 k1 = vec2(r2,he); + vec2 k2 = vec2(r2-r1,2.0*he); + p.x = abs(p.x); + vec2 ca = vec2(p.x-min(p.x,(p.y<0.0)?r1:r2), abs(p.y)-he); + vec2 cb = p - k1 + k2*clamp( dot(k1-p,k2)/dot2(k2), 0.0, 1.0 ); + float s = (cb.x<0.0 && ca.y<0.0) ? -1.0 : 1.0; + return s*sqrt( min(dot2(ca),dot2(cb)) ); +} + +float sdLine( in vec2 p, in vec2 a, in vec2 b ) +{ + vec2 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ); +} + +float sdBox( in vec2 p, in vec2 b ) +{ + vec2 d = abs(p)-b; + return length(max(d,vec2(0))) + min(max(d.x,d.y),0.0); +} + +float opSmoothUnion(float d1, float d2, float k){ + float h = clamp(0.5 + 0.5 * (d2 - d1) /k,0.0,1.0); + return mix(d2, d1 , h) - k * h * ( 1.0 - h); +} + +float sdCloud(in vec2 p, in vec2 a1, in vec2 b1, in vec2 a2, in vec2 b2, float w) +{ + //float lineVal1 = smoothstep(w - 0.0001, w, sdLine(p, a1, b1)); + float lineVal1 = sdLine(p, a1, b1); + float lineVal2 = sdLine(p, a2, b2); + vec2 ww = vec2(w*1.5, 0.0); + vec2 left = max(a1 + ww, a2 + ww); + vec2 right = min(b1 - ww, b2 - ww); + vec2 boxCenter = (left + right) * 0.5; + //float boxW = right.x - left.x; + float boxH = abs(a2.y - a1.y) * 0.5; + //float boxVal = sdBox(p - boxCenter, vec2(boxW, boxH)) + w; + float boxVal = sdBox(p - boxCenter, vec2(0.04, boxH)) + w; + + float uniVal1 = opSmoothUnion(lineVal1, boxVal, 0.05); + float uniVal2 = opSmoothUnion(lineVal2, boxVal, 0.05); + + return min(uniVal1, uniVal2); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = (2.0 * fragCoord.xy - iResolution.xy)/iResolution.y; + float battery = 1.0; + //if (iMouse.x > 1.0 && iMouse.y > 1.0) battery = iMouse.y / iResolution.y; + //else battery = 0.8; + + //if (abs(uv.x) < (9.0 / 16.0)) + { + // Grid + float fog = smoothstep(0.1, -0.02, abs(uv.y + 0.2)); + vec3 col = vec3(0.0, 0.1, 0.2); + if (uv.y < -0.2) + { + uv.y = 3.0 / (abs(uv.y + 0.2) + 0.05); + uv.x *= uv.y * 1.0; + float gridVal = grid(uv, battery); + col = mix(col, vec3(1.0, 0.5, 1.0), gridVal); + } + else + { + float fujiD = min(uv.y * 4.5 - 0.5, 1.0); + uv.y -= battery * 1.1 - 0.51; + + vec2 sunUV = uv; + vec2 fujiUV = uv; + + // Sun + sunUV += vec2(0.75, 0.2); + //uv.y -= 1.1 - 0.51; + col = vec3(1.0, 0.2, 1.0); + float sunVal = sun(sunUV, battery); + + col = mix(col, vec3(1.0, 0.4, 0.1), sunUV.y * 2.0 + 0.2); + col = mix(vec3(0.0, 0.0, 0.0), col, sunVal); + + // fuji + float fujiVal = sdTrapezoid( uv + vec2(-0.75+sunUV.y * 0.0, 0.5), 1.75 + pow(uv.y * uv.y, 2.1), 0.2, 0.5); + float waveVal = uv.y + sin(uv.x * 20.0 + iTime * 2.0) * 0.05 + 0.2; + float wave_width = smoothstep(0.0,0.01,(waveVal)); + + // fuji color + col = mix( col, mix(vec3(0.0, 0.0, 0.25), vec3(1.0, 0.0, 0.5), fujiD), step(fujiVal, 0.0)); + // fuji top snow + col = mix( col, vec3(1.0, 0.5, 1.0), wave_width * step(fujiVal, 0.0)); + // fuji outline + col = mix( col, vec3(1.0, 0.5, 1.0), 1.0-smoothstep(0.0,0.01,abs(fujiVal)) ); + //col = mix( col, vec3(1.0, 1.0, 1.0), 1.0-smoothstep(0.03,0.04,abs(fujiVal)) ); + //col = vec3(1.0, 1.0, 1.0) *(1.0-smoothstep(0.03,0.04,abs(fujiVal))); + + // horizon color + col += mix( col, mix(vec3(1.0, 0.12, 0.8), vec3(0.0, 0.0, 0.2), clamp(uv.y * 3.5 + 3.0, 0.0, 1.0)), step(0.0, fujiVal) ); + + // cloud + vec2 cloudUV = uv; + cloudUV.x = mod(cloudUV.x + iTime * 0.1, 4.0) - 2.0; + float cloudTime = iTime * 0.5; + float cloudY = -0.5; + float cloudVal1 = sdCloud(cloudUV, + vec2(0.1 + sin(cloudTime + 140.5)*0.1,cloudY), + vec2(1.05 + cos(cloudTime * 0.9 - 36.56) * 0.1, cloudY), + vec2(0.2 + cos(cloudTime * 0.867 + 387.165) * 0.1,0.25+cloudY), + vec2(0.5 + cos(cloudTime * 0.9675 - 15.162) * 0.09, 0.25+cloudY), 0.075); + cloudY = -0.6; + float cloudVal2 = sdCloud(cloudUV, + vec2(-0.9 + cos(cloudTime * 1.02 + 541.75) * 0.1,cloudY), + vec2(-0.5 + sin(cloudTime * 0.9 - 316.56) * 0.1, cloudY), + vec2(-1.5 + cos(cloudTime * 0.867 + 37.165) * 0.1,0.25+cloudY), + vec2(-0.6 + sin(cloudTime * 0.9675 + 665.162) * 0.09, 0.25+cloudY), 0.075); + + float cloudVal = min(cloudVal1, cloudVal2); + + //col = mix(col, vec3(1.0,1.0,0.0), smoothstep(0.0751, 0.075, cloudVal)); + col = mix(col, vec3(0.0, 0.0, 0.2), 1.0 - smoothstep(0.075 - 0.0001, 0.075, cloudVal)); + col += vec3(1.0, 1.0, 1.0)*(1.0 - smoothstep(0.0,0.01,abs(cloudVal - 0.075))); + } + + col += fog * fog * fog; + col = mix(vec3(col.r, col.r, col.r) * 0.5, col, battery * 0.7); + + fragColor = vec4(col,1.0); + } + //else fragColor = vec4(0.0); + + +} + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_inercia.py b/examples/shadertoy_glsl_inercia.py new file mode 100644 index 00000000..0ecef49e --- /dev/null +++ b/examples/shadertoy_glsl_inercia.py @@ -0,0 +1,490 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/cs2GWD + +#define lofi(i,j) (floor((i)/(j))*(j)) +#define lofir(i,j) (round((i)/(j))*(j)) + +const float PI=3.1415926; +const float TAU=PI*2.; + +mat2 r2d(float t){ + float c=cos(t),s=sin(t); + return mat2(c,s,-s,c); +} + +mat3 orthbas(vec3 z){ + z=normalize(z); + vec3 up=abs(z.y)>.999?vec3(0,0,1):vec3(0,1,0); + vec3 x=normalize(cross(up,z)); + return mat3(x,cross(z,x),z); +} + +uvec3 pcg3d(uvec3 s){ + s=s*1145141919u+1919810u; + s+=s.yzx*s.zxy; + s^=s>>16; + s+=s.yzx*s.zxy; + return s; +} + +vec3 pcg3df(vec3 s){ + uvec3 r=pcg3d(floatBitsToUint(s)); + return vec3(r)/float(0xffffffffu); +} + +struct Grid{ + vec3 s; + vec3 c; + vec3 h; + int i; + float d; +}; + +Grid dogrid(vec3 ro,vec3 rd){ + Grid r; + r.s=vec3(2,2,100); + for(int i=0;i<3;i++){ + r.c=(floor(ro/r.s)+.5)*r.s; + r.h=pcg3df(r.c); + r.i=i; + + if(r.h.x<.4){ + break; + }else if(i==0){ + r.s=vec3(2,1,100); + }else if(i==1){ + r.s=vec3(1,1,100); + } + } + + vec3 src=-(ro-r.c)/rd; + vec3 dst=abs(.501*r.s/rd); + vec3 bv=src+dst; + float b=min(min(bv.x,bv.y),bv.z); + r.d=b; + + return r; +} + +float sdbox(vec3 p,vec3 s){ + vec3 d=abs(p)-s; + return length(max(d,0.))+min(0.,max(max(d.x,d.y),d.z)); +} + +float sdbox(vec2 p,vec2 s){ + vec2 d=abs(p)-s; + return length(max(d,0.))+min(0.,max(d.x,d.y)); +} + +vec4 map(vec3 p,Grid grid){ + p-=grid.c; + p.z+=.4*sin(2.*iTime+1.*fract(grid.h.z*28.)+.3*(grid.c.x+grid.c.y)); + + vec3 psize=grid.s/2.; + psize.z=1.; + psize-=.02; + float d=sdbox(p+vec3(0,0,1),psize)-.02; + + float pcol=1.; + + vec3 pt=p; + + if(grid.i==0){//2x2 + if(grid.h.y<.3){//speaker + vec3 c=vec3(0); + pt.xy*=r2d(PI/4.); + c.xy=lofir(pt.xy,.1); + pt=pt-c; + pt.xy*=r2d(-PI/4.); + + float r=.02*smoothstep(.9,.7,abs(p.x))*smoothstep(.9,.7,abs(p.y)); + float hole=length(pt.xy)-r; + d=max(d,-hole); + }else if(grid.h.y<.5){//eq + vec3 c=vec3(0); + c.x=clamp(lofir(pt.x,.2),-.6,.6); + pt-=c; + float hole=sdbox(pt.xy,vec2(0.,.7))-.03; + d=max(d,-hole); + + pt.y-=.5-smoothstep(-.5,.5,sin(iTime+c.x+grid.h.z*100.)); + float d2=sdbox(pt,vec3(.02,.07,.07))-.03; + + if(d250.){break;} + } + + March r; + r.isect=isect; + r.rp=rp; + r.rl=rl; + r.grid=grid; + + return r; +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = vec2(fragCoord.x / iResolution.x, fragCoord.y / iResolution.y); + vec2 p=uv*2.-1.; + p.x*=iResolution.x/iResolution.y; + + vec3 col=vec3(0); + + float canim=smoothstep(-.2,.2,sin(iTime)); + vec3 co=mix(vec3(-6,-8,-40),vec3(0,-2,-40),canim); + vec3 ct=vec3(0,0,-50); + float cr=mix(.5,.0,canim); + co.xy+=iTime; + ct.xy+=iTime; + mat3 cb=orthbas(co-ct); + vec3 ro=co+cb*vec3(4.*p*r2d(cr),0); + vec3 rd=cb*normalize(vec3(0,0,-2)); + + March march=domarch(ro,rd,100); + + if(march.isect.x<1E-2){ + vec3 basecol=vec3(.5); + vec3 speccol=vec3(.2); + float specpow=30.; + float ndelta=1E-4; + + float mtl=march.isect.y; + float mtlp=march.isect.z; + if(mtl==0.){ + mtlp=mix(mtlp,1.-mtlp,step(fract(march.grid.h.z*66.),.1)); + vec3 c=.9+.0*sin(.1*(march.grid.c.x+march.grid.c.y)+march.grid.h.z+vec3(0,2,3)); + basecol=mix(vec3(.04),c,mtlp); + }else if(mtl==1.){ + basecol=vec3(0); + speccol=vec3(.5); + specpow=60.; + + vec2 size=vec2(.05,.2); + vec2 pp=(march.rp-march.grid.c).xy; + vec2 c=lofi(pp.xy,size)+size/2.; + vec2 cc=pp-c; + vec3 led=vec3(1); + led*=exp(-60.*sdbox(cc,vec2(0.,.08))); + led*=c.x>.5?vec3(5,1,2):vec3(1,5,2); + // float lv=texture(iChannel0,vec2(march.grid.h.z,0)).x*1.; + col+=led*step(c.x,-.8); + basecol=.04*led; + }else if(mtl==2.){//led + basecol=vec3(0); + speccol=vec3(1.); + specpow=100.; + + col+=mtlp*vec3(2,.5,.5); + }else if(mtl==3.){//metal + basecol=vec3(.2); + speccol=vec3(1.8); + specpow=100.; + ndelta=3E-2; + }else if(mtl==4.){//kaoss + basecol=vec3(0); + speccol=vec3(.5); + specpow=60.; + + vec2 size=vec2(.1); + vec2 pp=(march.rp-march.grid.c).xy; + vec2 c=lofi(pp.xy,size)+size/2.; + vec2 cc=pp-c; + vec3 led=vec3(1); + led*=exp(-60.*sdbox(cc,vec2(0.,.0))); + led*=vec3(2,1,2); + float plasma=sin(length(c)*10.-10.*iTime+march.grid.h.z*.7); + plasma+=sin(c.y*10.-7.*iTime); + led*=.5+.5*sin(plasma); + col+=2.*led; + basecol=.04*led; + }else if(mtl==5.){//808 + basecol=vec3(.9,mtlp,.02); + } + + vec3 n=nmap(march.rp,march.grid,ndelta); + vec3 v=-rd; + + { + vec3 l=normalize(vec3(1,3,5)); + vec3 h=normalize(l+v); + float dotnl=max(0.,dot(n,l)); + float dotnh=max(0.,dot(n,h)); + float shadow=step(1E-1,domarch(march.rp,l,30).isect.x); + vec3 diff=basecol/PI; + vec3 spec=speccol*pow(dotnh,specpow); + col+=vec3(.5,.6,.7)*shadow*dotnl*(diff+spec); + } + { + vec3 l=normalize(vec3(-1,-1,5)); + vec3 h=normalize(l+v); + float dotnl=max(0.,dot(n,l)); + float dotnh=max(0.,dot(n,h)); + float shadow=step(1E-1,domarch(march.rp,l,30).isect.x); + vec3 diff=basecol/PI; + vec3 spec=speccol*pow(dotnh,specpow); + col+=shadow*dotnl*(diff+spec); + } + } + + col=pow(col,vec3(.4545)); + col=smoothstep(vec3(0,-.1,-.2),vec3(1,1.1,1.2),col); + fragColor = vec4(col,0); +} + + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_mouse_event.py b/examples/shadertoy_glsl_mouse_event.py new file mode 100644 index 00000000..6a951c18 --- /dev/null +++ b/examples/shadertoy_glsl_mouse_event.py @@ -0,0 +1,50 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/Mss3zH + +// Shows how to use the mouse input (only left button supported): +// +// mouse.xy = mouse position during last button down +// abs(mouse.zw) = mouse position during last button click +// sign(mouze.z) = button is down +// sign(mouze.w) = button is clicked + + +float distanceToSegment( vec2 a, vec2 b, vec2 p ) +{ + vec2 pa = p - a, ba = b - a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 p = fragCoord / iResolution.x; + vec2 cen = 0.5*iResolution.xy/iResolution.x; + vec4 m = iMouse / iResolution.x; + + vec3 col = vec3(0.0); + + if( m.z>0.0 ) // button is down + { + float d = distanceToSegment( m.xy, abs(m.zw), p ); + col = mix( col, vec3(1.0,1.0,0.0), 1.0-smoothstep(.004,0.008, d) ); + } + if( m.w>0.0 ) // button click + { + col = mix( col, vec3(1.0,1.0,1.0), 1.0-smoothstep(0.1,0.105, length(p-cen)) ); + } + + col = mix( col, vec3(1.0,0.0,0.0), 1.0-smoothstep(0.03,0.035, length(p- m.xy )) ); + col = mix( col, vec3(0.0,0.0,1.0), 1.0-smoothstep(0.03,0.035, length(p-abs(m.zw))) ); + + fragColor = vec4( col, 1.0 ); +} + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_sdf.py b/examples/shadertoy_glsl_sdf.py new file mode 100644 index 00000000..10b5b18d --- /dev/null +++ b/examples/shadertoy_glsl_sdf.py @@ -0,0 +1,657 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/Xds3zN + +#if HW_PERFORMANCE==0 +#define AA 1 +#else +#define AA 2 // make this 2 or 3 for antialiasing +#endif + +//------------------------------------------------------------------ +float dot2( in vec2 v ) { return dot(v,v); } +float dot2( in vec3 v ) { return dot(v,v); } +float ndot( in vec2 a, in vec2 b ) { return a.x*b.x - a.y*b.y; } + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( vec3 p, vec3 b ) +{ + vec3 d = abs(p) - b; + return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); +} + +float sdBoxFrame( vec3 p, vec3 b, float e ) +{ + p = abs(p )-b; + vec3 q = abs(p+e)-e; + + return min(min( + length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0), + length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)), + length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0)); +} +float sdEllipsoid( in vec3 p, in vec3 r ) // approximated +{ + float k0 = length(p/r); + float k1 = length(p/(r*r)); + return k0*(k0-1.0)/k1; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb) +{ + p.x = abs(p.x); + float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy); + return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); + + const vec3 k = vec3(-0.8660254, 0.5, 0.57735); + p = abs(p); + p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy; + vec2 d = vec2( + length(p.xy - vec2(clamp(p.x, -k.z*h.x, k.z*h.x), h.x))*sign(p.y - h.x), + p.z-h.y ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdOctogonPrism( in vec3 p, in float r, float h ) +{ + const vec3 k = vec3(-0.9238795325, // sqrt(2+sqrt(2))/2 + 0.3826834323, // sqrt(2-sqrt(2))/2 + 0.4142135623 ); // sqrt(2)-1 + // reflections + p = abs(p); + p.xy -= 2.0*min(dot(vec2( k.x,k.y),p.xy),0.0)*vec2( k.x,k.y); + p.xy -= 2.0*min(dot(vec2(-k.x,k.y),p.xy),0.0)*vec2(-k.x,k.y); + // polygon side + p.xy -= vec2(clamp(p.x, -k.z*r, k.z*r), r); + vec2 d = vec2( length(p.xy)*sign(p.y), p.z-h ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdRoundCone( in vec3 p, in float r1, float r2, float h ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + float b = (r1-r2)/h; + float a = sqrt(1.0-b*b); + float k = dot(q,vec2(-b,a)); + + if( k < 0.0 ) return length(q) - r1; + if( k > a*h ) return length(q-vec2(0.0,h)) - r2; + + return dot(q, vec2(a,b) ) - r1; +} + +float sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2) +{ + // sampling independent computations (only depend on shape) + vec3 ba = b - a; + float l2 = dot(ba,ba); + float rr = r1 - r2; + float a2 = l2 - rr*rr; + float il2 = 1.0/l2; + + // sampling dependant computations + vec3 pa = p - a; + float y = dot(pa,ba); + float z = y - l2; + float x2 = dot2( pa*l2 - ba*y ); + float y2 = y*y*l2; + float z2 = z*z*l2; + + // single square root! + float k = sign(rr)*rr*rr*x2; + if( sign(z)*a2*z2 > k ) return sqrt(x2 + z2) *il2 - r2; + if( sign(y)*a2*y2 < k ) return sqrt(x2 + y2) *il2 - r1; + return (sqrt(x2*a2*il2)+y*rr)*il2 - r1; +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + const float k = sqrt(3.0); + h.x *= 0.5*k; + p.xy /= h.x; + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; + p.x -= clamp( p.x, -2.0, 0.0 ); + float d1 = length(p.xy)*sign(-p.y)*h.x; + float d2 = abs(p.z)-h.y; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +// vertical +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +// arbitrary orientation +float sdCylinder(vec3 p, vec3 a, vec3 b, float r) +{ + vec3 pa = p - a; + vec3 ba = b - a; + float baba = dot(ba,ba); + float paba = dot(pa,ba); + + float x = length(pa*baba-ba*paba) - r*baba; + float y = abs(paba-baba*0.5)-baba*0.5; + float x2 = x*x; + float y2 = y*y*baba; + float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0)); + return sign(d)*sqrt(abs(d))/baba; +} + +// vertical +float sdCone( in vec3 p, in vec2 c, float h ) +{ + vec2 q = h*vec2(c.x,-c.y)/c.y; + vec2 w = vec2( length(p.xz), p.y ); + + vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 ); + vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 ); + float k = sign( q.y ); + float d = min(dot( a, a ),dot(b, b)); + float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) ); + return sqrt(d)*sign(s); +} + +float sdCappedCone( in vec3 p, in float h, in float r1, in float r2 ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + vec2 k1 = vec2(r2,h); + vec2 k2 = vec2(r2-r1,2.0*h); + vec2 ca = vec2(q.x-min(q.x,(q.y < 0.0)?r1:r2), abs(q.y)-h); + vec2 cb = q - k1 + k2*clamp( dot(k1-q,k2)/dot2(k2), 0.0, 1.0 ); + float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; + return s*sqrt( min(dot2(ca),dot2(cb)) ); +} + +float sdCappedCone(vec3 p, vec3 a, vec3 b, float ra, float rb) +{ + float rba = rb-ra; + float baba = dot(b-a,b-a); + float papa = dot(p-a,p-a); + float paba = dot(p-a,b-a)/baba; + + float x = sqrt( papa - paba*paba*baba ); + + float cax = max(0.0,x-((paba<0.5)?ra:rb)); + float cay = abs(paba-0.5)-0.5; + + float k = rba*rba + baba; + float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 ); + + float cbx = x-ra - f*rba; + float cby = paba - f; + + float s = (cbx < 0.0 && cay < 0.0) ? -1.0 : 1.0; + + return s*sqrt( min(cax*cax + cay*cay*baba, + cbx*cbx + cby*cby*baba) ); +} + +// c is the sin/cos of the desired cone angle +float sdSolidAngle(vec3 pos, vec2 c, float ra) +{ + vec2 p = vec2( length(pos.xz), pos.y ); + float l = length(p) - ra; + float m = length(p - c*clamp(dot(p,c),0.0,ra) ); + return max(l,m*sign(c.y*p.x-c.x*p.y)); +} + +float sdOctahedron(vec3 p, float s) +{ + p = abs(p); + float m = p.x + p.y + p.z - s; + + // exact distance + #if 0 + vec3 o = min(3.0*p - m, 0.0); + o = max(6.0*p - m*2.0 - o*3.0 + (o.x+o.y+o.z), 0.0); + return length(p - s*o/(o.x+o.y+o.z)); + #endif + + // exact distance + #if 1 + vec3 q; + if( 3.0*p.x < m ) q = p.xyz; + else if( 3.0*p.y < m ) q = p.yzx; + else if( 3.0*p.z < m ) q = p.zxy; + else return m*0.57735027; + float k = clamp(0.5*(q.z-q.y+s),0.0,s); + return length(vec3(q.x,q.y-s+k,q.z-k)); + #endif + + // bound, not exact + #if 0 + return m*0.57735027; + #endif +} + +float sdPyramid( in vec3 p, in float h ) +{ + float m2 = h*h + 0.25; + + // symmetry + p.xz = abs(p.xz); + p.xz = (p.z>p.x) ? p.zx : p.xz; + p.xz -= 0.5; + + // project into face plane (2D) + vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y); + + float s = max(-q.x,0.0); + float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 ); + + float a = m2*(q.x+s)*(q.x+s) + q.y*q.y; + float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t); + + float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b); + + // recover 3D and scale, and add sign + return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y));; +} + +// la,lb=semi axis, h=height, ra=corner +float sdRhombus(vec3 p, float la, float lb, float h, float ra) +{ + p = abs(p); + vec2 b = vec2(la,lb); + float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 ); + vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h); + return min(max(q.x,q.y),0.0) + length(max(q,0.0)); +} + +float sdHorseshoe( in vec3 p, in vec2 c, in float r, in float le, vec2 w ) +{ + p.x = abs(p.x); + float l = length(p.xy); + p.xy = mat2(-c.x, c.y, + c.y, c.x)*p.xy; + p.xy = vec2((p.y>0.0 || p.x>0.0)?p.x:l*sign(-c.x), + (p.x>0.0)?p.y:l ); + p.xy = vec2(p.x,abs(p.y-r))-vec2(le,0.0); + + vec2 q = vec2(length(max(p.xy,0.0)) + min(0.0,max(p.x,p.y)),p.z); + vec2 d = abs(q) - w; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdU( in vec3 p, in float r, in float le, vec2 w ) +{ + p.x = (p.y>0.0) ? abs(p.x) : length(p.xy); + p.x = abs(p.x-r); + p.y = p.y - le; + float k = max(p.x,p.y); + vec2 q = vec2( (k<0.0) ? -k : length(max(p.xy,0.0)), abs(p.z) ) - w; + return length(max(q,0.0)) + min(max(q.x,q.y),0.0); +} + +//------------------------------------------------------------------ + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x0.0 ) + { + tmax = min( tmax, tp1 ); + res = vec2( tp1, 1.0 ); + } + //else return res; + + // raymarch primitives + vec2 tb = iBox( ro-vec3(0.0,0.4,-0.5), rd, vec3(2.5,0.41,3.0) ); + if( tb.x0.0 && tb.x0.0 ) tmax = min( tmax, tp ); + + float res = 1.0; + float t = mint; + for( int i=ZERO; i<24; i++ ) + { + float h = map( ro + rd*t ).x; + float s = clamp(8.0*h/t,0.0,1.0); + res = min( res, s ); + t += clamp( h, 0.01, 0.2 ); + if( res<0.004 || t>tmax ) break; + } + res = clamp( res, 0.0, 1.0 ); + return res*res*(3.0-2.0*res); +} + +// https://iquilezles.org/articles/normalsSDF +vec3 calcNormal( in vec3 pos ) +{ +#if 0 + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); +#else + // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times + vec3 n = vec3(0.0); + for( int i=ZERO; i<4; i++ ) + { + vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0); + n += e*map(pos+0.0005*e).x; + //if( n.x+n.y+n.z>100.0 ) break; + } + return normalize(n); +#endif +} + +// https://iquilezles.org/articles/nvscene2008/rwwtt.pdf +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=ZERO; i<5; i++ ) + { + float h = 0.01 + 0.12*float(i)/4.0; + float d = map( pos + h*nor ).x; + occ += (h-d)*sca; + sca *= 0.95; + if( occ>0.35 ) break; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y); +} + +// https://iquilezles.org/articles/checkerfiltering +float checkersGradBox( in vec2 p, in vec2 dpdx, in vec2 dpdy ) +{ + // filter kernel + vec2 w = abs(dpdx)+abs(dpdy) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +vec3 render( in vec3 ro, in vec3 rd, in vec3 rdx, in vec3 rdy ) +{ + // background + vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y,0.0)*0.3; + + // raycast scene + vec2 res = raycast(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.2 + 0.2*sin( m*2.0 + vec3(0.0,1.0,2.0) ); + float ks = 1.0; + + if( m<1.5 ) + { + // project pixel footprint into the plane + vec3 dpdx = ro.y*(rd/rd.y-rdx/rdx.y); + vec3 dpdy = ro.y*(rd/rd.y-rdy/rdy.y); + + float f = checkersGradBox( 3.0*pos.xz, 3.0*dpdx.xz, 3.0*dpdy.xz ); + col = 0.15 + f*vec3(0.05); + ks = 0.4; + } + + // lighting + float occ = calcAO( pos, nor ); + + vec3 lin = vec3(0.0); + + // sun + { + vec3 lig = normalize( vec3(-0.5, 0.4, -0.6) ); + vec3 hal = normalize( lig-rd ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + //if( dif>0.0001 ) + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0); + //spe *= 0.04+0.96*pow(clamp(1.0-sqrt(0.5*(1.0-dot(rd,lig))),0.0,1.0),5.0); + lin += col*2.20*dif*vec3(1.30,1.00,0.70); + lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; + } + // sky + { + float dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 )); + dif *= occ; + float spe = smoothstep( -0.2, 0.2, ref.y ); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0+dot(nor,rd),0.0,1.0), 5.0 ); + //if( spe>0.001 ) + spe *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + lin += col*0.60*dif*vec3(0.40,0.60,1.15); + lin += 2.00*spe*vec3(0.40,0.60,1.30)*ks; + } + // back + { + float dif = clamp( dot( nor, normalize(vec3(0.5,0.0,0.6))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + dif *= occ; + lin += col*0.55*dif*vec3(0.25,0.25,0.25); + } + // sss + { + float dif = pow(clamp(1.0+dot(nor,rd),0.0,1.0),2.0); + dif *= occ; + lin += col*0.25*dif*vec3(1.00,1.00,1.00); + } + + col = lin; + + col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) ); + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = ( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 mo = iMouse.xy/iResolution.xy; + float time = 32.0 + iTime*1.5; + + // camera + vec3 ta = vec3( 0.25, -0.75, -0.75 ); + vec3 ro = ta + vec3( 4.5*cos(0.1*time + 7.0*mo.x), 2.2, 4.5*sin(0.1*time + 7.0*mo.x) ); + // camera-to-world transformation + mat3 ca = setCamera( ro, ta, 0.0 ); + + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=ZERO; m1 + } + tot /= float(AA*AA); +#endif + + fragColor = vec4( tot, 1.0 ); +} + + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_sea.py b/examples/shadertoy_glsl_sea.py new file mode 100644 index 00000000..8276797e --- /dev/null +++ b/examples/shadertoy_glsl_sea.py @@ -0,0 +1,174 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/mt2XR3 + +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +// Created by S.Guillitte + +#define time iTime + +mat2 m2 = mat2(0.8, 0.6, -0.6, 0.8); + +float noise(in vec2 p){ + + float res=0.; + float f=1.; + for( int i=0; i< 3; i++ ) + { + p=m2*p*f+.6; + f*=1.2; + res+=sin(p.x+sin(2.*p.y)); + } + return res/3.; +} + +vec3 noised(in vec2 p){//noise with derivatives + float res=0.; + vec2 dres=vec2(0.); + float f=1.; + mat2 j=m2; + for( int i=0; i< 3; i++ ) + { + p=m2*p*f+.6; + f*=1.2; + float a=p.x+sin(2.*p.y); + res+=sin(a); + dres+=cos(a)*vec2(1.,2.*cos(2.*p.y))*j; + j*=m2*f; + + } + return vec3(res,dres)/3.; +} + + +float fbmabs( vec2 p ) { + + float f=.7; + float r = 0.0; + for(int i = 0;i<12;i++){ + vec3 n = noised(p); + r += abs(noise( p*f +n.xz)+.5)/f; + f *=1.45; + p=m2*p; + } + return r; +} + +float sea( vec2 p ) +{ + float f=.7; + float r = 0.0; + for(int i = 0;i<6;i++){ + r += (1.-abs(noise( p*f -.6*time)))/f; + f *=1.4; + p-=vec2(-.01,.04)*(r+.2*time/(.1-f)); + } + return r/4.+.8; +} + + + +float rocks(vec2 p){ + return 1.-fbmabs(p)*.15; +} + +vec3 map( vec3 p) +{ + float d1 =p.y+ cos(.2*p.x-sin(.5*p.z))*cos(.2*p.z+sin(.3*p.x))+.5-rocks(p.xz); + float d2 =p.y-.4*sea(p.xz); + //dh = d2-d1; + float d = min(d1,d2); + return vec3(d,d1,d2); + +} + +vec3 normalRocks(in vec2 p) +{ + const vec2 e = vec2(0.004, 0.0); + return normalize(vec3( + rocks(p + e.xy) - rocks(p - e.xy), + .008, + rocks(p + e.yx) - rocks(p - e.yx) + )); +} + +vec3 normalSea(in vec2 p) +{ + const vec2 e = vec2(0.002, 0.0); + return normalize(vec3( + sea(p + e.xy) - sea(p - e.xy), + .004, + sea(p + e.yx) - sea(p - e.yx) + )); +} + +vec3 sky(in vec2 p) +{ + return sin(vec3(1.7,1.5,1)+ 2.-fbmabs(p*12.)*.25)+.3; +} + +vec3 march(in vec3 ro, in vec3 rd) +{ + const float maxd = 35.0; + const float precis = 0.001; + float h = precis * 2.0; + float t = 0.0; + float res = -1.0; + for(int i = 0; i < 128; i++) + { + if(h < precis*t || t > maxd) break; + h = map(ro + rd * t).x; + t += h*.5; + } + if(t < maxd) res = t; + return vec3(res,map(ro + rd * t).yz); +} + + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + + vec2 p = (2.0 * fragCoord.xy - iResolution.xy) / iResolution.y; + vec3 col = vec3(0.); + vec3 rd = normalize(vec3(p, -2.)); + vec3 ro = vec3(0.0, 2.0, -2.+.2*time); + vec3 li = normalize(vec3(2., 2., -4.)); + + vec3 v = march(ro, rd); + float t = v.x; + float dh = v.z-v.y; + if(t > 0.) + { + + vec3 pos = ro + t * rd; + float k=rocks(pos.xz/2.)*2.; + vec3 nor = normalRocks(pos.xz/2.); + float r = max(dot(nor, li),0.05)/2.; + + r+=.4*exp(-500.*dh*dh); + + col =vec3(r*k*k, r*k, r*.8); + if(dh<0.03){ + vec3 nor = normalSea(pos.xz); + nor = reflect(rd, nor); + col +=vec3(0.9,.2,.05)*dh*1.; + col += pow(max(dot(li, nor), 0.0), 5.0)*vec3(.5); + col +=.2* sky(nor.xz/(.5+nor.y)); + + } + col = .1+col; + + } + else //sky + col = sky(rd.xz*(.1+rd.y)); + + fragColor = vec4(col, 1.0); +} + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_glsl_stone.py b/examples/shadertoy_glsl_stone.py new file mode 100644 index 00000000..57181f5b --- /dev/null +++ b/examples/shadertoy_glsl_stone.py @@ -0,0 +1,288 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ + +// https://www.shadertoy.com/view/ldSSzV + +/* +"Wet stone" by Alexander Alekseev aka TDM - 2014 +License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +Contact: tdmaav@gmail.com +*/ + +#define SMOOTH +#define AA + +const int NUM_STEPS = 32; +const int AO_SAMPLES = 4; +const vec2 AO_PARAM = vec2(1.2, 3.5); +const vec2 CORNER_PARAM = vec2(0.25, 40.0); +const float INV_AO_SAMPLES = 1.0 / float(AO_SAMPLES); +const float TRESHOLD = 0.1; +const float EPSILON = 1e-3; +const float LIGHT_INTENSITY = 0.25; +const vec3 RED = vec3(1.0,0.7,0.7) * LIGHT_INTENSITY; +const vec3 ORANGE = vec3(1.0,0.67,0.43) * LIGHT_INTENSITY; +const vec3 BLUE = vec3(0.54,0.77,1.0) * LIGHT_INTENSITY; +const vec3 WHITE = vec3(1.2,1.07,0.98) * LIGHT_INTENSITY; + +const float DISPLACEMENT = 0.1; + +// math +mat3 fromEuler(vec3 ang) { + vec2 a1 = vec2(sin(ang.x),cos(ang.x)); + vec2 a2 = vec2(sin(ang.y),cos(ang.y)); + vec2 a3 = vec2(sin(ang.z),cos(ang.z)); + mat3 m; + m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x); + m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x); + m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y); + return m; +} +vec3 saturation(vec3 c, float t) { + return mix(vec3(dot(c,vec3(0.2126,0.7152,0.0722))),c,t); +} +float hash11(float p) { + return fract(sin(p * 727.1)*435.545); +} +float hash12(vec2 p) { + float h = dot(p,vec2(127.1,311.7)); + return fract(sin(h)*437.545); +} +vec3 hash31(float p) { + vec3 h = vec3(127.231,491.7,718.423) * p; + return fract(sin(h)*435.543); +} + +// 3d noise +float noise_3(in vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + vec3 u = f*f*(3.0-2.0*f); + + vec2 ii = i.xy + i.z * vec2(5.0); + float a = hash12( ii + vec2(0.0,0.0) ); + float b = hash12( ii + vec2(1.0,0.0) ); + float c = hash12( ii + vec2(0.0,1.0) ); + float d = hash12( ii + vec2(1.0,1.0) ); + float v1 = mix(mix(a,b,u.x), mix(c,d,u.x), u.y); + + ii += vec2(5.0); + a = hash12( ii + vec2(0.0,0.0) ); + b = hash12( ii + vec2(1.0,0.0) ); + c = hash12( ii + vec2(0.0,1.0) ); + d = hash12( ii + vec2(1.0,1.0) ); + float v2 = mix(mix(a,b,u.x), mix(c,d,u.x), u.y); + + return max(mix(v1,v2,u.z),0.0); +} + +// fBm +float fbm3(vec3 p, float a, float f) { + return noise_3(p); +} + +float fbm3_high(vec3 p, float a, float f) { + float ret = 0.0; + float amp = 1.0; + float frq = 1.0; + for(int i = 0; i < 5; i++) { + float n = pow(noise_3(p * frq),2.0); + ret += n * amp; + frq *= f; + amp *= a * (pow(n,0.2)); + } + return ret; +} + +// lighting +float diffuse(vec3 n,vec3 l,float p) { return pow(max(dot(n,l),0.0),p); } +float specular(vec3 n,vec3 l,vec3 e,float s) { + float nrm = (s + 8.0) / (3.1415 * 8.0); + return pow(max(dot(reflect(e,n),l),0.0),s) * nrm; +} + +// distance functions +float plane(vec3 gp, vec4 p) { + return dot(p.xyz,gp+p.xyz*p.w); +} +float sphere(vec3 p,float r) { + return length(p)-r; +} +float capsule(vec3 p,float r,float h) { + p.y -= clamp(p.y,-h,h); + return length(p)-r; +} +float cylinder(vec3 p,float r,float h) { + return max(abs(p.y/h),capsule(p,r,h)); +} +float box(vec3 p,vec3 s) { + p = abs(p)-s; + return max(max(p.x,p.y),p.z); +} +float rbox(vec3 p,vec3 s) { + p = abs(p)-s; + return length(p-min(p,0.0)); +} +float quad(vec3 p,vec2 s) { + p = abs(p) - vec3(s.x,0.0,s.y); + return max(max(p.x,p.y),p.z); +} + +// boolean operations +float boolUnion(float a,float b) { return min(a,b); } +float boolIntersect(float a,float b) { return max(a,b); } +float boolSub(float a,float b) { return max(a,-b); } + +// smooth operations. thanks to iq +float boolSmoothIntersect(float a, float b, float k ) { + float h = clamp(0.5+0.5*(b-a)/k, 0.0, 1.0); + return mix(a,b,h) + k*h*(1.0-h); +} +float boolSmoothSub(float a, float b, float k ) { + return boolSmoothIntersect(a,-b,k); +} + +// world +float rock(vec3 p) { + float d = sphere(p,1.0); + for(int i = 0; i < 9; i++) { + float ii = float(i); + float r = 2.5 + hash11(ii); + vec3 v = normalize(hash31(ii) * 2.0 - 1.0); + #ifdef SMOOTH + d = boolSmoothSub(d,sphere(p+v*r,r * 0.8), 0.03); + #else + d = boolSub(d,sphere(p+v*r,r * 0.8)); + #endif + } + return d; +} + +float map(vec3 p) { + float d = rock(p) + fbm3(p*4.0,0.4,2.96) * DISPLACEMENT; + d = boolUnion(d,plane(p,vec4(0.0,1.0,0.0,1.0))); + return d; +} + +float map_detailed(vec3 p) { + float d = rock(p) + fbm3_high(p*4.0,0.4,2.96) * DISPLACEMENT; + d = boolUnion(d,plane(p,vec4(0.0,1.0,0.0,1.0))); + return d; +} + +// tracing +vec3 getNormal(vec3 p, float dens) { + vec3 n; + n.x = map_detailed(vec3(p.x+EPSILON,p.y,p.z)); + n.y = map_detailed(vec3(p.x,p.y+EPSILON,p.z)); + n.z = map_detailed(vec3(p.x,p.y,p.z+EPSILON)); + return normalize(n-map_detailed(p)); +} +vec2 getOcclusion(vec3 p, vec3 n) { + vec2 r = vec2(0.0); + for(int i = 0; i < AO_SAMPLES; i++) { + float f = float(i)*INV_AO_SAMPLES; + float hao = 0.01+f*AO_PARAM.x; + float hc = 0.01+f*CORNER_PARAM.x; + float dao = map(p + n * hao) - TRESHOLD; + float dc = map(p - n * hc) - TRESHOLD; + r.x += clamp(hao-dao,0.0,1.0) * (1.0-f); + r.y += clamp(hc+dc,0.0,1.0) * (1.0-f); + } + r.x = clamp(1.0-r.x*INV_AO_SAMPLES*AO_PARAM.y,0.0,1.0); + r.y = clamp(r.y*INV_AO_SAMPLES*CORNER_PARAM.y,0.0,1.0); + return r; +} +vec2 spheretracing(vec3 ori, vec3 dir, out vec3 p) { + vec2 td = vec2(0.0); + for(int i = 0; i < NUM_STEPS; i++) { + p = ori + dir * td.x; + td.y = map(p); + if(td.y < TRESHOLD) break; + td.x += (td.y-TRESHOLD) * 0.9; + } + return td; +} + +// stone +vec3 getStoneColor(vec3 p, float c, vec3 l, vec3 n, vec3 e) { + c = min(c + pow(noise_3(vec3(p.x*20.0,0.0,p.z*20.0)),70.0) * 8.0, 1.0); + float ic = pow(1.0-c,0.5); + vec3 base = vec3(0.42,0.3,0.2) * 0.35; + vec3 sand = vec3(0.51,0.41,0.32)*0.9; + vec3 color = mix(base,sand,c); + + float f = pow(1.0 - max(dot(n,-e),0.0), 5.0) * 0.75 * ic; + color += vec3(diffuse(n,l,0.5) * WHITE); + color += vec3(specular(n,l,e,8.0) * WHITE * 1.5 * ic); + n = normalize(n - normalize(p) * 0.4); + color += vec3(specular(n,l,e,80.0) * WHITE * 1.5 * ic); + color = mix(color,vec3(1.0),f); + + color *= sqrt(abs(p.y*0.5+0.5)) * 0.4 + 0.6; + color *= (n.y * 0.5 + 0.5) * 0.4 + 0.6; + + return color; +} + +vec3 getPixel(in vec2 coord, float time) { + vec2 iuv = coord / iResolution.xy * 2.0 - 1.0; + vec2 uv = iuv; + uv.x *= iResolution.x / iResolution.y; + + // ray + vec3 ang = vec3(0.0,0.2,time); + if(iMouse.z > 0.0) ang = vec3(0.0,clamp(2.0-iMouse.y*0.01,0.0,3.1415),iMouse.x*0.01); + mat3 rot = fromEuler(ang); + + vec3 ori = vec3(0.0,0.0,2.8); + vec3 dir = normalize(vec3(uv.xy,-2.0)); + ori = ori * rot; + dir = dir * rot; + + // tracing + vec3 p; + vec2 td = spheretracing(ori,dir,p); + vec3 n = getNormal(p,td.y); + vec2 occ = getOcclusion(p,n); + vec3 light = normalize(vec3(0.0,1.0,0.0)); + + // color + vec3 color = vec3(1.0); + if(td.x < 3.5 && p.y > -0.89) color = getStoneColor(p,occ.y,light,n,dir); + color *= occ.x; + return color; +} + +// main +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + float time = iTime * 0.3; + +#ifdef AA + vec3 color = vec3(0.0); + for(int i = -1; i <= 1; i++) + for(int j = -1; j <= 1; j++) { + vec2 uv = fragCoord+vec2(i,j)/3.0; + color += getPixel(uv, time); + } + color /= 9.0; +#else + vec3 color = getPixel(fragCoord, time); +#endif + color = sqrt(color); + color = saturation(color,1.7); + + // vignette + vec2 iuv = fragCoord / iResolution.xy * 2.0 - 1.0; + float vgn = smoothstep(1.2,0.7,abs(iuv.y)) * smoothstep(1.1,0.8,abs(iuv.x)); + color *= 1.0 - (1.0 - vgn) * 0.15; + + fragColor = vec4(color,1.0); +} + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/examples/shadertoy_liberation.py b/examples/shadertoy_liberation.py index 1b9234c5..4d2d3ab1 100644 --- a/examples/shadertoy_liberation.py +++ b/examples/shadertoy_liberation.py @@ -125,7 +125,7 @@ } fn shader_main(frag_coord: vec2) -> vec4 { - var uv = frag_coord / i_resolution - 0.5; + var uv = frag_coord / i_resolution.xy - 0.5; uv.x *= i_resolution.x / i_resolution.y; var fro = vec3(0.0, 0.0, -10.0); diff --git a/examples/shadertoy_riders.py b/examples/shadertoy_riders.py index 528f0f04..ddec8e89 100644 --- a/examples/shadertoy_riders.py +++ b/examples/shadertoy_riders.py @@ -31,8 +31,8 @@ } fn shader_main(frag_coord: vec2) -> vec4 { - let uv = (frag_coord - i_resolution / 2.0) / i_resolution.y; - let d=vec2(0.0,0.5)/i_resolution; + let uv = (frag_coord - i_resolution.xy / 2.0) / i_resolution.y; + let d=vec2(0.0,0.5)/i_resolution.xy; let col = render(uv)+render(uv+d.xy)+render(uv-d.xy)+render(uv+d.yx)+render(uv-d.yx); return vec4(col/5.0, 1.0); } diff --git a/examples/shadertoy_sea.py b/examples/shadertoy_sea.py index 0062c20f..6645a806 100644 --- a/examples/shadertoy_sea.py +++ b/examples/shadertoy_sea.py @@ -162,7 +162,7 @@ } fn getPixel( coord: vec2, time: f32 ) -> vec3 { - var uv = coord / i_resolution; + var uv = coord / i_resolution.xy; uv = uv * 2.0 - 1.0; uv.x *= i_resolution.x / i_resolution.y; diff --git a/tests/test_util_shadertoy.py b/tests/test_util_shadertoy.py index b2860272..d1372f93 100644 --- a/tests/test_util_shadertoy.py +++ b/tests/test_util_shadertoy.py @@ -18,15 +18,15 @@ def force_offscreen(): del os.environ["WGPU_FORCE_OFFSCREEN"] -def test_shadertoy(): - # Import here, because it imports the wgou.gui.auto +def test_shadertoy_wgsl(): + # Import here, because it imports the wgpu.gui.auto from wgpu.utils.shadertoy import Shadertoy # noqa shader_code = """ fn shader_main(frag_coord: vec2) -> vec4 { - let uv = frag_coord / i_resolution; + let uv = frag_coord / i_resolution.xy; - if ( length(frag_coord - i_mouse) < 20.0 ) { + if ( length(frag_coord - i_mouse.xy) < 20.0 ) { return vec4(0.0, 0.0, 0.0, 1.0); }else{ return vec4( 0.5 + 0.5 * sin(i_time * vec3(uv, 1.0) ), 1.0); @@ -38,5 +38,31 @@ def test_shadertoy(): shader = Shadertoy(shader_code, resolution=(800, 450)) assert shader.resolution == (800, 450) assert shader.shader_code == shader_code + assert shader.shader_type == "wgsl" + + shader._draw_frame() + + +def test_shadertoy_glsl(): + # Import here, because it imports the wgpu.gui.auto + from wgpu.utils.shadertoy import Shadertoy # noqa + + shader_code = """ + void shader_main(out vec4 fragColor, vec2 frag_coord) { + vec2 uv = frag_coord / i_resolution.xy; + + if ( length(frag_coord - i_mouse.xy) < 20.0 ) { + fragColor = vec4(0.0, 0.0, 0.0, 1.0); + }else{ + fragColor = vec4( 0.5 + 0.5 * sin(i_time * vec3(uv, 1.0) ), 1.0); + } + + } + """ + + shader = Shadertoy(shader_code, resolution=(800, 450)) + assert shader.resolution == (800, 450) + assert shader.shader_code == shader_code + assert shader.shader_type == "glsl" shader._draw_frame() diff --git a/wgpu/utils/shadertoy.py b/wgpu/utils/shadertoy.py index 7da71c99..66c8b47b 100644 --- a/wgpu/utils/shadertoy.py +++ b/wgpu/utils/shadertoy.py @@ -6,7 +6,77 @@ from wgpu.gui.auto import WgpuCanvas, run -vertex_code = """ +vertex_code_glsl = """ +#version 450 core + +layout(location = 0) out vec2 uv; + +void main(void){ + int index = int(gl_VertexID); + if (index == 0) { + gl_Position = vec4(-1.0, -1.0, 0.0, 1.0); + uv = vec2(0.0, 1.0); + } else if (index == 1) { + gl_Position = vec4(3.0, -1.0, 0.0, 1.0); + uv = vec2(2.0, 1.0); + } else { + gl_Position = vec4(-1.0, 3.0, 0.0, 1.0); + uv = vec2(0.0, -1.0); + } +} +""" + +builtin_variables_glsl = """ +#version 450 core + +vec3 i_resolution; +vec4 i_mouse; +float i_time; +float i_time_delta; +int i_frame; + +// Shadertoy compatibility, see we can use the same code copied from shadertoy website + +#define iTime i_time +#define iResolution i_resolution +#define iTimeDelta i_time_delta +#define iMouse i_mouse +#define iFrame i_frame + +#define mainImage shader_main +""" + +fragment_code_glsl = """ +layout(location = 0) in vec2 uv; + +struct ShadertoyInput { + vec4 mouse; + vec3 resolution; + float time; + float time_delta; + int frame; +}; + +layout(binding = 0) uniform ShadertoyInput input; +out vec4 FragColor; +void main(){ + + i_time = input.time; + i_resolution = input.resolution; + i_time_delta = input.time_delta; + i_mouse = input.mouse; + i_frame = input.frame; + + + vec2 uv = vec2(uv.x, 1.0 - uv.y); + vec2 frag_coord = uv * i_resolution.xy; + + shader_main(FragColor, frag_coord); + +} + +""" +vertex_code_wgsl = """ struct Varyings { @builtin(position) position : vec4, @@ -14,7 +84,7 @@ }; @vertex -fn vs_main(@builtin(vertex_index) index: u32) -> Varyings { +fn main(@builtin(vertex_index) index: u32) -> Varyings { var out: Varyings; if (index == u32(0)) { out.position = vec4(-1.0, -1.0, 0.0, 1.0); @@ -31,12 +101,12 @@ } """ -builtin_variables = """ +builtin_variables_wgsl = """ -var i_time: f32; -var i_resolution: vec2; +var i_resolution: vec3; +var i_mouse: vec4; var i_time_delta: f32; -var i_mouse: vec2; +var i_time: f32; var i_frame: u32; // TODO: more global variables @@ -44,38 +114,42 @@ """ -fragment_code = """ +fragment_code_wgsl = """ struct ShadertoyInput { - resolution: vec2, - mouse: vec2, + mouse: vec4, + resolution: vec3, time: f32, time_delta: f32, frame: u32, }; +struct Varyings { + @builtin(position) position : vec4, + @location(0) uv : vec2, +}; + @group(0) @binding(0) var input: ShadertoyInput; @fragment -fn fs_main(in: Varyings) -> @location(0) vec4 { +fn main(in: Varyings) -> @location(0) vec4 { i_time = input.time; i_resolution = input.resolution; i_time_delta = input.time_delta; i_mouse = input.mouse; - i_mouse.y = i_resolution.y - i_mouse.y; i_frame = input.frame; let uv = vec2(in.uv.x, 1.0 - in.uv.y); - let frag_coord = uv * i_resolution; + let frag_coord = uv * i_resolution.xy; return shader_main(frag_coord); } - """ +""" binding_layout = [ { @@ -86,19 +160,19 @@ ] uniform_dtype = [ - ("resolution", "float32", (2)), - ("mouse", "float32", (2)), + ("mouse", "float32", (4)), + ("resolution", "float32", (3)), ("time", "float32"), ("time_delta", "float32"), ("frame", "uint32"), - ("__padding", "uint8", (4)), # padding to 32 bytes + ("__padding", "uint32", (2)), # padding to 48 bytes ] class Shadertoy: """Provides a "screen pixel shader programming interface" similar to `shadertoy `_. - It helps you research and quickly build or test shaders using WGSL via WGPU. + It helps you research and quickly build or test shaders using `WGSL` or `GLSL` via WGPU. Parameters: shader_code (str): The shader code to use. @@ -106,10 +180,11 @@ class Shadertoy: The shader code must contain a entry point function: - ``fn shader_main(frag_coord: vec2) -> vec4{}`` + WGSL: ``fn shader_main(frag_coord: vec2) -> vec4{}`` + GLSL: ``void shader_main(out vec4 frag_color, in vec2 frag_coord){}`` It has a parameter ``frag_coord`` which is the current pixel coordinate (in range 0..resolution, origin is bottom-left), - and it must return a vec4 color, which is the color of the pixel at that coordinate. + and it must return a vec4 color (for GLSL, it's the ``out vec4 frag_color`` parameter), which is the color of the pixel at that coordinate. some built-in variables are available in the shader: @@ -119,6 +194,8 @@ class Shadertoy: * ``i_resolution``: the resolution of the shadertoy * ``i_mouse``: the mouse position in pixels + For GLSL, you can also use the aliases ``iTime``, ``iTimeDelta``, ``iFrame``, ``iResolution``, and ``iMouse`` of these built-in variables, + the entry point function also has an alias ``mainImage``, so you can use the shader code copied from shadertoy website without making any changes. """ # todo: add more built-in variables @@ -129,7 +206,7 @@ def __init__(self, shader_code, resolution=(800, 450)) -> None: self._uniform_data = np.zeros((), dtype=uniform_dtype) self._shader_code = shader_code - self._uniform_data["resolution"] = resolution + self._uniform_data["resolution"] = resolution + (1,) self._prepare_render() self._bind_events() @@ -137,13 +214,26 @@ def __init__(self, shader_code, resolution=(800, 450)) -> None: @property def resolution(self): """The resolution of the shadertoy as a tuple (width, height) in pixels.""" - return tuple(self._uniform_data["resolution"]) + return tuple(self._uniform_data["resolution"])[:2] @property def shader_code(self): """The shader code to use.""" return self._shader_code + @property + def shader_type(self): + """The shader type, automatically detected from the shader code, can be "wgsl" or "glsl".""" + if "fn shader_main" in self.shader_code: + return "wgsl" + elif ( + "void shader_main" in self.shader_code + or "void mainImage" in self.shader_code + ): + return "glsl" + else: + raise ValueError("Invalid shader code.") + def _prepare_render(self): import wgpu.backends.rs # noqa @@ -161,8 +251,24 @@ def _prepare_render(self): device=self._device, format=wgpu.TextureFormat.bgra8unorm ) - shader_code = vertex_code + builtin_variables + self.shader_code + fragment_code - shader_program = self._device.create_shader_module(code=shader_code) + shader_type = self.shader_type + if shader_type == "glsl": + vertex_shader_code = vertex_code_glsl + frag_shader_code = ( + builtin_variables_glsl + self.shader_code + fragment_code_glsl + ) + elif shader_type == "wgsl": + vertex_shader_code = vertex_code_wgsl + frag_shader_code = ( + builtin_variables_wgsl + self.shader_code + fragment_code_wgsl + ) + + vertex_shader_program = self._device.create_shader_module( + label="triangle_vert", code=vertex_shader_code + ) + frag_shader_program = self._device.create_shader_module( + label="triangle_frag", code=frag_shader_code + ) self._uniform_buffer = self._device.create_buffer( size=self._uniform_data.nbytes, @@ -192,8 +298,8 @@ def _prepare_render(self): bind_group_layouts=[bind_group_layout] ), vertex={ - "module": shader_program, - "entry_point": "vs_main", + "module": vertex_shader_program, + "entry_point": "main", "buffers": [], }, primitive={ @@ -204,8 +310,8 @@ def _prepare_render(self): depth_stencil=None, multisample=None, fragment={ - "module": shader_program, - "entry_point": "fs_main", + "module": frag_shader_program, + "entry_point": "main", "targets": [ { "format": wgpu.TextureFormat.bgra8unorm, @@ -229,15 +335,26 @@ def _prepare_render(self): def _bind_events(self): def on_resize(event): w, h = event["width"], event["height"] - self._uniform_data["resolution"] = (w, h) + self._uniform_data["resolution"] = (w, h, 1) def on_mouse_move(event): - xy = event["x"], event["y"] if event["button"] == 1 or 1 in event["buttons"]: - self._uniform_data["mouse"] = xy + xy = event["x"], self.resolution[1] - event["y"] + self._uniform_data["mouse"][:2] = xy + + def on_mouse_down(event): + if event["button"] == 1 or 1 in event["buttons"]: + x, y = event["x"], self.resolution[1] - event["y"] + self._uniform_data["mouse"] = (x, y, x, -y) + + def on_mouse_up(event): + if event["button"] == 1 or 1 in event["buttons"]: + self._uniform_data["mouse"][2] = -abs(self._uniform_data["mouse"][2]) self._canvas.add_event_handler(on_resize, "resize") - self._canvas.add_event_handler(on_mouse_move, "pointer_move", "pointer_down") + self._canvas.add_event_handler(on_mouse_move, "pointer_move") + self._canvas.add_event_handler(on_mouse_down, "pointer_down") + self._canvas.add_event_handler(on_mouse_up, "pointer_up") def _update(self): now = time.perf_counter() @@ -296,9 +413,9 @@ def show(self): shader = Shadertoy( """ fn shader_main(frag_coord: vec2) -> vec4 { - let uv = frag_coord / i_resolution; + let uv = frag_coord / i_resolution.xy; - if ( length(frag_coord - i_mouse) < 20.0 ) { + if ( length(frag_coord - i_mouse.xy) < 20.0 ) { return vec4(0.0, 0.0, 0.0, 1.0); }else{ return vec4( 0.5 + 0.5 * sin(i_time * vec3(uv, 1.0) ), 1.0);