4848 [size divisions dimensions]
4949 {:size size :divisions divisions :cellsize (/ size divisions) :dimensions dimensions})
5050
51- ; ; Here is a corresponding Midje test.
51+ ; ; Here is a corresponding [ Midje](https://github.com/marick/Midje) test.
5252; ; Note that ideally you practise [Test Driven Development (TDD)](https://martinfowler.com/bliki/TestDrivenDevelopment.html), i.e. you start with writing one failing test.
5353; ; Because this is a Clojure notebook, the unit tests are displayed after the implementation.
5454(fact " Noise parameter initialisation"
208208; ;
209209; ; Using above functions one can now implement Worley noise.
210210; ; For each pixel the distance to the closest seed point is calculated.
211- ; ; The distance to each random point in all neighbouring cells is calculated and the minimum is taken .
211+ ; ; This is achieved by determining the distance to each random point in all neighbouring cells and then taking the minimum.
212212(defn worley-noise
213213 [{:keys [size dimensions] :as params}]
214214 (let [random-points (random-points params)]
275275 [{:keys [divisions dimensions]}]
276276 (tensor/clone (tensor/compute-tensor (repeat dimensions divisions) random-gradient)))
277277
278- ; ; The function is tested that it generates 2D and 3D random gradient fields correctly .
278+ ; ; The function is verified to correctly generate 2D and 3D random gradient fields.
279279(facts " Random gradients"
280280 (with-redefs [rand (constantly 1.5 )]
281281 (dtype/shape (random-gradients {:divisions 8 :dimensions 2 }))
286286 ((random-gradients {:divisions 8 :dimensions 3 }) 0 0 0 )
287287 => (vec3 (/ 1 (sqrt 3 )) (/ 1 (sqrt 3 )) (/ 1 (sqrt 3 )))))
288288
289- ; ; The gradient field can be plotted with Plotly as a scatter plot of disconnected lines/
289+ ; ; The gradient field can be plotted with Plotly as a scatter plot of disconnected lines.
290290(let [gradients (tensor/reshape (random-gradients (make-noise-params 256 8 2 ))
291291 [(* 8 8 )])
292292 points (tensor/reshape (tensor/compute-tensor [8 8 ] (fn [y x] (vec2 x y)))
311311; ; First we define a function to determine the fractional part of a number.
312312(defn frac
313313 [x]
314- (mod x 1.0 ))
314+ (- x ( Math/floor x) ))
315315
316316(facts " Fractional part of floating point number"
317317 (frac 0.25 ) => 0.25
329329 (cell-pos {:cellsize 4 } (vec2 7 5 )) => (vec2 0.75 0.25 )
330330 (cell-pos {:cellsize 4 } (vec3 7 5 2 )) => (vec3 0.75 0.25 0.5 ))
331331
332- ; ; A tensor converting the corner vectors can be computed by subtracting the corner coordinates from the point coordinates.
332+ ; ; A 2 × 2 tensor of corner vectors can be computed by subtracting the corner coordinates from the point coordinates.
333333(defn corner-vectors
334334 [{:keys [dimensions] :as params} point]
335335 (let [cell-pos (cell-pos params point)]
338338 (fn [& args] (sub cell-pos (apply vec-n (reverse args)))))))
339339
340340(facts " Compute relative vectors from cell corners to point in cell"
341- (let [v2 (corner-vectors {:cellsize 4 :dimensions 2 } (vec2 7 6 ))
342- v3 (corner-vectors {:cellsize 4 :dimensions 3 } (vec3 7 6 5 ))]
343- (v2 0 0 ) => (vec2 0.75 0.5 )
344- (v2 0 1 ) => (vec2 -0.25 0.5 )
345- (v2 1 0 ) => (vec2 0.75 -0.5 )
346- (v2 1 1 ) => (vec2 -0.25 -0.5 )
347- (v3 0 0 0 ) => (vec3 0.75 0.5 0.25 )))
341+ (let [corners2 (corner-vectors {:cellsize 4 :dimensions 2 } (vec2 7 6 ))
342+ corners3 (corner-vectors {:cellsize 4 :dimensions 3 } (vec3 7 6 5 ))]
343+ (corners2 0 0 ) => (vec2 0.75 0.5 )
344+ (corners2 0 1 ) => (vec2 -0.25 0.5 )
345+ (corners2 1 0 ) => (vec2 0.75 -0.5 )
346+ (corners2 1 1 ) => (vec2 -0.25 -0.5 )
347+ (corners3 0 0 0 ) => (vec3 0.75 0.5 0.25 )))
348348
349349; ; ### Extract gradients of cell corners
350350; ;
351351; ; The function below retrieves the gradient values at a cell's corners, utilizing `wrap-get` for modular access.
352+ ; ; The result is a 2 × 2 tensor of gradient vectors.
352353(defn corner-gradients
353354 [{:keys [dimensions] :as params} gradients point]
354355 (let [division (map (partial division-index params) point)]
480481; ;
481482; ; ### Combination of Worley and Perlin noise
482483; ;
483- ; ; One can mix Worley and Perlin noise by simply doing a linear combination of the two .
484+ ; ; You can blend Worley and Perlin noise by performing a linear combination of both .
484485(def perlin-worley-norm (dfn/+ (dfn/* 0.3 perlin-norm) (dfn/* 0.7 worley-norm)))
485486
486487; ; Here for example is the average of Perlin and Worley noise.
567568 1 0 2 0 4 2 )
568569
569570
570- ; ; The clamp function is used to clamp a value to a range.
571+ ; ; The clamp function is used to element-wise clamp values to a range.
571572(defn clamp
572573 [value low high]
573574 (dfn/max low (dfn/min value high)))
582583
583584; ; ### Generating octaves of noise
584585; ;
585- ; ; The octaves function is to create a series of decreasing weights and normalize them so that they add up to 1.
586+ ; ; The octaves function is used to create a series of decreasing weights and normalize them so that they add up to 1.
586587(defn octaves
587588 [n decay]
588589 (let [series (take n (iterate #(* % decay) 1.0 ))
738739 (GL11/glGetTexImage GL11/GL_TEXTURE_2D 0 GL12/GL_RGBA GL11/GL_FLOAT buffer)
739740 (float-buffer->array buffer)))
740741
741- ; ; This method sets up rendering to a specified texture of specified size and then executes the body.
742+ ; ; This method sets up rendering using a specified texture as a framebuffer and then executes the body.
742743(defmacro framebuffer-render
743744 [texture width height & body]
744745 `(let [fbo# (GL30/glGenFramebuffers )]
756757 (GL30/glDeleteFramebuffers fbo#)))))
757758
758759; ; We also create a method to set up the layout of the vertex buffer.
759- ; ; Our vertex data is only going to be 3D coordinates of points.
760+ ; ; Our vertex data is only going to contain 3D coordinates of points.
760761(defn setup-point-attribute
761762 [program]
762763 (let [point-attribute (GL20/glGetAttribLocation program " point" )]
803804 (GL20/glDeleteProgram program)))))
804805
805806
806- ; ; We are going to use this simple vertex shader to simply pass through the points from the vertex buffer without any transformations.
807+ ; ; We are going to use a simple vertex shader to simply pass through the points from the vertex buffer without any transformations.
807808(def vertex-passthrough
808809" #version 130
809810in vec3 point;
@@ -839,7 +840,7 @@ float noise(vec3 idx)
839840}" )
840841
841842; ; We can test this mock function using the following probing shader.
842- ; ; Note that we are using the `template` macro of the `comb` Clojure library to generate the shader code from a template.
843+ ; ; Note that we are using the `template` macro of the `comb` Clojure library to generate the probing shader code from a template.
843844(def noise-probe
844845 (template/fn [x y z]
845846" #version 130
@@ -852,7 +853,8 @@ void main()
852853
853854; ; Here multiple tests are run to test that the mock implements a checkboard pattern correctly.
854855(tabular " Test noise mock"
855- (fact (nth (render-pixel [vertex-passthrough] [noise-mock (noise-probe ?x ?y ?z)]) 0 )
856+ (fact (nth (render-pixel [vertex-passthrough]
857+ [noise-mock (noise-probe ?x ?y ?z)]) 0 )
856858 => ?result)
857859 ?x ?y ?z ?result
858860 0 0 0 0.0
@@ -945,7 +947,7 @@ void main()
945947 fragColor = vec4(ray_box(box_min, box_max, origin, direction), 0, 0);
946948}" ))
947949
948- ; ; The shader is tested with different ray origins and directions.
950+ ; ; The `ray-box` shader is tested with different ray origins and directions.
949951(tabular " Test intersection of ray with box"
950952 (fact ((juxt first second)
951953 (render-pixel [vertex-passthrough]
@@ -1056,7 +1058,7 @@ void main()
10561058; ;
10571059; ; The following fragment shader is used to render an image of a box filled with fog.
10581060; ;
1059- ; ; * The pixel coordinate and the resolution of the image are used to determine a viewing direction which also gets rotated using the rotation matrix.
1061+ ; ; * The pixel coordinate and the resolution of the image are used to determine a viewing direction which also gets rotated using the rotation matrix and normalized .
10601062; ; * The origin of the camera is set at a specified distance to the center of the box and rotated as well.
10611063; ; * The ray box function is used to determine the near and far intersection points of the ray with the box.
10621064; ; * The cloud transfer function is used to sample the cloud density along the ray and determine the overall opacity and color of the fog box.
@@ -1074,17 +1076,19 @@ vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
10741076vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
10751077void main()
10761078{
1077- vec3 direction = normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
1079+ vec3 direction =
1080+ normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
10781081 vec3 origin = rotation * vec3(0, 0, -distance);
10791082 vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), origin, direction);
10801083 vec4 transfer = cloud_transfer(origin, direction, interval);
1081- vec3 background = mix(vec3(0.125, 0.125, 0.25), vec3(1, 1, 1), pow(dot(direction, light), 1000.0));
1084+ vec3 background = mix(vec3(0.125, 0.125, 0.25), vec3(1, 1, 1),
1085+ pow(dot(direction, light), 1000.0));
10821086 fragColor = vec4(background * (1.0 - transfer.a) + transfer.rgb, 1.0);
10831087}" )
10841088
10851089; ; Uniform variables are parameters that remain constant throughout the shader execution, unlike vertex input data.
10861090; ; Here we use the following uniform variables:
1087- ; ; * **resolution: a 2D vector containing the window pixel width and height
1091+ ; ; * **resolution** : a 2D vector containing the window pixel width and height
10881092; ; * **light:** a 3D unit vector pointing to the light source
10891093; ; * **rotation:** a 3x3 rotation matrix to rotate the camera around the origin
10901094; ; * **focal_length:** the ratio of camera focal length to pixel size of the virtual camera
@@ -1224,7 +1228,9 @@ out vec4 fragColor;
12241228float remap_clamp(float value, float low1, float high1, float low2, float high2);
12251229void main()
12261230{
1227- fragColor = vec4(remap_clamp(<%= value %>, <%= low1 %>, <%= high1 %>, <%= low2 %>, <%= high2 %>));
1231+ fragColor = vec4(remap_clamp(<%= value %>,
1232+ <%= low1 %>, <%= high1 %>,
1233+ <%= low2 %>, <%= high2 %>));
12281234}" ))
12291235
12301236; ; `remap_clamp` is tested using a parametrized tests.
@@ -1291,7 +1297,8 @@ float remap_noise(vec3 idx)
12911297uniform vec3 light;
12921298float mie(float mu)
12931299{
1294- return 3 * (1 - G * G) * (1 + mu * mu) / (8 * M_PI * (2 + G * G) * pow(1 + G * G - 2 * G * mu, 1.5));
1300+ return 3 * (1 - G * G) * (1 + mu * mu) /
1301+ (8 * M_PI * (2 + G * G) * pow(1 + G * G - 2 * G * mu, 1.5));
12951302}
12961303float in_scatter(vec3 point, vec3 direction)
12971304{
@@ -1312,7 +1319,8 @@ void main()
13121319
13131320; ; The shader is tested using a few values.
13141321(tabular " Shader function for scattering phase function"
1315- (fact (first (render-pixel [vertex-passthrough] [(mie-scatter ?g) (mie-probe ?mu)]))
1322+ (fact (first (render-pixel [vertex-passthrough]
1323+ [(mie-scatter ?g) (mie-probe ?mu)]))
13161324 => (roughly ?result 1e-6 ))
13171325 ?g ?mu ?result
13181326 0 0 (/ 3 (* 16 PI))
@@ -1325,18 +1333,18 @@ void main()
13251333(defn scatter-amount [theta]
13261334 (first (render-pixel [vertex-passthrough] [(mie-scatter 0.76 ) (mie-probe (cos theta))])))
13271335
1328- ; ; We can use this function to plot a mix of isotropic and anisotropic scattering .
1336+ ; ; We can use this function to plot Mie scattering for different angles .
13291337(let [scatter
13301338 (tc/dataset {:x (map (fn [theta]
13311339 (* (cos (to-radians theta))
1332- (+ 0.75 ( * 0.25 ( scatter-amount (to-radians theta)) ))))
1340+ (scatter-amount (to-radians theta))))
13331341 (range 361 ))
13341342 :y (map (fn [theta]
13351343 (* (sin (to-radians theta))
1336- (+ 0.75 ( * 0.25 ( scatter-amount (to-radians theta)) ))))
1344+ (scatter-amount (to-radians theta))))
13371345 (range 361 )) })]
13381346 (-> scatter
1339- (plotly/base {:=title " Mixed Mie and isotropic scattering" :=mode " lines" })
1347+ (plotly/base {:=title " Mie scattering" :=mode " lines" })
13401348 (plotly/layer-point {:=x :x :=y :y })
13411349 plotly/plot
13421350 (assoc-in [:layout :yaxis :scaleanchor ] " x" )))
@@ -1394,6 +1402,7 @@ float shadow(vec3 point)
13941402
13951403; ; ## Further topics
13961404; ;
1405+ ; ; I hope you enjoyed this little tour of volumetric clouds.
13971406; ; Here are some references to get from a cloud prototype to more realistic clouds.
13981407; ;
13991408; ; * [Vertical density profile](https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/)
0 commit comments