0.00
60.0 fps

Metaballs

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.);
}