diff --git a/demos/gtk-demo/alienplanet.glsl b/demos/gtk-demo/alienplanet.glsl new file mode 100644 index 0000000000..3e3aea3ac8 --- /dev/null +++ b/demos/gtk-demo/alienplanet.glsl @@ -0,0 +1,345 @@ +// Originally from: https://www.shadertoy.com/view/wsjBD3 +// License CC0: A battered alien planet +// Been experimenting with space inspired shaders + +#define PI 3.141592654 +#define TAU (2.0*PI) + +#define TOLERANCE 0.00001 +#define MAX_ITER 65 +#define MIN_DISTANCE 0.01 +#define MAX_DISTANCE 9.0 + +const vec3 skyCol1 = vec3(0.35, 0.45, 0.6); +const vec3 skyCol2 = vec3(0.4, 0.7, 1.0); +const vec3 skyCol3 = pow(skyCol1, vec3(0.25)); +const vec3 sunCol1 = vec3(1.0,0.6,0.4); +const vec3 sunCol2 = vec3(1.0,0.9,0.7); +const vec3 smallSunCol1 = vec3(1.0,0.5,0.25)*0.5; +const vec3 smallSunCol2 = vec3(1.0,0.5,0.25)*0.5; +const vec3 mountainColor = 1.0*sqrt(vec3(0.95, 0.65, 0.45)); +const float cellWidth = 1.0; +const vec4 planet = vec4(80.0, -20.0, 100.0, 50.0)*1000.0; + +void rot(inout vec2 p, float a) { + float c = cos(a); + float s = sin(a); + p = vec2(p.x*c + p.y*s, -p.x*s + p.y*c); +} + +vec2 mod2(inout vec2 p, vec2 size) { + vec2 c = floor((p + size*0.5)/size); + p = mod(p + size*0.5,size) - size*0.5; + return c; +} + +float circle(vec2 p, float r) { + return length(p) - r; +} + +float egg(vec2 p, float ra, float rb) { + const float k = sqrt(3.0); + p.x = abs(p.x); + float r = ra - rb; + return ((p.y<0.0) ? length(vec2(p.x, p.y )) - r : + (k*(p.x+r) 0.0 && b > 0.0) return vec2(-1.0, -1.0); + float discr = b * b - c; + if(discr < 0.0) return vec2(-1.0); + float normalMultiplier = 1.0; + float s = sqrt(discr); + float t0 = -b - s; + float t1 = -b + s;; + return vec2(t0, t1); +} + +float noise1(vec2 p) { + vec2 n = mod2(p, vec2(cellWidth)); + vec2 hh = hash(sqrt(2.0)*(n+1000.0)); + hh.x *= hh.y; + + float r = 0.225*cellWidth; + + float d = circle(p, 2.0*r); + + float h = hh.x*smoothstep(0.0, r, -d); + + return h*0.25; +} + +float noise2(vec2 p) { + vec2 n = mod2(p, vec2(cellWidth)); + vec2 hh = hash(sqrt(2.0)*(n+1000.0)); + hh.x *= hh.y; + + rot(p, TAU*hh.y); + float r = 0.45*cellWidth; + +// float d = circle(p, 1.0*r); + float d = egg(p, 0.75*r, 0.5*r*abs(hh.y)); + + float h = (hh.x)*smoothstep(0.0, r, -2.0*d); + + return h*0.275; +} + + +float height(vec2 p, float dd, int mx) { + const float aa = 0.45; + const float ff = 2.03; + const float tt = 1.2; + const float oo = 3.93; + const float near = 0.25; + const float far = 0.65; + + float a = 1.0; + float o = 0.2; + float s = 0.0; + float d = 0.0; + + int i = 0; + + for (; i < 4;++i) { + float nn = a*noise2(p); + s += nn; + d += abs(a); + p += o; + a *= aa; + p *= ff; + o *= oo; + rot(p, tt); + } + + float lod = s/d; + + float rdd = dd/MAX_DISTANCE; + mx = int(mix(float(4), float(mx), step(rdd, far))); + + for (; i < mx; ++i) { + float nn = a*noise1(p); + s += nn; + d += abs(a); + p += o; + a *= aa; + p *= ff; + o *= oo; + rot(p, tt); + } + + float hid = (s/d); + + return mix(hid, lod, smoothstep(near, far, rdd)); +} + +float loheight(vec2 p, float d) { + return height(p, d, 0); +} + +float height(vec2 p, float d) { + return height(p, d, 6); +} + +float hiheight(vec2 p, float d) { + return height(p, d, 8); +} + +vec3 normal(vec2 p, float d) { + vec2 eps = vec2(0.00125, 0.0); + + vec3 n; + + n.x = (hiheight(p - eps.xy, d) - hiheight(p + eps.xy, d)); + n.y = 2.0*eps.x; + n.z = (hiheight(p - eps.yx, d) - hiheight(p + eps.yx, d)); + + return normalize(n); +} + +const float stepLength[] = float[](0.9, 0.25); + + +float march(vec3 ro, vec3 rd, out int max_iter) { + float dt = 0.1; + float d = MIN_DISTANCE; + int currentStep = 0; + float lastd = d; + for (int i = 0; i < MAX_ITER; ++i) + { + vec3 p = ro + d*rd; + float h = height(p.xz, d); + + if (d > MAX_DISTANCE) { + max_iter = i; + return MAX_DISTANCE; + } + + float hd = p.y - h; + + if (hd < TOLERANCE) { + ++currentStep; + if (currentStep >= stepLength.length()) { + max_iter = i; + return d; + } + + d = lastd; + continue; + } + + float sl = stepLength[currentStep]; + + dt = max(hd, TOLERANCE)*sl + 0.0025*d; + lastd = d; + d += dt; + } + + max_iter = MAX_ITER; + return MAX_DISTANCE; +} + +vec3 sunDirection() { + return normalize(vec3(-0.5, 0.085, 1.0)); +} + +vec3 smallSunDirection() { + return normalize(vec3(-0.2, -0.05, 1.0)); +} + +float psin(float f) { + return 0.5 + 0.5*sin(f); +} + +vec3 skyColor(vec3 ro, vec3 rd) { + vec3 sunDir = sunDirection(); + vec3 smallSunDir = smallSunDirection(); + + float sunDot = max(dot(rd, sunDir), 0.0); + float smallSunDot = max(dot(rd, smallSunDir), 0.0); + + float angle = atan(rd.y, length(rd.xz))*2.0/PI; + + vec3 skyCol = mix(mix(skyCol1, skyCol2, max(0.0, angle)), skyCol3, clamp(-angle*2.0, 0.0, 1.0)); + + vec3 sunCol = 0.5*sunCol1*pow(sunDot, 20.0) + 8.0*sunCol2*pow(sunDot, 2000.0); + vec3 smallSunCol = 0.5*smallSunCol1*pow(smallSunDot, 200.0) + 8.0*smallSunCol2*pow(smallSunDot, 20000.0); + + vec3 dust = pow(sunCol2*mountainColor, vec3(1.75))*smoothstep(0.05, -0.1, rd.y)*0.5; + + vec2 si = raySphere(ro, rd, planet); + + vec3 planetSurface = ro + si.x*rd; + vec3 planetNormal = normalize(planetSurface - planet.xyz); + float planetDiff = max(dot(planetNormal, sunDir), 0.0); + float planetBorder = max(dot(planetNormal, -rd), 0.0); + float planetLat = (planetSurface.x+planetSurface.y)*0.0005; + vec3 planetCol = mix(1.3*vec3(0.9, 0.8, 0.7), 0.3*vec3(0.9, 0.8, 0.7), pow(psin(planetLat+1.0)*psin(sqrt(2.0)*planetLat+2.0)*psin(sqrt(3.5)*planetLat+3.0), 0.5)); + + vec3 final = vec3(0.0); + + final += step(0.0, si.x)*pow(planetDiff, 0.75)*planetCol*smoothstep(-0.075, 0.0, rd.y)*smoothstep(0.0, 0.1, planetBorder); + + final += skyCol + sunCol + smallSunCol + dust; + + return final; +} + +vec3 getColor(vec3 ro, vec3 rd) { + int max_iter = 0; + vec3 skyCol = skyColor(ro, rd); + vec3 col = vec3(0); + + float d = march(ro, rd, max_iter); + + if (d < MAX_DISTANCE) { + vec3 sunDir = sunDirection(); + vec3 osunDir = sunDir*vec3(-1.0, .0, -1.0); + vec3 p = ro + d*rd; + + vec3 normal = normal(p.xz, d); + + float amb = 0.2; + + float dif1 = max(0.0, dot(sunDir, normal)); + vec3 shd1 = sunCol2*mix(amb, 1.0, pow(dif1, 0.75)); + + float dif2 = max(0.0, dot(osunDir, normal)); + vec3 shd2 = sunCol1*mix(amb, 1.0, pow(dif2, 0.75)); + + vec3 ref = reflect(rd, normal); + vec3 rcol = skyColor(p, ref); + + col = mountainColor*amb*skyCol3; + col += mix(shd1, shd2, -0.5)*mountainColor; + float fre = max(dot(normal, -rd), 0.0); + fre = pow(1.0 - fre, 5.0); + col += rcol*fre*0.5; + col += (1.0*p.y); + col = tanh(col); + col = mix(col, skyCol, smoothstep(0.5*MAX_DISTANCE, 1.0*MAX_DISTANCE, d)); + + } else { + col = skyCol; + } + +// col += vec3(1.1, 0.0, 0.0)* smoothstep(0.25, 1.0,(float(max_iter)/float(MAX_ITER))); + return col; +} + +vec3 getSample1(vec2 p, float time) { + float off = 0.5*iTime; + + vec3 ro = vec3(0.5, 1.0-0.25, -2.0 + off); + vec3 la = ro + vec3(0.0, -0.30, 2.0); + + vec3 ww = normalize(la - ro); + vec3 uu = normalize(cross(vec3(0.0,1.0,0.0), ww)); + vec3 vv = normalize(cross(ww, uu)); + vec3 rd = normalize(p.x*uu + p.y*vv + 2.0*ww); + + vec3 col = getColor(ro, rd) ; + + return col; +} + +vec3 getSample2(vec2 p, float time) { + p.y-=time*0.25; + float h = height(p, 0.0); + vec3 n = normal(p, 0.0); + + vec3 lp = vec3(10.0, -1.2, 0.0); + + vec3 ld = normalize(vec3(p.x, h, p.y)- lp); + + float d = max(dot(ld, n), 0.0); + + vec3 col = vec3(0.0); + + col = vec3(1.0)*(h+0.1); + col += vec3(1.5)*pow(d, 0.75); + + return col; +} + +void mainImage(out vec4 fragColor, vec2 fragCoord) { + vec2 q = fragCoord.xy/iResolution.xy; + vec2 p = -1.0 + 2.0*q; + p.x *= iResolution.x/iResolution.y; + + vec3 col = getSample1(p, iTime); + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/cogs.glsl b/demos/gtk-demo/cogs.glsl new file mode 100644 index 0000000000..a0768bdb39 --- /dev/null +++ b/demos/gtk-demo/cogs.glsl @@ -0,0 +1,224 @@ +// Originally from: https://www.shadertoy.com/view/3ljyDD +// License CC0: Hexagonal tiling + cog wheels +// Nothing fancy, just hexagonal tiling + cog wheels + +#define PI 3.141592654 +#define TAU (2.0*PI) +#define MROT(a) mat2(cos(a), sin(a), -sin(a), cos(a)) + +float hash(in vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,58.233))) * 13758.5453); +} + +float pcos(float a) { + return 0.5 + 0.5*cos(a); +} + +void rot(inout vec2 p, float a) { + float c = cos(a); + float s = sin(a); + p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y); +} + +float modPolar(inout vec2 p, float repetitions) { + float angle = 2.0*PI/repetitions; + float a = atan(p.y, p.x) + angle/2.; + float r = length(p); + float c = floor(a/angle); + a = mod(a,angle) - angle/2.; + p = vec2(cos(a), sin(a))*r; + // For an odd number of repetitions, fix cell index of the cell in -x direction + // (cell index would be e.g. -5 and 5 in the two halves of the cell): + if (abs(c) >= (repetitions/2.0)) c = abs(c); + return c; +} + +float pmin(float a, float b, float k) { + float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 ); + return mix( b, a, h ) - k*h*(1.0-h); +} + +const vec2 sz = vec2(1.0, sqrt(3.0)); +const vec2 hsz = 0.5*sz; +const float smallCount = 16.0; + +vec2 hextile(inout vec2 p) { + // See Art of Code: Hexagonal Tiling Explained! + // https://www.youtube.com/watch?v=VmrIDyYiJBA + + vec2 p1 = mod(p, sz)-hsz; + vec2 p2 = mod(p - hsz*1.0, sz)-hsz; + vec2 p3 = mix(p2, p1, vec2(length(p1) < length(p2))); + vec2 n = p3 - p; + p = p3; + + return n; +} + +float circle(vec2 p, float r) { + return length(p) - r; +} + +float box(vec2 p, vec2 b) { + vec2 d = abs(p)-b; + return length(max(d,0.0)) + min(max(d.x,d.y),0.0); +} + +float unevenCapsule(vec2 p, float r1, float r2, float h) { + p.x = abs(p.x); + float b = (r1-r2)/h; + float a = sqrt(1.0-b*b); + float k = dot(p,vec2(-b,a)); + if( k < 0.0 ) return length(p) - r1; + if( k > a*h ) return length(p-vec2(0.0,h)) - r2; + return dot(p, vec2(a,b) ) - r1; +} + +float cogwheel(vec2 p, float innerRadius, float outerRadius, float cogs, float holes) { + float cogWidth = 0.25*innerRadius*TAU/cogs; + + float d0 = circle(p, innerRadius); + + vec2 icp = p; + modPolar(icp, holes); + icp -= vec2(innerRadius*0.55, 0.0); + float d1 = circle(icp, innerRadius*0.25); + + vec2 cp = p; + modPolar(cp, cogs); + cp -= vec2(innerRadius, 0.0); + float d2 = unevenCapsule(cp.yx, cogWidth, cogWidth*0.75, (outerRadius-innerRadius)); + + float d3 = circle(p, innerRadius*0.20); + + float d = 1E6; + d = min(d, d0); + d = pmin(d, d2, 0.5*cogWidth); + d = min(d, d2); + d = max(d, -d1); + d = max(d, -d3); + + return d; +} + +float ccell1(vec2 p, float r) { + float d = 1E6; + const float bigCount = 60.0; + + vec2 cp0 = p; + rot(cp0, -iTime*TAU/bigCount); + float d0 = cogwheel(cp0, 0.36, 0.38, bigCount, 5.0); + + vec2 cp1 = p; + float nm = modPolar(cp1, 6.0); + + cp1 -= vec2(0.5, 0.0); + rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount); + float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0); + + d = min(d, d0); + d = min(d, d1); + return d; +} + +float ccell2(vec2 p, float r) { + float d = 1E6; + vec2 cp0 = p; + float nm = modPolar(cp0, 6.0); + vec2 cp1 = cp0; + const float off = 0.275; + const float count = smallCount + 2.0; + cp0 -= vec2(off, 0.0); + rot(cp0, 0.+TAU*nm/2.0 - iTime*TAU/count); + float d0 = cogwheel(cp0, 0.09, 0.105, count, 5.0); + + + cp1 -= vec2(0.5, 0.0); + rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount); + float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0); + + float l = length(p); + float d2 = l - (off+0.055); + float d3 = d2 + 0.020;; + + vec2 tp0 = p; + modPolar(tp0, 60.0); + tp0.x -= off; + float d4 = box(tp0, vec2(0.0125, 0.005)); + + float ctime = -(iTime*0.05 + r)*TAU; + + vec2 tp1 = p; + rot(tp1, ctime*12.0); + tp1.x -= 0.13; + float d5 = box(tp1, vec2(0.125, 0.005)); + + vec2 tp2 = p; + rot(tp2, ctime); + tp2.x -= 0.13*0.5; + float d6 = box(tp2, vec2(0.125*0.5, 0.0075)); + + float d7 = l - 0.025; + float d8 = l - 0.0125; + + d = min(d, d0); + d = min(d, d1); + d = min(d, d2); + d = max(d, -d3); + d = min(d, d4); + d = min(d, d5); + d = min(d, d6); + d = min(d, d7); + d = max(d, -d8); + + return d; +} + +float df(vec2 p, float scale, inout vec2 nn) { + p /= scale; + nn = hextile(p); + nn = round(nn); + float r = hash(nn); + + float d;; + + if (r < 0.5) { + d = ccell1(p, r); + } else { + d = ccell2(p, r); + } + + return d*scale; +} + +vec3 postProcess(vec3 col, vec2 q) { + //col = saturate(col); + col=pow(clamp(col,0.0,1.0),vec3(0.75)); + col=col*0.6+0.4*col*col*(3.0-2.0*col); // contrast + col=mix(col, vec3(dot(col, vec3(0.33))), -0.4); // satuation + col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7); // vigneting + return col; +} + +void mainImage(out vec4 fragColor, vec2 fragCoord) { + vec2 q = fragCoord/iResolution.xy; + vec2 p = -1.0 + 2.0*q; + p.x *= iResolution.x/iResolution.y; + float tm = iTime*0.1; + p += vec2(cos(tm), sin(tm*sqrt(0.5))); + float z = mix(0.5, 1.0, pcos(tm*sqrt(0.3))); + float aa = 4.0 / iResolution.y; + + vec2 nn = vec2(0.0); + float d = df(p, z, nn); + + vec3 col = vec3(160.0)/vec3(255.0); + vec3 baseCol = vec3(0.3); + vec4 logoCol = vec4(baseCol, 1.0)*smoothstep(-aa, 0.0, -d); + col = mix(col, logoCol.xyz, pow(logoCol.w, 8.0)); + col += 0.4*pow(abs(sin(20.0*d)), 0.6); + + col = postProcess(col, q); + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 7c9c657f5e..f179d9819b 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -121,6 +121,15 @@ gtkgears.c gtkgears.h + + gtkshadertoy.c + gtkshadertoy.h + alienplanet.glsl + mandelbrot.glsl + neon.glsl + cogs.glsl + glowingstars.glsl + iconscroll.ui @@ -260,6 +269,7 @@ scale.c search_entry.c search_entry2.c + shadertoy.c shortcuts.c shortcut_triggers.c sizegroup.c diff --git a/demos/gtk-demo/glowingstars.glsl b/demos/gtk-demo/glowingstars.glsl new file mode 100644 index 0000000000..3d73131024 --- /dev/null +++ b/demos/gtk-demo/glowingstars.glsl @@ -0,0 +1,174 @@ +// Originally from: https://www.shadertoy.com/view/ttBcRV +// License CC0: Flying through glowing stars +// The result of playing around trying to improve an old shader + +#define PI 3.141592654 +#define TAU (2.0*PI) +#define TIME iTime +#define RESOLUTION iResolution + +#define LESS(a,b,c) mix(a,b,step(0.,c)) +#define SABS(x,k) LESS((.5/(k))*(x)*(x)+(k)*.5,abs(x),abs(x)-(k)) + +#define MROT(a) mat2(cos(a), sin(a), -sin(a), cos(a)) + +vec3 hsv2rgb(vec3 c) { + const vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float hash(in vec3 co) { + return fract(sin(dot(co, vec3(12.9898,58.233, 12.9898+58.233))) * 13758.5453); +} + +float starn(vec2 p, float r, int n, float m) { + // From IQ: https://www.shadertoy.com/view/3tSGDy + // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + + // Minor tweak to use SABS over abs to smooth inner corners + // SABS: https://www.shadertoy.com/view/Ws2SDK + + // next 4 lines can be precomputed for a given shape + float an = 3.141593/float(n); + float en = 3.141593/m; // m is between 2 and n + vec2 acs = vec2(cos(an),sin(an)); + vec2 ecs = vec2(cos(en),sin(en)); // ecs=vec2(0,1) for regular polygon, + + float bn = mod(atan(p.x,p.y),2.0*an) - an; + p = length(p)*vec2(cos(bn),SABS(sin(bn), 0.15)); + p -= r*acs; + p += ecs*clamp( -dot(p,ecs), 0.0, r*acs.y/ecs.y); + return length(p)*sign(p.x); +} + +vec4 alphaBlend(vec4 back, vec4 front) { + vec3 xyz = mix(back.xyz*back.w, front.xyz, front.w); + float w = mix(back.w, 1.0, front.w); + return vec4(xyz, w); +} + +void rot(inout vec2 p, float a) { + float c = cos(a); + float s = sin(a); + p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y); +} + +vec3 offset(float z) { + float a = z; + vec2 p = -0.075*(vec2(cos(a), sin(a*sqrt(2.0))) + vec2(cos(a*sqrt(0.75)), sin(a*sqrt(0.5)))); + return vec3(p, z); +} + +vec3 doffset(float z) { + float eps = 0.05; + return 0.5*(offset(z + eps) - offset(z - eps))/eps; +} + +vec3 ddoffset(float z) { + float eps = 0.05; + return 0.5*(doffset(z + eps) - doffset(z - eps))/eps; +} + +vec4 planeCol(vec3 ro, vec3 rd, float n, vec3 pp) { + const float s = 0.5; + + vec2 p = pp.xy; + float z = pp.z; + vec2 dpy = dFdy(p); + float aa = length(dpy); + + p -= (1.0+5.0*(pp.z - ro.z))*offset(z).xy; + + p *= s; + float r = hash(vec3(floor(p+0.5), n)); + p = fract(p+0.5)-0.5; + rot(p, ((TAU*r+n)*0.25)); + float d = starn(p, 0.20, 3 + 2*int(3.0*r), 3.0); + d -= 0.06; + d/=s; + + float ds = -d+0.03; + vec3 cols = hsv2rgb(vec3(337.0/360.0+0.1*sin(n*0.3), 0.8, 0.54+0.2*sin(n*0.3))); + float ts = 1.0 - smoothstep(-aa, 0.0, ds); + vec4 cs = vec4(cols, ts*0.93); + + float db = abs(d) - (0.06); + db = abs(db) - 0.03; + db = abs(db) - 0.00; + db = max(db, -d+0.03); + vec3 colb = vec3(1.0, 0.7, 0.5); + float tb = exp(-(db)*30.0*(1.0 - 10.0*aa)); + vec4 cb = vec4(1.5*colb, tb); + + vec4 ct = alphaBlend(cs, cb); + + return ct; +} + +vec3 color(vec3 ww, vec3 uu, vec3 vv, vec3 ro, vec2 p) { + vec3 rd = normalize(p.x*uu + p.y*vv + (2.0-tanh(length(p)))*ww); + + vec4 col = vec4(vec3(0.0), 1.0); + + const float planeDist = 1.0; + const int furthest = 6; + const int fadeFrom = furthest-3; + + float nz = floor(ro.z / planeDist); + + for (int i = furthest; i >= 1; --i) { + float pz = planeDist*nz + planeDist*float(i); + + float pd = (pz - ro.z)/rd.z; + + if (pd > 0.0) { + vec3 pp = ro + rd*pd; + + vec4 pcol = planeCol(ro, rd, nz+float(i), pp); + float fadeIn = 1.0-smoothstep(planeDist*float(fadeFrom), planeDist*float(furthest), pp.z-ro.z); + pcol.xyz *= sqrt(fadeIn); + + col = alphaBlend(col, pcol); + } + } + + return col.xyz*col.w; +} + +vec3 postProcess(vec3 col, vec2 q) { + col=pow(clamp(col,0.0,1.0),vec3(0.75)); + col=col*0.6+0.4*col*col*(3.0-2.0*col); + col=mix(col, vec3(dot(col, vec3(0.33))), -0.4); + col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7); + return col; +} + +vec3 effect(vec2 p, vec2 q) { + float tm = TIME*0.65; + + vec3 ro = offset(tm); + vec3 dro = doffset(tm); + vec3 ddro = ddoffset(tm); + + vec3 ww = normalize(dro); + vec3 uu = normalize(cross(vec3(0.0,1.0,0.0)+1.5*ddro, ww)); + vec3 vv = normalize(cross(ww, uu)); + + vec3 col = color(ww, uu, vv, ro, p); + col = postProcess(col, q); + + const float fadeIn = 2.0; + + return col*smoothstep(0.0, fadeIn, TIME); +} + +void mainImage(out vec4 fragColor, vec2 fragCoord) { + vec2 q = fragCoord/RESOLUTION.xy; + vec2 p = -1. + 2. * q; + p.x *= RESOLUTION.x/RESOLUTION.y; + + vec3 col = effect(p, q); + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/gtkshadertoy.c b/demos/gtk-demo/gtkshadertoy.c new file mode 100644 index 0000000000..866bd69481 --- /dev/null +++ b/demos/gtk-demo/gtkshadertoy.c @@ -0,0 +1,524 @@ +#include +#include +#include +#include + +#include "gtkshadertoy.h" + +const char *default_image_shader = + "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n" + " // Normalized pixel coordinates (from 0 to 1)\n" + " vec2 uv = fragCoord/iResolution.xy;\n" + "\n" + " // Time varying pixel color\n" + " vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));\n" + "\n" + " if (distance(iMouse.xy, fragCoord.xy) <= 10.0) {\n" + " col = vec3(0.0);\n" + " }\n" + "\n" + " // Output to screen\n" + " fragColor = vec4(col,1.0);\n" + "}\n"; + +const char *shadertoy_vertex_shader = + "#version 150 core\n" + "\n" + "uniform vec3 iResolution;\n" + "\n" + "in vec2 position;\n" + "out vec2 fragCoord;\n" + "\n" + "void main() {\n" + " gl_Position = vec4(position, 0.0, 1.0);\n" + "\n" + " // Convert from OpenGL coordinate system (with origin in center\n" + " // of screen) to Shadertoy/texture coordinate system (with origin\n" + " // in lower left corner)\n" + " fragCoord = (gl_Position.xy + vec2(1.0)) / vec2(2.0) * iResolution.xy;\n" + "}\n"; + +const char *fragment_prefix = + "#version 150 core\n" + "\n" + "uniform vec3 iResolution; // viewport resolution (in pixels)\n" + "uniform float iTime; // shader playback time (in seconds)\n" + "uniform float iTimeDelta; // render time (in seconds)\n" + "uniform int iFrame; // shader playback frame\n" + "uniform float iChannelTime[4]; // channel playback time (in seconds)\n" + "uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)\n" + "uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click\n" + "uniform sampler2D iChannel0;\n" + "uniform sampler2D iChannel1;\n" + "uniform sampler2D iChannel2;\n" + "uniform sampler2D iChannel3;\n" + "uniform vec4 iDate; // (year, month, day, time in seconds)\n" + "uniform float iSampleRate; // sound sample rate (i.e., 44100)\n" + "\n" + "in vec2 fragCoord;\n" + "out vec4 fragColor;\n"; + + +// Fragment shader suffix +const char *fragment_suffix = + " void main() {\n" + " mainImage(fragColor, fragCoord);\n" + " }\n"; + +typedef struct { + char *image_shader; + gboolean image_shader_dirty; + + gboolean error_set; + + /* Vertex buffers */ + GLuint vao; + GLuint buffer; + + /* Active program */ + GLuint program; + + /* Location of uniforms for program */ + GLuint resolution_location; + GLuint time_location; + GLuint timedelta_location; + GLuint frame_location; + GLuint mouse_location; + + /* Current uniform values */ + float resolution[3]; + float time; + float timedelta; + float mouse[4]; + int frame; + + /* Animation data */ + gint64 first_frame_time; + gint64 first_frame; + guint tick; +} GtkShadertoyPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_GL_AREA) + +static gboolean gtk_shadertoy_render (GtkGLArea *area, + GdkGLContext *context); +static void gtk_shadertoy_reshape (GtkGLArea *area, + int width, + int height); +static void gtk_shadertoy_realize (GtkWidget *widget); +static void gtk_shadertoy_unrealize (GtkWidget *widget); +static gboolean gtk_shadertoy_tick (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer user_data); + +GtkWidget * +gtk_shadertoy_new (void) +{ + return g_object_new (gtk_shadertoy_get_type (), NULL); +} + +static void +drag_begin_cb (GtkGestureDrag *drag, + double x, + double y, + gpointer user_data) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + int height = gtk_widget_get_height (GTK_WIDGET (shadertoy)); + int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy)); + + priv->mouse[0] = x * scale; + priv->mouse[1] = (height - y) * scale; + priv->mouse[2] = priv->mouse[0]; + priv->mouse[3] = priv->mouse[1]; +} + +static void +drag_update_cb (GtkGestureDrag *drag, + double dx, + double dy, + gpointer user_data) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + int width = gtk_widget_get_width (GTK_WIDGET (shadertoy)); + int height = gtk_widget_get_height (GTK_WIDGET (shadertoy)); + int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy)); + double x, y; + + gtk_gesture_drag_get_start_point (drag, &x, &y); + x += dx; + y += dy; + + if (x >= 0 && x < width && + y >= 0 && y < height) + { + priv->mouse[0] = x * scale; + priv->mouse[1] = (height - y) * scale; + } +} + +static void +drag_end_cb (GtkGestureDrag *drag, + gdouble dx, + gdouble dy, + gpointer user_data) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + priv->mouse[2] = -priv->mouse[2]; + priv->mouse[3] = -priv->mouse[3]; +} + +static void +gtk_shadertoy_init (GtkShadertoy *shadertoy) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + GtkGesture *drag; + + priv->image_shader = g_strdup (default_image_shader); + priv->tick = gtk_widget_add_tick_callback (GTK_WIDGET (shadertoy), gtk_shadertoy_tick, shadertoy, NULL); + + drag = gtk_gesture_drag_new (); + gtk_widget_add_controller (GTK_WIDGET (shadertoy), GTK_EVENT_CONTROLLER (drag)); + g_signal_connect (drag, "drag-begin", (GCallback)drag_begin_cb, shadertoy); + g_signal_connect (drag, "drag-update", (GCallback)drag_update_cb, shadertoy); + g_signal_connect (drag, "drag-end", (GCallback)drag_end_cb, shadertoy); +} + +static void +gtk_shadertoy_finalize (GObject *obj) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (obj); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + gtk_widget_remove_tick_callback (GTK_WIDGET (shadertoy), priv->tick); + g_free (priv->image_shader); + + G_OBJECT_CLASS (gtk_shadertoy_parent_class)->finalize (obj); +} + +static void +gtk_shadertoy_class_init (GtkShadertoyClass *klass) +{ + GTK_GL_AREA_CLASS (klass)->render = gtk_shadertoy_render; + GTK_GL_AREA_CLASS (klass)->resize = gtk_shadertoy_reshape; + + GTK_WIDGET_CLASS (klass)->realize = gtk_shadertoy_realize; + GTK_WIDGET_CLASS (klass)->unrealize = gtk_shadertoy_unrealize; + + G_OBJECT_CLASS (klass)->finalize = gtk_shadertoy_finalize; +} + +/* new window size or exposure */ +static void +gtk_shadertoy_reshape (GtkGLArea *area, int width, int height) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) area); + + priv->resolution[0] = width; + priv->resolution[1] = height; + priv->resolution[2] = 1.0; /* screen aspect ratio */ + + /* Set the viewport */ + glViewport (0, 0, (GLint) width, (GLint) height); +} + +static GLuint +create_shader (int type, + const char *src, + GError **error) +{ + GLuint shader; + int status; + + shader = glCreateShader (type); + glShaderSource (shader, 1, &src, NULL); + glCompileShader (shader); + + glGetShaderiv (shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) + { + int log_len; + char *buffer; + + glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc (log_len + 1); + glGetShaderInfoLog (shader, log_len, NULL, buffer); + + g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_COMPILATION_FAILED, + "Compile failure in %s shader:\n%s", + type == GL_VERTEX_SHADER ? "vertex" : "fragment", + buffer); + + g_free (buffer); + + glDeleteShader (shader); + + return 0; + } + + return shader; +} + +static gboolean +init_shaders (GtkShadertoy *shadertoy, + const char *vertex_source, + const char *fragment_source, + GError **error) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + GLuint vertex, fragment; + GLuint program = 0; + int status; + gboolean res = TRUE; + + vertex = create_shader (GL_VERTEX_SHADER, vertex_source, error); + if (vertex == 0) + return FALSE; + + fragment = create_shader (GL_FRAGMENT_SHADER, fragment_source, error); + if (fragment == 0) + { + glDeleteShader (vertex); + return FALSE; + } + + program = glCreateProgram (); + glAttachShader (program, vertex); + glAttachShader (program, fragment); + + glLinkProgram (program); + + glGetProgramiv (program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + int log_len; + char *buffer; + + glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc (log_len + 1); + glGetProgramInfoLog (program, log_len, NULL, buffer); + + g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_LINK_FAILED, + "Linking failure:\n%s", buffer); + res = FALSE; + + g_free (buffer); + + glDeleteProgram (program); + + goto out; + } + + if (priv->program != 0) + glDeleteProgram (priv->program); + + priv->program = program; + priv->resolution_location = glGetUniformLocation (program, "iResolution"); + priv->time_location = glGetUniformLocation (program, "iTime"); + priv->timedelta_location = glGetUniformLocation (program, "iTimeDelta"); + priv->frame_location = glGetUniformLocation (program, "iFrame"); + priv->mouse_location = glGetUniformLocation (program, "iMouse"); + + glDetachShader (program, vertex); + glDetachShader (program, fragment); + +out: + /* These are now owned by the program and can be deleted */ + glDeleteShader (vertex); + glDeleteShader (fragment); + + return res; +} + +static void +gtk_shadertoy_realize_shader (GtkShadertoy *shadertoy) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + char *fragment_shader; + GError *error = NULL; + + fragment_shader = g_strconcat (fragment_prefix, priv->image_shader, fragment_suffix, NULL); + if (!init_shaders (shadertoy, shadertoy_vertex_shader, fragment_shader, &error)) + { + priv->error_set = TRUE; + gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), error); + g_error_free (error); + } + g_free (fragment_shader); + + /* Start new shader at time zero */ + priv->first_frame_time = 0; + priv->first_frame = 0; + + priv->image_shader_dirty = FALSE; +} + +static gboolean +gtk_shadertoy_render (GtkGLArea *area, + GdkGLContext *context) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (area); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + if (gtk_gl_area_get_error (area) != NULL) + return FALSE; + + if (priv->image_shader_dirty) + gtk_shadertoy_realize_shader (shadertoy); + + /* Clear the viewport */ + glClearColor (0.0, 0.0, 0.0, 1.0); + glClear (GL_COLOR_BUFFER_BIT); + + glUseProgram (priv->program); + + /* Update uniforms */ + if (priv->resolution_location != -1) + glUniform3fv (priv->resolution_location, 1, priv->resolution); + if (priv->time_location != -1) + glUniform1f (priv->time_location, priv->time); + if (priv->timedelta_location != -1) + glUniform1f (priv->timedelta_location, priv->timedelta); + if (priv->frame_location != -1) + glUniform1i (priv->frame_location, priv->frame); + if (priv->mouse_location != -1) + glUniform4fv (priv->mouse_location, 1, priv->mouse); + + /* Use the vertices in our buffer */ + glBindBuffer (GL_ARRAY_BUFFER, priv->buffer); + glEnableVertexAttribArray (0); + glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0); + + glDrawArrays (GL_TRIANGLES, 0, 6); + + /* We finished using the buffers and program */ + glDisableVertexAttribArray (0); + glBindBuffer (GL_ARRAY_BUFFER, 0); + glUseProgram (0); + + /* Flush the contents of the pipeline */ + glFlush (); + + return TRUE; +} + +const char * +gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + return priv->image_shader; +} + +void +gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy, + const char *shader) +{ + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + g_free (priv->image_shader); + priv->image_shader = g_strdup (shader); + + /* Don't override error we didn't set it ourselves */ + if (priv->error_set) + { + gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), NULL); + priv->error_set = FALSE; + } + priv->image_shader_dirty = TRUE; +} + +static void +gtk_shadertoy_realize (GtkWidget *widget) +{ + GtkGLArea *glarea = GTK_GL_AREA (widget); + GtkShadertoy *shadertoy = GTK_SHADERTOY (widget); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + + /* Draw two triangles across whole screen */ + const GLfloat vertex_data[] = { + -1.0f, -1.0f, 0.f, 1.f, + -1.0f, 1.0f, 0.f, 1.f, + 1.0f, 1.0f, 0.f, 1.f, + + -1.0f, -1.0f, 0.f, 1.f, + 1.0f, 1.0f, 0.f, 1.f, + 1.0f, -1.0f, 0.f, 1.f, + }; + + GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->realize (widget); + + gtk_gl_area_make_current (glarea); + if (gtk_gl_area_get_error (glarea) != NULL) + return; + + glGenVertexArrays (1, &priv->vao); + glBindVertexArray (priv->vao); + + glGenBuffers (1, &priv->buffer); + glBindBuffer (GL_ARRAY_BUFFER, priv->buffer); + glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW); + glBindBuffer (GL_ARRAY_BUFFER, 0); + + gtk_shadertoy_realize_shader (shadertoy); +} + +static void +gtk_shadertoy_unrealize (GtkWidget *widget) +{ + GtkGLArea *glarea = GTK_GL_AREA (widget); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) widget); + + gtk_gl_area_make_current (glarea); + if (gtk_gl_area_get_error (glarea) == NULL) + { + if (priv->buffer != 0) + glDeleteBuffers (1, &priv->buffer); + + if (priv->vao != 0) + glDeleteVertexArrays (1, &priv->vao); + + if (priv->program != 0) + glDeleteProgram (priv->program); + } + + GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->unrealize (widget); +} + +static gboolean +gtk_shadertoy_tick (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer user_data) +{ + GtkShadertoy *shadertoy = GTK_SHADERTOY (widget); + GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy); + gint64 frame_time; + gint64 frame; + float previous_time; + + frame = gdk_frame_clock_get_frame_counter (frame_clock); + frame_time = gdk_frame_clock_get_frame_time (frame_clock); + + if (priv->first_frame_time == 0) + { + priv->first_frame_time = frame_time; + priv->first_frame = frame; + previous_time = 0; + } + else + previous_time = priv->time; + + priv->time = (frame_time - priv->first_frame_time) / 1000000.0f; + priv->frame = frame - priv->first_frame; + priv->timedelta = priv->time - previous_time; + + gtk_widget_queue_draw (widget); + + return G_SOURCE_CONTINUE; +} diff --git a/demos/gtk-demo/gtkshadertoy.h b/demos/gtk-demo/gtkshadertoy.h new file mode 100644 index 0000000000..38f48995b8 --- /dev/null +++ b/demos/gtk-demo/gtkshadertoy.h @@ -0,0 +1,34 @@ +#ifndef __GTK_SHADERTOY_H__ +#define __GTK_SHADERTOY_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SHADERTOY (gtk_shadertoy_get_type ()) +#define GTK_SHADERTOY(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_SHADERTOY, \ + GtkShadertoy)) +#define GTK_IS_SHADERTOY(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_SHADERTOY)) + +typedef struct _GtkShadertoy GtkShadertoy; +typedef struct _GtkShadertoyClass GtkShadertoyClass; + +struct _GtkShadertoy { + GtkGLArea parent; +}; + +struct _GtkShadertoyClass { + GtkGLAreaClass parent_class; +}; + +GType gtk_shadertoy_get_type (void) G_GNUC_CONST; +GtkWidget *gtk_shadertoy_new (void); +const char *gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy); +void gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy, + const char *shader); + +G_END_DECLS + +#endif /* __GTK_SHADERTOY_H__ */ diff --git a/demos/gtk-demo/mandelbrot.glsl b/demos/gtk-demo/mandelbrot.glsl new file mode 100644 index 0000000000..0a0c1ee3fd --- /dev/null +++ b/demos/gtk-demo/mandelbrot.glsl @@ -0,0 +1,95 @@ +// Originally from: https://www.shadertoy.com/view/wdBfDK +// License: CC0 + +#define MANDELBROT_ZOOM_START 0.0 +#define MANDELBROT_ITER 240 + +void pR(inout vec2 p, in float a) { + p = cos(a)*p + sin(a)*vec2(p.y, -p.x); +} + +vec2 pMod2(inout vec2 p, in vec2 size) { + vec2 c = floor((p + size*0.5)/size); + p = mod(p + size*0.5,size) - size*0.5; + return c; +} + + +vec3 mandelbrot(float time, vec2 p, out float ii) { + vec3 col = vec3(0.0); + + float ztime = (time - MANDELBROT_ZOOM_START)*step(MANDELBROT_ZOOM_START, time); + + float zoo = 0.64 + 0.36*cos(.07*ztime); + float coa = cos(0.15*(1.0-zoo)*ztime); + float sia = sin(0.15*(1.0-zoo)*ztime); + zoo = pow(zoo,8.0); + vec2 xy = vec2( p.x*coa-p.y*sia, p.x*sia+p.y*coa); + vec2 c = vec2(-.745,.186) + xy*zoo; + + const float B = 10.0; + float l = 0.0; + vec2 z = vec2(0.0); + + vec2 zc = vec2(1.0); + + pR(zc, ztime); + + float d = 1e20; + + int i = 0; + + for(int j = 0; j < MANDELBROT_ITER; ++j) { + float re2 = z.x*z.x; + float im2 = z.y*z.y; + float reim= z.x*z.y; + + if(re2 + im2 > (B*B)) break; + + z = vec2(re2 - im2, 2.0*reim) + c; + + vec2 zm = z; + vec2 n = pMod2(zm, vec2(4)); + vec2 pp = zm - zc; + float dd = dot(pp, pp); + + d = min(d, dd); + + l += 1.0; + + i = j; + } + + ii = float(i)/float(MANDELBROT_ITER); + + float sl = l - log2(log2(dot(z,z))) + 4.0; + + vec3 dc = vec3(pow(max(1.0 - d, 0.0), 20.0)); + vec3 gc = 0.5 + 0.5*cos(3.0 + sl*0.15 + vec3(0.1,0.5,0.9)); + return gc + dc*smoothstep(28.8, 29.0, ztime); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + float s = 2.0/iResolution.y; + + vec2 o1 = vec2(1.0/8.0, 3.0/8.0)*s; + vec2 o2 = vec2(-3.0/8.0, 1.0/8.0)*s; + + vec2 p = (-iResolution.xy + 2.0*fragCoord.xy)/iResolution.y; + float ii = 0.0; + vec3 col = mandelbrot(iTime, p+o1, ii); + + // "smart" AA? Is that a good idea? + vec2 dii2 = vec2(dFdx(ii), dFdy(ii)); + float dii = length(dii2); + + if(abs(dii) > 0.01) { + col += mandelbrot(iTime, p-o1, ii); + col += mandelbrot(iTime, p+o2, ii); + col += mandelbrot(iTime, p-o2, ii); + col *=0.25; +// col = vec3(1.0, 0.0, 0.0); + } + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 05b32c6f46..9ee9777d13 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -70,6 +70,7 @@ demos = files([ 'scale.c', 'search_entry.c', 'search_entry2.c', + 'shadertoy.c', 'shortcuts.c', 'shortcut_triggers.c', 'sidebar.c', @@ -98,6 +99,7 @@ extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', + 'gtkshadertoy.c', 'puzzlepiece.c', 'bluroverlay.c', 'demoimage.c', diff --git a/demos/gtk-demo/neon.glsl b/demos/gtk-demo/neon.glsl new file mode 100644 index 0000000000..b42790efe9 --- /dev/null +++ b/demos/gtk-demo/neon.glsl @@ -0,0 +1,220 @@ +// Originally from: https://www.shadertoy.com/view/WlByzy +// License CC0: Neonwave style road, sun and city +// The result of a bit of experimenting with neonwave style colors. + +#define PI 3.141592654 +#define TAU (2.0*PI) + +#define TIME iTime +#define RESOLUTION iResolution + +vec3 hsv2rgb(vec3 c) { + const vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float hash(in float co) { + return fract(sin(co*12.9898) * 13758.5453); +} + +float hash(in vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,58.233))) * 13758.5453); +} + +float psin(float a) { + return 0.5 + 0.5*sin(a); +} + +float mod1(inout float p, float size) { + float halfsize = size*0.5; + float c = floor((p + halfsize)/size); + p = mod(p + halfsize, size) - halfsize; + return c; +} + +float circle(vec2 p, float r) { + return length(p) - r; +} + +float box(vec2 p, vec2 b) { + vec2 d = abs(p)-b; + return length(max(d,0.0)) + min(max(d.x,d.y),0.0); +} + +float planex(vec2 p, float w) { + return abs(p.y) - w; +} + +float planey(vec2 p, float w) { + return abs(p.x) - w; +} + +float pmin(float a, float b, float k) { + float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 ); + return mix( b, a, h ) - k*h*(1.0-h); +} + +float pmax(float a, float b, float k) { + return -pmin(-a, -b, k); +} + +float sun(vec2 p) { + const float ch = 0.0125; + vec2 sp = p; + vec2 cp = p; + mod1(cp.y, ch*6.0); + + float d0 = circle(sp, 0.5); + float d1 = planex(cp, ch); + float d2 = p.y+ch*3.0; + + float d = d0; + d = pmax(d, -max(d1, d2), ch*2.0); + + return d; +} + +float city(vec2 p) { + float sd = circle(p, 0.5); + float cd = 1E6; + + const float count = 5.0; + const float width = 0.1; + + for (float i = 0.0; i < count; ++i) { + vec2 pp = p; + pp.x += i*width/count; + float nn = mod1(pp.x, width); + float rr = hash(nn+sqrt(3.0)*i); + float dd = box(pp-vec2(0.0, -0.5), vec2(0.02, 0.35*(1.0-smoothstep(0.0, 5.0, abs(nn)))*rr+0.1)); + cd = min(cd, dd); + } + + return max(sd,cd); +} +vec3 sunEffect(vec2 p) { + float aa = 4.0 / RESOLUTION.y; + + vec3 col = vec3(0.1); + vec3 skyCol1 = hsv2rgb(vec3(283.0/360.0, 0.83, 0.16)); + vec3 skyCol2 = hsv2rgb(vec3(297.0/360.0, 0.79, 0.43)); + col = mix(skyCol1, skyCol2, pow(clamp(0.5*(1.0+p.y+0.1*sin(4.0*p.x+TIME*0.5)), 0.0, 1.0), 4.0)); + + p.y -= 0.375; + float ds = sun(p); + float dc = city(p); + + float dd = circle(p, 0.5); + + vec3 sunCol = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 1.0), clamp(0.5 - 1.0*p.y, 0.0, 1.0)); + vec3 glareCol = sqrt(sunCol); + vec3 cityCol = sunCol*sunCol; + + col += glareCol*(exp(-30.0*ds))*step(0.0, ds); + + + float t1 = smoothstep(0.0, 0.075, -dd); + float t2 = smoothstep(0.0, 0.3, -dd); + col = mix(col, sunCol, smoothstep(-aa, 0.0, -ds)); + col = mix(col, glareCol, smoothstep(-aa, 0.0, -dc)*t1); + col += vec3(0.0, 0.25, 0.0)*(exp(-90.0*dc))*step(0.0, dc)*t2; + +// col += 0.3*psin(d*400); + + return col; +} + +float ground(vec2 p) { + p.y += TIME*80.0; + p *= 0.075; + vec2 gp = p; + gp = fract(gp) - vec2(0.5); + float d0 = abs(gp.x); + float d1 = abs(gp.y); + float d2 = circle(gp, 0.05); + + const float rw = 2.5; + const float sw = 0.0125; + + vec2 rp = p; + mod1(rp.y, 12.0); + float d3 = abs(rp.x) - rw; + float d4 = abs(d3) - sw*2.0; + float d5 = box(rp, vec2(sw*2.0, 2.0)); + vec2 sp = p; + mod1(sp.y, 4.0); + sp.x = abs(sp.x); + sp -= vec2(rw - 0.125, 0.0); + float d6 = box(sp, vec2(sw, 1.0)); + + float d = d0; + d = pmin(d, d1, 0.1); + d = max(d, -d3); + d = min(d, d4); + d = min(d, d5); + d = min(d, d6); + + return d; +} + +vec3 groundEffect(vec2 p) { + vec3 ro = vec3(0.0, 20.0, 0.0); + vec3 ww = normalize(vec3(0.0, -0.025, 1.0)); + vec3 uu = normalize(cross(vec3(0.0,1.0,0.0), ww)); + vec3 vv = normalize(cross(ww,uu)); + vec3 rd = normalize(p.x*uu + p.y*vv + 2.5*ww); + + float distg = (-9.0 - ro.y)/rd.y; + + const vec3 shineCol = 0.75*vec3(0.5, 0.75, 1.0); + const vec3 gridCol = vec3(1.0); + + vec3 col = vec3(0.0); + if (distg > 0.0) { + vec3 pg = ro + rd*distg; + float aa = length(dFdx(pg))*0.0002*RESOLUTION.x; + + float dg = ground(pg.xz); + + col = mix(col, gridCol, smoothstep(-aa, 0.0, -(dg+0.0175))); + col += shineCol*(exp(-10.0*clamp(dg, 0.0, 1.0))); + col = clamp(col, 0.0, 1.0); + +// col += 0.3*psin(dg*100); + col *= pow(1.0-smoothstep(ro.y*3.0, 220.0+ro.y*2.0, distg), 2.0); + } + + return col; +} + +vec3 postProcess(vec3 col, vec2 q) { + col = clamp(col,0.0,1.0); +// col=pow(col,vec3(0.75)); + col=col*0.6+0.4*col*col*(3.0-2.0*col); + col=mix(col, vec3(dot(col, vec3(0.33))), -0.4); + col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7); + return col; +} + +vec3 effect(vec2 p, vec2 q) { + vec3 col = vec3(0.0); + + vec2 off = vec2(0.0, 0.0); + + col += sunEffect(p+off); + col += groundEffect(p+off); + + col = postProcess(col, q); + return col; +} + +void mainImage(out vec4 fragColor, vec2 fragCoord) { + vec2 q = fragCoord/iResolution.xy; + vec2 p = -1. + 2. * q; + p.x *= RESOLUTION.x / RESOLUTION.y; + + vec3 col = effect(p, q); + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/shadertoy.c b/demos/gtk-demo/shadertoy.c new file mode 100644 index 0000000000..f6cad85ad3 --- /dev/null +++ b/demos/gtk-demo/shadertoy.c @@ -0,0 +1,191 @@ +/* OpenGL/Shadertoy + * + * Generate pixels using a custom fragment shader. + * + * The names of the uniforms are compatible with the shaders on shadertoy.com, so + * many of the shaders there work here too. + */ +#include +#include +#include +#include "gtkshadertoy.h" + +static GtkWidget *demo_window = NULL; +static GtkWidget *shadertoy = NULL; +static GtkTextBuffer *textbuffer = NULL; + +static void +run (void) +{ + GtkTextIter start, end; + char *text; + + gtk_text_buffer_get_bounds (textbuffer, &start, &end); + text = gtk_text_buffer_get_text (textbuffer, &start, &end, FALSE); + + gtk_shadertoy_set_image_shader (GTK_SHADERTOY (shadertoy), text); + g_free (text); +} + +static void +run_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + run (); +} + +static void +load_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + const char *path = user_data; + GBytes *initial_shader; + + initial_shader = g_resources_lookup_data (path, 0, NULL); + gtk_text_buffer_set_text (textbuffer, g_bytes_get_data (initial_shader, NULL), -1); + g_bytes_unref (initial_shader); + + run (); +} + +static void +clear_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + gtk_text_buffer_set_text (textbuffer, "", 0); +} + +static void +close_window (GtkWidget *widget) +{ + /* Reset the state */ + demo_window = NULL; + shadertoy = NULL; + textbuffer = NULL; +} + +static GtkWidget * +new_shadertoy (const char *path) +{ + GBytes *shader; + GtkWidget *toy; + + toy = gtk_shadertoy_new (); + shader = g_resources_lookup_data (path, 0, NULL); + gtk_shadertoy_set_image_shader (GTK_SHADERTOY (toy), + g_bytes_get_data (shader, NULL)); + g_bytes_unref (shader); + + return toy; +} + +static GtkWidget * +new_button (const char *path) +{ + GtkWidget *button, *toy; + + button = gtk_button_new (); + g_signal_connect (button, "clicked", G_CALLBACK (load_clicked_cb), (char *)path); + + toy = new_shadertoy (path); + gtk_widget_set_size_request (toy, 64, 36); + gtk_button_set_child (GTK_BUTTON (button), toy); + + return button; +} + +static GtkWidget * +create_shadertoy_window (GtkWidget *do_widget) +{ + GtkWidget *window, *box, *hbox, *button, *textview, *sw, *aspect, *centerbox; + + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Shadertoy"); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 600); + g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE); + gtk_widget_set_margin_start (box, 12); + gtk_widget_set_margin_end (box, 12); + gtk_widget_set_margin_top (box, 12); + gtk_widget_set_margin_bottom (box, 12); + gtk_box_set_spacing (GTK_BOX (box), 6); + gtk_window_set_child (GTK_WINDOW (window), box); + + aspect = gtk_aspect_frame_new (0.5, 0.5, 1.77777, FALSE); + gtk_widget_set_hexpand (aspect, TRUE); + gtk_widget_set_vexpand (aspect, TRUE); + gtk_box_append (GTK_BOX (box), aspect); + + shadertoy = new_shadertoy ("/shadertoy/alienplanet.glsl"); + gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (aspect), shadertoy); + + sw = gtk_scrolled_window_new (); + gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw), 250); + gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (sw), TRUE); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_set_hexpand (sw, TRUE); + gtk_box_append (GTK_BOX (box), sw); + + textview = gtk_text_view_new (); + gtk_text_view_set_monospace (GTK_TEXT_VIEW (textview), TRUE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), textview); + + textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); + gtk_text_buffer_set_text (textbuffer, + gtk_shadertoy_get_image_shader (GTK_SHADERTOY (shadertoy)), + -1); + + centerbox = gtk_center_box_new (); + gtk_box_append (GTK_BOX (box), centerbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE); + gtk_box_set_spacing (GTK_BOX (hbox), 6); + gtk_center_box_set_start_widget (GTK_CENTER_BOX (centerbox), hbox); + + button = gtk_button_new_from_icon_name ("media-playback-start-symbolic"); + g_signal_connect (button, "clicked", G_CALLBACK (run_clicked_cb), NULL); + gtk_box_append (GTK_BOX (hbox), button); + + button = gtk_button_new_from_icon_name ("edit-clear-all-symbolic"); + g_signal_connect (button, "clicked", G_CALLBACK (clear_clicked_cb), NULL); + gtk_box_append (GTK_BOX (hbox), button); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE); + gtk_box_set_spacing (GTK_BOX (hbox), 6); + gtk_center_box_set_end_widget (GTK_CENTER_BOX (centerbox), hbox); + + button = new_button ("/shadertoy/alienplanet.glsl"); + gtk_box_append (GTK_BOX (hbox), button); + + button = new_button ("/shadertoy/mandelbrot.glsl"); + gtk_box_append (GTK_BOX (hbox), button); + + button = new_button ("/shadertoy/neon.glsl"); + gtk_box_append (GTK_BOX (hbox), button); + + button = new_button ("/shadertoy/cogs.glsl"); + gtk_box_append (GTK_BOX (hbox), button); + + button = new_button ("/shadertoy/glowingstars.glsl"); + gtk_box_append (GTK_BOX (hbox), button); + + return window; +} + +GtkWidget * +do_shadertoy (GtkWidget *do_widget) +{ + if (!demo_window) + demo_window = create_shadertoy_window (do_widget); + + if (!gtk_widget_get_visible (demo_window)) + gtk_widget_show (demo_window); + else + gtk_window_destroy (GTK_WINDOW (demo_window)); + + return demo_window; +}