Skip to content

Commit 28aa17e

Browse files
committed
Explaining Perlin noise
1 parent 461d903 commit 28aa17e

1 file changed

Lines changed: 33 additions & 20 deletions

File tree

src/volumetric_clouds/main.clj

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205

206206

207207
;; ### Sampling Worley noise
208+
;;
208209
;; Using above functions one can now implement Worley noise.
209210
;; For each pixel the distance to the closest seed point is calculated.
210211
;; The distance to each random point in all neighbouring cells is calculated and the minimum is taken.
@@ -226,7 +227,7 @@
226227
;; Here a 256x256 Worley noise tensor is created.
227228
(def worley (worley-noise (make-noise-params 256 8 2)))
228229

229-
;; The values are normalised to be between 0 and 255.
230+
;; The values are inverted and normalised to be between 0 and 255.
230231
(def worley-norm
231232
(dfn/* (/ 255 (- (dfn/reduce-max worley) (dfn/reduce-min worley)))
232233
(dfn/- (dfn/reduce-max worley) worley)))
@@ -304,37 +305,38 @@
304305
(plotly/base {:=title "Random gradients" :=mode "lines"})
305306
(plotly/layer-point {:=x :x :=y :y})))
306307

307-
308+
;; ### Corner vectors
309+
;;
310+
;; The next step is to determine the vectors to the corners of the cell for a given point.
311+
;; First we define a function to determine the fractional part of a number.
308312
(defn frac
309313
[x]
310-
(- x (Math/floor x)))
311-
314+
(mod x 1.0))
312315

313316
(facts "Fractional part of floating point number"
314317
(frac 0.25) => 0.25
315318
(frac 1.75) => 0.75
316319
(frac -0.25) => 0.75)
317320

318321

322+
;; This function can be used to determine the relative position of a point in a cell.
319323
(defn cell-pos
320324
[{:keys [cellsize]} point]
321325
(apply vec-n (map frac (div point cellsize))))
322326

323-
324327
(facts "Relative position of point in a cell"
325328
(cell-pos {:cellsize 4} (vec2 2 3)) => (vec2 0.5 0.75)
326329
(cell-pos {:cellsize 4} (vec2 7 5)) => (vec2 0.75 0.25)
327330
(cell-pos {:cellsize 4} (vec3 7 5 2)) => (vec3 0.75 0.25 0.5))
328331

329-
332+
;; A tensor converting the corner vectors can be computed by subtracting the corner coordinates from the point coordinates.
330333
(defn corner-vectors
331334
[{:keys [dimensions] :as params} point]
332335
(let [cell-pos (cell-pos params point)]
333336
(tensor/compute-tensor
334337
(repeat dimensions 2)
335338
(fn [& args] (sub cell-pos (apply vec-n (reverse args)))))))
336339

337-
338340
(facts "Compute relative vectors from cell corners to point in cell"
339341
(let [v2 (corner-vectors {:cellsize 4 :dimensions 2} (vec2 7 6))
340342
v3 (corner-vectors {:cellsize 4 :dimensions 3} (vec3 7 6 5))]
@@ -344,15 +346,16 @@
344346
(v2 1 1) => (vec2 -0.25 -0.5)
345347
(v3 0 0 0) => (vec3 0.75 0.5 0.25)))
346348

347-
349+
;; ### Extract gradients of cell corners
350+
;;
351+
;; The function below retrieves the gradient values at a cell's corners, utilizing `wrap-get` for modular access.
348352
(defn corner-gradients
349353
[{:keys [dimensions] :as params} gradients point]
350354
(let [division (map (partial division-index params) point)]
351355
(tensor/compute-tensor
352356
(repeat dimensions 2)
353357
(fn [& coords] (apply wrap-get gradients (map + (reverse division) coords))))))
354358

355-
356359
(facts "Get 2x2 tensor of gradients from a larger tensor using wrap around"
357360
(let [gradients2 (tensor/compute-tensor [4 6] (fn [y x] (vec2 x y)))
358361
gradients3 (tensor/compute-tensor [4 6 8] (fn [z y x] (vec3 x y z))) ]
@@ -369,15 +372,16 @@
369372
((corner-gradients {:cellsize 4 :dimensions 3} gradients3 (vec3 9 6 3)) 0 0 0)
370373
=> (vec3 2 1 0)))
371374

372-
375+
;; ### Influence values
376+
;;
377+
;; The influence value is the function value of the function with the selected random gradient at a corner.
373378
(defn influence-values
374379
[gradients vectors]
375380
(tensor/compute-tensor
376381
(repeat (count (dtype/shape gradients)) 2)
377382
(fn [& args] (dot (apply gradients args) (apply vectors args)))
378383
:double))
379384

380-
381385
(facts "Compute influence values from corner vectors and gradients"
382386
(let [gradients2 (tensor/compute-tensor [2 2] (fn [_y x] (vec2 x 10)))
383387
vectors2 (tensor/compute-tensor [2 2] (fn [y _x] (vec2 1 y)))
@@ -391,26 +395,27 @@
391395
(influence2 1 1) => 11.0
392396
(influence3 1 1 1) => 111.0))
393397

394-
398+
;; ### Interpolating the influence values
399+
;;
400+
;; For interpolation the following "ease curve" is used.
395401
(defn ease-curve
396402
[t]
397403
(-> t (* 6.0) (- 15.0) (* t) (+ 10.0) (* t t t)))
398404

399-
400405
(facts "Monotonously increasing function with zero derivative at zero and one"
401406
(ease-curve 0.0) => 0.0
402407
(ease-curve 0.25) => (roughly 0.103516 1e-6)
403408
(ease-curve 0.5) => 0.5
404409
(ease-curve 0.75) => (roughly 0.896484 1e-6)
405410
(ease-curve 1.0) => 1.0)
406411

407-
412+
;; The ease curve monotonously increases in the interval from zero to one.
408413
(-> (tc/dataset {:t (range 0.0 1.025 0.025)
409414
:ease (map ease-curve (range 0.0 1.025 0.025))})
410415
(plotly/base {:=title "Ease Curve"})
411416
(plotly/layer-line {:=x :t :=y :ease}))
412417

413-
418+
;; The interpolation weights are recursively calculated from the ease curve and the coordinate distances of the point to upper and lower cell boundary.
414419
(defn interpolation-weights
415420
([params point]
416421
(interpolation-weights (cell-pos params point)))
@@ -422,7 +427,6 @@
422427
(tensor/->tensor [(dfn/* (ease-curve w1) elem) (dfn/* (ease-curve w2) elem)]))
423428
1.0)))
424429

425-
426430
(facts "Interpolation weights"
427431
(let [weights2 (interpolation-weights {:cellsize 8} (vec2 2 7))
428432
weights3 (interpolation-weights {:cellsize 8} (vec3 2 7 3))]
@@ -433,6 +437,14 @@
433437
(weights3 0 0 0) => (roughly 0.010430 1e-6)))
434438

435439

440+
;; ### Sampling Perlin noise
441+
;;
442+
;; A Perlin noise sample is computed by
443+
;; * Getting the random gradients for the cell corners.
444+
;; * Getting the corner vectors for the cell corners.
445+
;; * Computing the influence values which have the desired gradients.
446+
;; * Determining the interpolation weights.
447+
;; * Computing the weighted sum of the influence values.
436448
(defn perlin-sample
437449
[params gradients point]
438450
(let [gradients (corner-gradients params gradients point)
@@ -441,26 +453,27 @@
441453
weights (interpolation-weights params point)]
442454
(dfn/reduce-+ (dfn/* weights influence))))
443455

444-
456+
;; Now one can sample the Perlin noise by performing above computation for the center of each pixel.
445457
(defn perlin-noise
446458
[{:keys [size dimensions] :as params}]
447459
(let [gradients (random-gradients params)]
448460
(tensor/clone
449461
(tensor/compute-tensor
450462
(repeat dimensions size)
451463
(fn [& args]
452-
(let [center (add (apply vec-n (reverse args))
453-
(apply vec-n (repeat dimensions 0.5)))]
464+
(let [center (apply vec-n (map #(+ % 0.5) (reverse args)))]
454465
(perlin-sample params gradients center)))
455466
:double))))
456467

457-
468+
;; Here a 256x256 Perlin noise tensor is created.
458469
(def perlin (perlin-noise (make-noise-params 256 8 2)))
459470

471+
;; The values are normalised to be between 0 and 255.
460472
(def perlin-norm
461473
(dfn/* (/ 255 (- (dfn/reduce-max perlin) (dfn/reduce-min perlin)))
462474
(dfn/- perlin (dfn/reduce-min perlin))))
463475

476+
;; Finally one can display the noise.
464477
(bufimg/tensor->image perlin-norm)
465478

466479
;; ## Combination of Worley and Perlin noise

0 commit comments

Comments
 (0)