Classic metaballs demo effect.
Log in to post a comment.
precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform float iAmplitude; // value=0.3, min=0.1, max=0.5, step=.001
uniform float iWhitePoint; // value=0.88, min=0.88, max=1.5, step=.001
uniform float iPhase; // value=2.8, min=0, max=7, step=.1
uniform float iPhaseSky; // value=2.0, min=0, max=7, step=.1
uniform float albedoMode; // value=0, min=0, max=1, step=1 (solid, plasma)
#define AA 2
#define MAX_DIST 1000.
#define EPSILON 0.01
#define PI 3.1415
#define m3 mat3(-0.7373, 0.4562, 0.4980, 0, -0.7373, 0.6754, 0.6754, 0.4980, 0.5437)
#define NUM_BALLS 10
const vec3 lightDir = normalize(vec3(5,2,10));
vec3 matCol, skyCol;
vec4 metaballs[NUM_BALLS];
//
// Hash functions by Dave Hoskins:
//
// https://www.shadertoy.com/view/4djSRW
//
float hash11(float p) {
p = fract(p * .1031);
p *= p + 33.33;
p *= p + p;
return fract(p);
}
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float hash13(vec3 p3) {
p3 = fract(p3 * .1031);
p3 += dot(p3, p3.zyx + 31.32);
return fract((p3.x + p3.y) * p3.z);
}
vec2 hash21(float p) {
vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx+p3.yz)*p3.zy);
}
vec2 hash23(vec3 p3) {
p3 = fract(p3 * vec3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx+33.33);
return fract((p3.xx+p3.yz)*p3.zy);
}
vec3 hash31(float p) {
vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx+33.33);
return fract((p3.xxy+p3.yzz)*p3.zyx);
}
//
// rotation matrices
//
mat2 rot2D(float a) {
return mat2(cos(a), sin(a), -sin(a), cos(a));
}
//
// shading
//
const float T2 = 7.5;
float filmic_reinhard_curve (float x) {
float q = (T2*T2 + 1.0)*x*x;
return q / (q + x + T2*T2);
}
vec3 filmic_reinhard(vec3 x) {
float w = filmic_reinhard_curve(iWhitePoint);
return vec3(
filmic_reinhard_curve(x.r),
filmic_reinhard_curve(x.g),
filmic_reinhard_curve(x.b)) / w;
}
//
// Background types
//
vec3 plasma(vec3 pos) {
vec3 p = vec3(pos + iTime * 0.05 + 1.);
float a = 1.;
vec3 n = vec3(0);
for (int i = 0; i <7; i++){
p = m3 * p;
vec3 s = sin(p.zxy / a) * a;
p += s * 2.;
n += s;
}
return n * 0.25 + 0.5;
}
//
// SDF framework by Inigo Quilez:
//
// https://www.shadertoy.com/view/Xds3zN
//
float sminCubic( float a, float b ) {
float h = max( 1.-abs(a-b), 0.0 );
return min( a, b ) - h*h*h*(1.0/6.0);
}
float sdMetaBalls( vec3 pos ) {
float dmin = MAX_DIST;
for (int i=0; i<NUM_BALLS; i++) {
float d = length(metaballs[i].xyz - pos) - metaballs[i].w;
dmin = sminCubic(dmin, d);
}
return dmin;
}
float map(in vec3 pos) {
float metaballs = sdMetaBalls(pos);
float l = length(pos.xz);
float fr = 2. / sqrt(iAmplitude);
float f = pos.y+1.35 + iAmplitude * sin((metaballs - 1.5 * iTime + l * smoothstep(.0, 0.5, l)) * fr ) * (1./(4. + l * l)) ;
return sminCubic(metaballs, f);
}
vec3 calcNormal(in vec3 pos) {
const vec2 e = vec2(1., -1.)*EPSILON;
return normalize( e.xyy*map(pos + e.xyy) +
e.yyx*map(pos + e.yyx) +
e.yxy*map(pos + e.yxy) +
e.xxx*map(pos + e.xxx));
}
vec3 bgCol(vec3 rd) {
return mix(skyCol, skyCol*.5, max(0., rd.y));
}
float intersect(in vec3 ro, in vec3 rd) {
float tmax = rd.y < 0. ? -(ro.y + (1.35 + iAmplitude))/rd.y : MAX_DIST;
float t = hash13(rd + iTime) * EPSILON;
bool hit = false;
for (int i=0; i<64; i++) {
float res = map(ro+rd*t);
if (abs(res) < max(EPSILON, .0025*t) || t > tmax) {
hit = true;
break;
}
t += res;
}
return hit ? t : tmax;
}
float calcSoftshadow(in vec3 ro, in vec3 rd, in float mint, in float tmax) {
float res = 1.0;
float t = mint;
float ph = 1e10;
for (int i=0; i<16; i++) {
float h = map(ro + rd*t);
float y = h*h/(2.0*ph);
float d = sqrt(h*h-y*y);
res = min(res, 10.0*d/max(0.0, t-y));
ph = h;
t += h;
if (res<0.0001 || t>tmax) break;
}
res = clamp(res, 0.0, 1.0);
return smoothstep(0., 1., res);// res*res*(3.0-2.0*res);
}
vec3 render(in vec3 ro, in vec3 rd) {
float t = intersect(ro, rd);
vec3 col;
if (t < MAX_DIST) {
vec3 p = ro + t * rd;
vec3 n = calcNormal(p);
vec3 ref = reflect(rd, n);
vec3 raf = refract(rd, n, 1.0/1.5);
vec3 albedo = albedoMode == 0. ? matCol : plasma(.05*p);
float fre = 0.03 + 0.97*pow( max(0.0, 1.0+dot(n, rd)), 5.0 );
float dif = max(0., dot(n, lightDir));
vec3 hal = normalize(lightDir-rd);
float sha = mix(calcSoftshadow(p, ref, EPSILON, 4.), 1., clamp(p.y, 0., 1.));
float spc = pow(clamp(dot(n, hal), 0., 1.), 32.) * dif;
// sub surface scattering
vec3 o = p;
float a = 0.;
for(float i = 0.1; i < 2.5; i += 0.2) {
o += i * raf;
a += min(0., map(o));
}
float sss = 70./max(0.1, -a);
col = albedo * (dif * 2. + .1 + pow(max(0., sss), .25));
// fake reflections
col = mix(col, sha * skyCol, fre);
col += 50. * spc * fre;
// fog
col = mix(col, bgCol(rd), clamp(1. - exp(-0.1*t) * 1.2, 0., 1.));
} else {
col = bgCol(rd);
}
float sun = 1.5 * pow(dot(rd, lightDir), 16.);
col += sun;
col = filmic_reinhard(.25 * col);
col = smoothstep(-0.025, 1.0, col);
return sqrt(col);
}
void main() {
for( int i=0; i<NUM_BALLS; i++ ) {
float h = float(i+1)/float(NUM_BALLS);
metaballs[i].xyz = vec3(1.2,1.5,1.2)*sin( 6.2831*hash31(h) + hash31(h*2.39996322972865332 )*iTime );
metaballs[i].y += (.1 + hash11(h*1.61803398875)) * iTime + h * 16.;
metaballs[i].y = mod(metaballs[i].y + 4., 16.) - 8.;
metaballs[i].w = .8 + 0.4*sin(6.28*hash11(-h));
}
matCol = .5 + 0.3*sin( iPhase + vec3(0.0,0.6,1.2) );
matCol *= matCol;
skyCol = 1.5 + 0.4*sin( iPhaseSky + vec3(0.0,1.0,2.0) );
skyCol *= skyCol;
vec3 tot = vec3(0);
vec2 q = (gl_FragCoord.xy * 2. - iResolution.xy) / iResolution.y;
for (int m=0; m<AA; m++) {
for (int n=0; n<AA; n++) {
vec2 o = vec2(float(m), float(n))*(2./float(AA)) - .5;
// rotate AA offset to reduce aliasing
o *= rot2D(0.463647609);
vec2 p = q + o/iResolution.y;
vec3 ro = vec3(0., 0.25, -7.);
vec3 rd = normalize(vec3(p.xy, 2.5));
vec3 col = render(ro, rd);
tot += col;
}
}
tot *= (1./float(AA*AA));
// noise
tot += vec3(hash12(gl_FragCoord.xy) * 0.02);
gl_FragColor = vec4(tot, 1.);
}