Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I also could not found a solution on the web today, so I tried to derive it.</p> <p>Firstly the notations of a 3D Perlin noise is defined.</p> <h1>Notation</h1> <p>Assume the 3D Perlin noise is computed by the trilinear interpolation as</p> <pre><code>n = Lerp( Lerp( Lerp(dot000, dot100, u), Lerp(dot010, dot110, u), v), Lerp( Lerp(dot001, dot101, u), Lerp(dot011, dot111, u), v), w) </code></pre> <p>where <code>u</code>, <code>v</code>, <code>w</code> are the interpolation factors by the quintic polynomial of fraction coordinates (i.e., improved Perlin noise):</p> <pre><code>x0 = frac(x) y0 = frac(y) z0 = frac(z) x1 = x0 - 1 y1 = y0 - 1 z1 = z0 - 1 u = x0 * x0 * x0 * (x0 * (6 * x0 - 15) + 10) v = y0 * y0 * y0 * (y0 * (6 * y0 - 15) + 10) w = z0 * z0 * z0 * (z0 * (6 * z0 - 15) + 10) </code></pre> <p>and <code>dot___</code>s are dot products of the gradient vectors <code>(gx___, gy___, gz___)</code>s at lattice points and the fraction coordinates:</p> <pre><code>dot000 = gx000 * x0 + gy000 * y0 + gz000 * z0 dot100 = gx100 * x1 + gy100 * y0 + gz100 * z0 dot010 = gx010 * x0 + gy010 * y1 + gz010 * z0 dot110 = gx110 * x1 + gy110 * y1 + gz110 * z0 dot001 = gx001 * x0 + gy001 * y0 + gz001 * z1 dot101 = gx101 * x1 + gy101 * y0 + gz101 * z1 dot011 = gx011 * x0 + gy011 * y1 + gz011 * z1 dot111 = gx111 * x1 + gy111 * y1 + gz111 * z1 </code></pre> <h1>Computing the derivatives</h1> <p>First, compute derivatives of <code>u</code>, <code>v</code> and <code>w</code></p> <pre><code>u' = 30 * x0 * x0 * (x0 - 1) * (x0 - 1) v' = 30 * y0 * y0 * (y0 - 1) * (y0 - 1) w' = 30 * z0 * z0 * (z0 - 1) * (z0 - 1) </code></pre> <p>By expanding <code>n</code> with <code>Lerp(a, b, t) = a + (b - a) * t</code>,</p> <pre><code>n = dot000 + u(dot100 - dot000) + v(dot010 - dot000) + w(dot001 - dot000) + uv(dot110 - dot010 - dot100 + dot000) + uw(dot101 - dot001 - dot100 + dot000) + vw(dot011 - dot001 - dot010 + dot000) + uvw(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000) </code></pre> <p>Then take partial derivatives of <code>n</code>,</p> <pre><code>nx = gx000 + u' (dot100 - dot000) + u (gx100 - gx000) + v (gx010 - gx000) + w (gx001 - gx000) + u'v (dot110 - dot010 - dot100 + dot000) + uv (gx110 - gx010 - gx100 + gx000) + u'w (dot101 - dot001 - dot100 + dot000) + uw (gx101 - gx001 - gx100 - gx000) + vw (gx011 - gx001 - gx010 + gx000) + u'vw(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000) + uvw (gx111 - gx011 - gx101 + gx001 - gx110 + gx010 + gx100 - gx000) </code></pre> <p>,</p> <pre><code>ny = gy000 + u (gy100 - gy000) + v' (dot010 - dot000) + v (gy010 - gy000) + w (gy001 - gy000) + uv' (dot110 - dot010 - dot100 + dot000) + uv (gy110 - gy010 - gy100 + gy000) + uw (gy101 - gy001 - gy100 + gy000) + v'w (dot011 - dot001 - dot010 + dot000) + vw (gy011 - gy001 - gy010 + gy000) + uv'w(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000) + uvw (gy111 - gy011 - gy101 + gy001 - gy110 + gy010 + gy100 - gy000) </code></pre> <p>,</p> <pre><code>nz = gz000 + u (gz100 - gz000) + v (gz010 - gz000) + w' (dot001 - dot000) + w (gz001 - gz000) + uv (gz110 - gz010 - gz100 + gz000) + uw' (dot101 - dot001 - dot100 + dot000) + uw (gz101 - gz001 - gz100 + gz000) + vw' (dot011 - dot001 - dot010 + dot000) + vw (gz011 - gz001 - gz010 + gz000) + uvw'(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000) + uvw (gz111 - gz011 - gz101 + gz001 - gz110 + gz010 + gz100 - gz000) </code></pre> <p>Then <code>(nx, ny, nz)</code> is the gradient vector (partial derivatives) of the noise function.</p> <h1>Optimization</h1> <p>Some common sub-expression can be factored out, if the compiler cannot handle it. For example:</p> <pre><code>uv = u * v vw = v * w uw = u * w uvw = uv * w </code></pre> <p>The coefficients in the expanded <code>n</code> are reused multiple times. They can be computed by:</p> <pre><code>k0 = dot100 - dot000 k1 = dot010 - dot000 k2 = dot001 - dot000 k3 = dot110 - dot010 - k0 k4 = dot101 - dot001 - k0 k5 = dot011 - dot001 - k1 k6 = (dot111 - dot011) - (dot101 - dot001) - k3 </code></pre> <p>Also the derivatives has similar coefficients,</p> <pre><code>gxk0 = gx100 - gx000 gxk1 = gx010 - gx000 ... </code></pre> <p>The computation of <code>n</code> can uses the expanded form with <code>k0</code>, ... <code>k6</code> as well.</p> <h1>Final words</h1> <p>This solution has been verified against central difference method.</p> <p>Although this solution looks clumsy, my experiment (CPU only, SSE) showed that, computing these derivatives by this solution only incurs about <strong>50% extra time</strong> to computing a single 3D Perlin noise sample. </p> <p>Finite difference will at least need 300% extra time (doing extra 3 samples) or 600% (doing 6 samples for central difference).</p> <p>Therefore, this solution is better in performance, and should also be more numerically stable.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload