|
29 | 29 | [org.lwjgl.opengl GL GL11 GL12 GL13 GL15 GL20 GL30 GL32 GL42])) |
30 | 30 |
|
31 | 31 |
|
32 | | -;; # Procedural generation of volumetric clouds |
33 | | -;; |
34 | 32 | ;; Volumetric clouds are commonly used in flight simulators and visual effects. |
35 | 33 | ;; For a introductory video see [Sebastian Lague's video "Coding Adventure: Clouds](https://www.youtube.com/watch?v=4QOcCGI6xOU). |
36 | 34 | ;; Note that this article is about procedural generation and not about simulating real weather. |
|
39 | 37 | ;; |
40 | 38 | ;; [Worley noise](https://en.wikipedia.org/wiki/Worley_noise) is a type of structured noise which is defined for each pixel using the distance to the nearest seed point. |
41 | 39 | ;; |
42 | | -;; ### Noise parameters |
| 40 | +;; #### Noise parameters |
43 | 41 | ;; |
44 | 42 | ;; First we define a function to create parameters of the noise. |
45 | 43 | ;; |
|
57 | 55 | (make-noise-params 256 8 2) => {:size 256 :divisions 8 :cellsize 32 :dimensions 2}) |
58 | 56 |
|
59 | 57 |
|
60 | | -;; ### 2D and 3D vectors |
| 58 | +;; #### 2D and 3D vectors |
61 | 59 | ;; |
62 | 60 | ;; Next we need a function which allows us to create 2D or 3D vectors depending on the number of input parameters. |
63 | 61 | (defn vec-n |
|
69 | 67 | (vec-n 2 3 1) => (vec3 2 3 1)) |
70 | 68 |
|
71 | 69 |
|
72 | | -;; ### Random points |
| 70 | +;; #### Random points |
73 | 71 | ;; |
74 | 72 | ;; The following method generates a random point in a cell specified by the cell indices. |
75 | 73 | (defn random-point-in-cell |
|
115 | 113 | (plotly/layer-point {:=x :x :=y :y}))) |
116 | 114 |
|
117 | 115 |
|
118 | | -;; ### Modular distance |
| 116 | +;; #### Modular distance |
119 | 117 | ;; |
120 | 118 | ;; In order to get a periodic noise array, we need to component-wise wrap around distance vectors. |
121 | 119 | (defn mod-vec |
|
158 | 156 | 0 5 0 0 3.0) |
159 | 157 |
|
160 | 158 |
|
161 | | -;; ### Modular lookup |
| 159 | +;; #### Modular lookup |
162 | 160 | ;; |
163 | 161 | ;; We also need to lookup elements with wrap around. |
164 | 162 | ;; We recursively use `tensor/select` and then finally the tensor as a function to lookup along each axis. |
|
187 | 185 | (division-index {:cellsize 4} 7.5) => 1 |
188 | 186 | (division-index {:cellsize 4} -0.5) => -1) |
189 | 187 |
|
190 | | -;; ### Getting indices of Neighbours |
| 188 | +;; #### Getting indices of Neighbours |
191 | 189 | ;; |
192 | 190 | ;; The following function determines the neighbouring indices of a cell recursing over each dimension. |
193 | 191 | (defn neighbours |
|
204 | 202 | (neighbours 1 10) => [[0 9] [1 9] [2 9] [0 10] [1 10] [2 10] [0 11] [1 11] [2 11]]) |
205 | 203 |
|
206 | 204 |
|
207 | | -;; ### Sampling Worley noise |
| 205 | +;; #### Sampling Worley noise |
208 | 206 | ;; |
209 | 207 | ;; Using above functions one can now implement Worley noise. |
210 | 208 | ;; For each pixel the distance to the closest seed point is calculated. |
|
240 | 238 | ;; [Perlin noise](https://adrianb.io/2014/08/09/perlinnoise.html) is generated by choosing a random gradient vector at each cell corner. |
241 | 239 | ;; The noise tensor's intermediate values are interpolated with a continuous function, utilizing the gradient at the corner points. |
242 | 240 |
|
243 | | -;; ### Random gradients |
| 241 | +;; #### Random gradients |
244 | 242 | ;; |
245 | 243 | ;; The 2D or 3D gradients are generated by creating a vector where each component is set to a random number between -1 and 1. |
246 | 244 | ;; Random vectors are generated until the vector length is greater 0 and lower or equal to 1. |
|
305 | 303 | (plotly/base {:=title "Random gradients" :=mode "lines"}) |
306 | 304 | (plotly/layer-point {:=x :x :=y :y}))) |
307 | 305 |
|
308 | | -;; ### Corner vectors |
| 306 | +;; #### Corner vectors |
309 | 307 | ;; |
310 | 308 | ;; The next step is to determine the vectors to the corners of the cell for a given point. |
311 | 309 | ;; First we define a function to determine the fractional part of a number. |
|
346 | 344 | (v2 1 1) => (vec2 -0.25 -0.5) |
347 | 345 | (v3 0 0 0) => (vec3 0.75 0.5 0.25))) |
348 | 346 |
|
349 | | -;; ### Extract gradients of cell corners |
| 347 | +;; #### Extract gradients of cell corners |
350 | 348 | ;; |
351 | 349 | ;; The function below retrieves the gradient values at a cell's corners, utilizing `wrap-get` for modular access. |
352 | 350 | (defn corner-gradients |
|
372 | 370 | ((corner-gradients {:cellsize 4 :dimensions 3} gradients3 (vec3 9 6 3)) 0 0 0) |
373 | 371 | => (vec3 2 1 0))) |
374 | 372 |
|
375 | | -;; ### Influence values |
| 373 | +;; #### Influence values |
376 | 374 | ;; |
377 | 375 | ;; The influence value is the function value of the function with the selected random gradient at a corner. |
378 | 376 | (defn influence-values |
|
395 | 393 | (influence2 1 1) => 11.0 |
396 | 394 | (influence3 1 1 1) => 111.0)) |
397 | 395 |
|
398 | | -;; ### Interpolating the influence values |
| 396 | +;; #### Interpolating the influence values |
399 | 397 | ;; |
400 | 398 | ;; For interpolation the following "ease curve" is used. |
401 | 399 | (defn ease-curve |
|
437 | 435 | (weights3 0 0 0) => (roughly 0.010430 1e-6))) |
438 | 436 |
|
439 | 437 |
|
440 | | -;; ### Sampling Perlin noise |
| 438 | +;; #### Sampling Perlin noise |
441 | 439 | ;; |
442 | 440 | ;; A Perlin noise sample is computed by |
443 | 441 | ;; * Getting the random gradients for the cell corners. |
|
478 | 476 |
|
479 | 477 | ;; ## Mixing noise values |
480 | 478 | ;; |
481 | | -;; ### Combination of Worley and Perlin noise |
| 479 | +;; #### Combination of Worley and Perlin noise |
482 | 480 | ;; |
483 | 481 | ;; One can mix Worley and Perlin noise by simply doing a linear combination of the two. |
484 | 482 | (def perlin-worley-norm (dfn/+ (dfn/* 0.3 perlin-norm) (dfn/* 0.7 worley-norm))) |
485 | 483 |
|
486 | 484 | ;; Here for example is the average of Perlin and Worley noise. |
487 | 485 | (bufimg/tensor->image (dfn/+ (dfn/* 0.5 perlin-norm) (dfn/* 0.5 worley-norm))) |
488 | 486 |
|
489 | | -;; ### Interpolation |
| 487 | +;; #### Interpolation |
490 | 488 | ;; |
491 | 489 | ;; One can linearly interpolate tensor values by recursing over the dimensions as follows. |
492 | 490 | (defn interpolate |
|
517 | 515 | (interpolate y3 2.5 3.5 3.0) => 3.0 |
518 | 516 | (interpolate z3 2.5 3.5 5.5) => 2.0)) |
519 | 517 |
|
520 | | -;; ### Octaves of noise |
| 518 | +;; #### Octaves of noise |
521 | 519 | ;; |
522 | 520 | ;; Fractal Brownian Motion is implemented by computing a weighted sum of the same base noise function using different frequencies. |
523 | 521 | (defn fractal-brownian-motion |
|
547 | 545 | (fractal-brownian-motion base1 [0.0 1.0] 0.0) => 0.0 |
548 | 546 | (fractal-brownian-motion base1 [0.0 1.0] 0.5) => 1.0)) |
549 | 547 |
|
550 | | -;; ### Remapping and clamping |
| 548 | +;; #### Remapping and clamping |
551 | 549 | ;; |
552 | 550 | ;; The remap function is used to map a range of values of an input tensor to a different range. |
553 | 551 | (defn remap |
|
580 | 578 | 0 2 3 2 |
581 | 579 | 4 2 3 3) |
582 | 580 |
|
583 | | -;; ### Generating octaves of noise |
| 581 | +;; #### Generating octaves of noise |
584 | 582 | ;; |
585 | 583 | ;; The octaves function is to create a series of decreasing weights and normalize them so that they add up to 1. |
586 | 584 | (defn octaves |
|
609 | 607 | low high 0 255) |
610 | 608 | 0 255))) |
611 | 609 |
|
612 | | -;; ### 2D examples |
| 610 | +;; #### 2D examples |
613 | 611 | ;; |
614 | 612 | ;; Here is an example of 4 octaves of Worley noise. |
615 | 613 | (bufimg/tensor->image (noise-octaves worley-norm (octaves 4 0.6) 120 230)) |
|
621 | 619 | (bufimg/tensor->image (noise-octaves perlin-worley-norm (octaves 4 0.6) 120 230)) |
622 | 620 |
|
623 | 621 |
|
624 | | -;; ## Testing shaders |
| 622 | +;; ## Volumetric clouds |
625 | 623 |
|
| 624 | +;; #### OpenGL setup |
| 625 | +;; |
| 626 | +;; In order to render the clouds we create a window and an OpenGL context. |
626 | 627 | (GLFW/glfwInit) |
627 | 628 |
|
628 | 629 | (def window-width 640) |
|
635 | 636 | (GLFW/glfwMakeContextCurrent window) |
636 | 637 | (GL/createCapabilities) |
637 | 638 |
|
638 | | - |
| 639 | +;; #### Compiling and linking shader programs |
| 640 | +;; |
| 641 | +;; The following method is used compile a shader program. |
639 | 642 | (defn make-shader [source shader-type] |
640 | 643 | (let [shader (GL20/glCreateShader shader-type)] |
641 | 644 | (GL20/glShaderSource shader source) |
|
664 | 667 | program)) |
665 | 668 |
|
666 | 669 |
|
667 | | -(def vertex-test " |
668 | | -#version 130 |
| 670 | +(def vertex-test |
| 671 | +"#version 130 |
669 | 672 | in vec3 point; |
670 | 673 | void main() |
671 | 674 | { |
672 | 675 | gl_Position = vec4(point, 1); |
673 | 676 | }") |
674 | 677 |
|
675 | 678 |
|
676 | | -(def fragment-test " |
677 | | -#version 130 |
| 679 | +(def fragment-test |
| 680 | +"#version 130 |
678 | 681 | out vec4 fragColor; |
679 | 682 | void main() |
680 | 683 | { |
|
0 commit comments