3D Curl Noise

Controls

Drag to move camera, scroll to zoom.

Overview

This is a 3D version of curl visualisation using three.js.

The curl of a vector field can be thought of as describing a local rotation. In graphics, it can be used to render fluid-like flow as discussed in "Curl-Noise for Procedural Fluid Flow" by Bridson et al.

The original version was based on a post by Pete Werner. The initial implementation used a scalar noise field and set the velocity of a particle by sampling the separate components on three planes. This formulation was not accurate and led to plane artefacts where the particle movement was aligned in the (1, 1, 1) direction. The correct method is to use a 3D vector field (a noise function that takes as input a 3D point and outputs a 3D vector). The correct code is listed at the bottom of this page.

However, a cheaper formulation exists. As discussed in a post by atyuwen, "the divergence of the cross product of two gradient fields is always zero". The cheaper method is displayed by default and is shown in the listing below:

function computeCurl(x, y, z){

  var eps = 1e-4;

  //Find rate of change in X
  var n1 = noise.simplex3(x + eps, y, z); 
  var n2 = noise.simplex3(x - eps, y, z); 
  //Average to find approximate derivative
  var a = (n1 - n2)/(2 * eps);

  //Find rate of change in Y
  n1 = noise.simplex3(x, y + eps, z); 
  n2 = noise.simplex3(x, y - eps, z); 
  //Average to find approximate derivative
  var b = (n1 - n2)/(2 * eps);

  //Find rate of change in Z
  n1 = noise.simplex3(x, y, z + eps); 
  n2 = noise.simplex3(x, y, z - eps); 
  //Average to find approximate derivative
  var c = (n1 - n2)/(2 * eps);

  var noiseGrad0 = [a, b, c];

  // Offset position for second noise read
  x += 10000.5;
  y += 10000.5;
  z += 10000.5;

  //Find rate of change in X
  n1 = noise.simplex3(x + eps, y, z); 
  n2 = noise.simplex3(x - eps, y, z); 
  //Average to find approximate derivative
  a = (n1 - n2)/(2 * eps);

  //Find rate of change in Y
  n1 = noise.simplex3(x, y + eps, z); 
  n2 = noise.simplex3(x, y - eps, z); 
  //Average to find approximate derivative
  b = (n1 - n2)/(2 * eps);

  //Find rate of change in Z
  n1 = noise.simplex3(x, y, z + eps); 
  n2 = noise.simplex3(x, y, z - eps); 
  //Average to find approximate derivative
  c = (n1 - n2)/(2 * eps);

  var noiseGrad1 = [a, b, c];

  noiseGrad1 = normalize(noiseGrad1);
  noiseGrad1 = normalize(noiseGrad1);
  var curl = cross(noiseGrad0, noiseGrad1);

  return normalize(curl);
}

Corrected original code can be toggled by selecting oldMethod in the controls. Note that noise3D returns an array of 3 elements and this method involves 18 calls to a scalar noise function compared to the 12 call method above:

function computeCurl(x, y, z){

  var eps = 1e-4;
  var curl = [0, 0, 0];
  
  //Find rate of change in X
  var n1 = noise3D(x + eps, y, z); 
  var n2 = noise3D(x - eps, y, z);
  var dx = [n1[0] - n2[0], n1[1] - n2[1], n1[2] - n2[2]];

  //Find rate of change in Y
  n1 = noise3D(x, y + eps, z); 
  n2 = noise3D(x, y - eps, z);
  var dy = [n1[0] - n2[0], n1[1] - n2[1], n1[2] - n2[2]];

  //Find rate of change in Z
  n1 = noise3D(x, y, z + eps); 
  n2 = noise3D(x, y, z - eps);
  var dz = [n1[0] - n2[0], n1[1] - n2[1], n1[2] - n2[2]];

  curl[0] = (dy[2] - dz[1]) / (2.0*eps);
  curl[1] = (dz[0] - dx[2]) / (2.0*eps);
  curl[2] = (dx[1] - dy[0]) / (2.0*eps);

  return normalize(curl);
}