learn GLSL from zero
ToolsCreate with shadersShader MakerFree online shader builderToolkitShader works ready for your siteSketcher LabPaint your landing’s hero with shaders3D StudioThe Three.js editor in your browser
GamesPlay with shadersOnomatoShaderType a word, blow it upThe WatcherAn eye that follows youAetherLiquid glass with an eye insideBloomcoreA word grows a garden of lightPyrokineThrow fire at the wallVoltaicThe pyramid throws lightningSurfSurf a shader ocean

Lesson 8 of 10 · Ray marching in GLSL

Two shapes

smin(): two spheres melting into one.

Plain min() unions two shapes but leaves a hard crease where they meet. smin() (smooth minimum) fuses them like beads of mercury: in the contact zone it interpolates instead of cutting, and a soft neck appears.

Two spheres, one on each side, fused. This is the magic that makes raymarching feel organic, not built from Lego bricks.

smin(a, b, k)Like min, but k controls how much they melt. Small k = nearly separate; large k = one mass. The neck comes from a quadratic blend, no extra geometry: two spheres and a number.
PruebaRaise the 0.6 in smin(s1, s2, 0.6) and the drops fuse harder. The second slider slides the right sphere (p-vec3(0.6).

The code

precision highp float;
float smin(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 map(vec3 p){
  float s1=length(p-vec3(-0.7,0.0,0.0))-0.85;
  float s2=length(p-vec3(0.6,0.0,0.0))-0.85;
  float blob=smin(s1, s2, 0.6);
  float flo=p.y+1.2;
  return min(blob, flo);
}
vec3 nrm(vec3 p){
  vec2 e=vec2(0.001,0.0);
  return normalize(vec3(
    map(p+e.xyy)-map(p-e.xyy),
    map(p+e.yxy)-map(p-e.yxy),
    map(p+e.yyx)-map(p-e.yyx)));
}
void main(){
  vec2 uv=(gl_FragCoord.xy*2.0-R)/R.y;
  vec3 ro=vec3(0.0,0.0,3.2);
  vec3 rd=normalize(vec3(uv,-1.5));
  float t=0.0;
  float hit=0.0;
  for(int i=0;i<64;i++){
    vec3 pos=ro+rd*t;
    float d=map(pos);
    if(d<0.001){ hit=1.0; break; }
    t+=d;
    if(t>20.0) break;
  }
  vec3 pos=ro+rd*t;
  vec3 n=nrm(pos);
  vec3 lightDir=normalize(vec3(0.7, 0.9, 0.5));
  float diff=max(dot(n,lightDir),0.0);
  vec3 lit=vec3(0.55,0.65,0.95)*(diff*0.9+0.14);
  vec3 sky=mix(vec3(0.05,0.06,0.12), vec3(0.16,0.22,0.42), uv.y*0.5+0.5);
  float fog=1.0-exp(-t*0.08);
  vec3 col=mix(lit, sky, fog);
  col=mix(sky, col, hit);
  gl_FragColor=vec4(col,1.0);
}
close